From cf4eb360bbdb546ca2d26f9ffb295657e034d079 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 12 Jun 2019 11:24:44 +0300 Subject: [PATCH] Expenses --- lib/data/models/expense_model.dart | 8 +- lib/redux/dashboard/dashboard_selectors.dart | 131 +++++++++++++++++-- lib/ui/dashboard/dashboard_panels.dart | 27 ++++ lib/ui/expense/expense_list.dart | 1 - lib/ui/expense/view/expense_view.dart | 2 +- 5 files changed, 158 insertions(+), 11 deletions(-) diff --git a/lib/data/models/expense_model.dart b/lib/data/models/expense_model.dart index 4f67c9d34..fbae01d69 100644 --- a/lib/data/models/expense_model.dart +++ b/lib/data/models/expense_model.dart @@ -275,7 +275,9 @@ abstract class ExpenseEntity extends Object return true; } else if (status.id == kExpenseStatusPending && isPending) { return true; - } else if (status.id == kExpenseStatusLogged && !isInvoiced && !isPending) { + } else if (status.id == kExpenseStatusLogged && + !isInvoiced && + !isPending) { return true; } } @@ -292,6 +294,10 @@ abstract class ExpenseEntity extends Object } } + bool isBetween(String startDate, String endDate) { + return startDate.compareTo(endDate) <= 0 && endDate.compareTo(endDate) >= 0; + } + @override double get listDisplayAmount => null; diff --git a/lib/redux/dashboard/dashboard_selectors.dart b/lib/redux/dashboard/dashboard_selectors.dart index f79e6b648..2bf5c5b83 100644 --- a/lib/redux/dashboard/dashboard_selectors.dart +++ b/lib/redux/dashboard/dashboard_selectors.dart @@ -320,14 +320,6 @@ List chartPayments( return data; } -var memoizedChartTasks = memo6((CompanyEntity company, - DashboardUIState settings, - BuiltMap taskMap, - BuiltMap invoiceMap, - BuiltMap projectMap, - BuiltMap clientMap) => - chartTasks(company, settings, taskMap, invoiceMap, projectMap, clientMap)); - List chartTasks( CompanyEntity company, DashboardUIState settings, @@ -436,3 +428,126 @@ List chartTasks( return data; } + +var memoizedChartTasks = memo6((CompanyEntity company, + DashboardUIState settings, + BuiltMap taskMap, + BuiltMap invoiceMap, + BuiltMap projectMap, + BuiltMap clientMap) => + chartTasks(company, settings, taskMap, invoiceMap, projectMap, clientMap)); + +List chartExpenses( + CompanyEntity company, + DashboardUIState settings, + BuiltMap invoiceMap, + BuiltMap expenseMap) { + const STATUS_LOGGED = 'logged'; + const STATUS_PENDING = 'pending'; + const STATUS_INVOICED = 'invoiced'; + const STATUS_PAID = 'paid'; + + final Map counts = { + STATUS_LOGGED: 0, + STATUS_PENDING: 0, + STATUS_INVOICED: 0, + STATUS_PAID: 0, + }; + + final Map> totals = { + STATUS_LOGGED: {}, + STATUS_PENDING: {}, + STATUS_INVOICED: {}, + STATUS_PAID: {}, + }; + + expenseMap.forEach((int, expense) { + final currencyId = expense.expenseCurrencyId; + final date = expense.expenseDate; + final amount = expense.amountWithTax; + + if (expense.isDeleted) { + // skip it + } else if (!expense.isBetween( + settings.startDate(company), settings.endDate(company))) { + // skip it + } else if (settings.currencyId > 0 && settings.currencyId != currencyId) { + // skip it + } else { + if (totals[STATUS_LOGGED][date] == null) { + totals[STATUS_LOGGED][date] = 0.0; + totals[STATUS_PENDING][date] = 0.0; + totals[STATUS_INVOICED][date] = 0.0; + totals[STATUS_PAID][date] = 0.0; + } + + if (expense.isInvoiced) { + final invoice = invoiceMap[expense.invoiceId] ?? InvoiceEntity(); + if (invoice.isPaid) { + totals[STATUS_PAID][date] += amount; + counts[STATUS_PAID]++; + } else { + totals[STATUS_INVOICED][date] += amount; + counts[STATUS_INVOICED]++; + } + } else if (expense.isPending) { + totals[STATUS_PENDING][date] += amount; + counts[STATUS_PENDING]++; + } else { + totals[STATUS_LOGGED][date] += amount; + counts[STATUS_LOGGED]++; + } + } + }); + + final ChartDataGroup loggedData = ChartDataGroup(STATUS_LOGGED); + final ChartDataGroup pendingData = ChartDataGroup(STATUS_PENDING); + final ChartDataGroup invoicedData = ChartDataGroup(STATUS_INVOICED); + final ChartDataGroup paidData = ChartDataGroup(STATUS_PAID); + + var date = DateTime.parse(settings.startDate(company)); + final endDate = DateTime.parse(settings.endDate(company)); + + while (!date.isAfter(endDate)) { + final key = convertDateTimeToSqlDate(date); + if (totals[STATUS_LOGGED].containsKey(key)) { + loggedData.rawSeries + .add(ChartMoneyData(date, totals[STATUS_LOGGED][key])); + loggedData.total += totals[STATUS_LOGGED][key]; + pendingData.rawSeries + .add(ChartMoneyData(date, totals[STATUS_PENDING][key])); + pendingData.total += totals[STATUS_PENDING][key]; + invoicedData.rawSeries + .add(ChartMoneyData(date, totals[STATUS_INVOICED][key])); + invoicedData.total += totals[STATUS_INVOICED][key]; + paidData.rawSeries.add(ChartMoneyData(date, totals[STATUS_PAID][key])); + paidData.total += totals[STATUS_PAID][key]; + } else { + loggedData.rawSeries.add(ChartMoneyData(date, 0.0)); + pendingData.rawSeries.add(ChartMoneyData(date, 0.0)); + invoicedData.rawSeries.add(ChartMoneyData(date, 0.0)); + paidData.rawSeries.add(ChartMoneyData(date, 0.0)); + } + date = date.add(Duration(days: 1)); + } + + loggedData.average = + round(loggedData.total ?? 0 / counts[STATUS_LOGGED] ?? 0, 2); + invoicedData.average = + round(invoicedData.total ?? 0 / counts[STATUS_INVOICED] ?? 0, 2); + paidData.average = round(paidData.total ?? 0 / counts[STATUS_PAID] ?? 0, 2); + + final List data = [ + loggedData, + invoicedData, + paidData, + ]; + + return data; +} + +var memoizedChartExpenses = memo4((CompanyEntity company, + DashboardUIState settings, + BuiltMap invoiceMap, + BuiltMap expenseMap) => + chartExpenses(company, settings, invoiceMap, expenseMap)); diff --git a/lib/ui/dashboard/dashboard_panels.dart b/lib/ui/dashboard/dashboard_panels.dart index 50e917c6c..a2064b8c1 100644 --- a/lib/ui/dashboard/dashboard_panels.dart +++ b/lib/ui/dashboard/dashboard_panels.dart @@ -280,6 +280,30 @@ class DashboardPanels extends StatelessWidget { title: AppLocalization.of(context).tasks); } + Widget _expenseChart(BuildContext context) { + final settings = viewModel.dashboardUIState; + final state = viewModel.state; + final isLoaded = state.expenseState.isLoaded; + final currentData = memoizedChartExpenses(state.selectedCompany, settings, + state.invoiceState.map, state.expenseState.map); + + List previousData; + if (settings.enableComparison) { + previousData = memoizedChartExpenses( + state.selectedCompany, + settings.rebuild((b) => b..offset += 1), + state.invoiceState.map, + state.expenseState.map); + } + + return _buildChart( + context: context, + currentData: currentData, + previousData: previousData, + isLoaded: isLoaded, + title: AppLocalization.of(context).expenses); + } + @override Widget build(BuildContext context) { final state = viewModel.state; @@ -300,6 +324,9 @@ class DashboardPanels extends StatelessWidget { company.isModuleEnabled(EntityType.task) ? _taskChart(context) : SizedBox(), + company.isModuleEnabled(EntityType.expense) + ? _expenseChart(context) + : SizedBox(), ], ), ConstrainedBox( diff --git a/lib/ui/expense/expense_list.dart b/lib/ui/expense/expense_list.dart index ce58e4a59..6dc575414 100644 --- a/lib/ui/expense/expense_list.dart +++ b/lib/ui/expense/expense_list.dart @@ -55,7 +55,6 @@ class ExpenseList extends StatelessWidget { Widget build(BuildContext context) { final localization = AppLocalization.of(context); final listState = viewModel.listState; - final filteredClientId = listState.filterEntityId; final state = viewModel.state; final widgets = []; BaseEntity filteredEntity; diff --git a/lib/ui/expense/view/expense_view.dart b/lib/ui/expense/view/expense_view.dart index bc05f23eb..d0f25b528 100644 --- a/lib/ui/expense/view/expense_view.dart +++ b/lib/ui/expense/view/expense_view.dart @@ -37,7 +37,7 @@ class _ExpenseViewState extends State @override Widget build(BuildContext context) { - final localization = AppLocalization.of(context); + //final localization = AppLocalization.of(context); final viewModel = widget.viewModel; return WillPopScope(