import 'package:charts_common/common.dart'; import 'package:invoiceninja_flutter/redux/dashboard/dashboard_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_selectors.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:memoize/memoize.dart'; import 'package:built_collection/built_collection.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; class ChartDataGroup { ChartDataGroup(this.name); final String name; final List rawSeries = []; List> chartSeries; double total = 0.0; double average = 0.0; double previousTotal = 0.0; } class ChartMoneyData { ChartMoneyData(this.date, this.amount); final DateTime date; final double amount; } var memoizedChartInvoices = memo4((CompanyEntity company, DashboardUIState settings, BuiltMap invoiceMap, BuiltMap clientMap) => chartInvoices( company: company, settings: settings, invoiceMap: invoiceMap, clientMap: clientMap)); List chartInvoices({ CompanyEntity company, DashboardUIState settings, BuiltMap invoiceMap, BuiltMap clientMap, }) { const STATUS_ACTIVE = 'active'; const STATUS_OUTSTANDING = 'outstanding'; final Map counts = { STATUS_ACTIVE: 0, STATUS_OUTSTANDING: 0, }; final Map> totals = { STATUS_ACTIVE: {}, STATUS_OUTSTANDING: {}, }; invoiceMap.forEach((int, invoice) { final client = clientMap[invoice.clientId] ?? ClientEntity(id: invoice.clientId); final currencyId = client.currencyId > 0 ? client.currencyId : company.currencyId; if (!invoice.isPublic || invoice.isDeleted || client.isDeleted || invoice.isRecurring) { // skip it } else if (!invoice.isBetween( settings.startDate(company), settings.endDate(company))) { // skip it } else if (settings.currencyId > 0 && settings.currencyId != currencyId) { // skip it } else { if (totals[STATUS_ACTIVE][invoice.invoiceDate] == null) { totals[STATUS_ACTIVE][invoice.invoiceDate] = 0.0; totals[STATUS_OUTSTANDING][invoice.invoiceDate] = 0.0; } totals[STATUS_ACTIVE][invoice.invoiceDate] += invoice.amount; totals[STATUS_OUTSTANDING][invoice.invoiceDate] += invoice.balance; counts[STATUS_ACTIVE]++; if (invoice.balance > 0) { counts[STATUS_OUTSTANDING]++; } } }); final ChartDataGroup activeData = ChartDataGroup(STATUS_ACTIVE); final ChartDataGroup outstandingData = ChartDataGroup(STATUS_OUTSTANDING); 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_ACTIVE].containsKey(key)) { activeData.rawSeries .add(ChartMoneyData(date, totals[STATUS_ACTIVE][key])); activeData.total += totals[STATUS_ACTIVE][key]; outstandingData.rawSeries .add(ChartMoneyData(date, totals[STATUS_OUTSTANDING][key])); outstandingData.total += totals[STATUS_OUTSTANDING][key]; } else { activeData.rawSeries.add(ChartMoneyData(date, 0.0)); outstandingData.rawSeries.add(ChartMoneyData(date, 0.0)); } date = date.add(Duration(days: 1)); } activeData.average = round(activeData.total ?? 0 / counts[STATUS_ACTIVE] ?? 0, 2); outstandingData.average = round(outstandingData.total ?? 0 / counts[STATUS_OUTSTANDING] ?? 0, 2); final List data = [ activeData, outstandingData, ]; return data; } var memoizedChartQuotes = memo4((CompanyEntity company, DashboardUIState settings, BuiltMap quoteMap, BuiltMap clientMap) => chartQuotes( company: company, settings: settings, quoteMap: quoteMap, clientMap: clientMap)); List chartQuotes({ CompanyEntity company, DashboardUIState settings, BuiltMap quoteMap, BuiltMap clientMap, }) { const STATUS_ACTIVE = 'active'; const STATUS_APPROVED = 'approved'; const STATUS_UNAPPROVED = 'unapproved'; final Map counts = { STATUS_ACTIVE: 0, STATUS_APPROVED: 0, STATUS_UNAPPROVED: 0, }; final Map> totals = { STATUS_ACTIVE: {}, STATUS_APPROVED: {}, STATUS_UNAPPROVED: {}, }; quoteMap.forEach((int, quote) { final client = clientMap[quote.clientId] ?? ClientEntity(id: quote.clientId); final currencyId = client.currencyId > 0 ? client.currencyId : company.currencyId; if (!quote.isPublic || quote.isDeleted || client.isDeleted || quote.isRecurring) { // skip it } else if (!quote.isBetween( settings.startDate(company), settings.endDate(company))) { // skip it } else if (settings.currencyId > 0 && settings.currencyId != currencyId) { // skip it } else { if (totals[STATUS_ACTIVE][quote.invoiceDate] == null) { totals[STATUS_ACTIVE][quote.invoiceDate] = 0.0; totals[STATUS_APPROVED][quote.invoiceDate] = 0.0; totals[STATUS_UNAPPROVED][quote.invoiceDate] = 0.0; } totals[STATUS_ACTIVE][quote.invoiceDate] += quote.amount; counts[STATUS_ACTIVE]++; if (quote.isApproved) { totals[STATUS_APPROVED][quote.invoiceDate] += quote.amount; counts[STATUS_APPROVED]++; } else { totals[STATUS_UNAPPROVED][quote.invoiceDate] += quote.amount; counts[STATUS_UNAPPROVED]++; } } }); final ChartDataGroup activeData = ChartDataGroup(STATUS_ACTIVE); final ChartDataGroup approvedData = ChartDataGroup(STATUS_APPROVED); final ChartDataGroup unapprovedData = ChartDataGroup(STATUS_UNAPPROVED); 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_ACTIVE].containsKey(key)) { activeData.rawSeries .add(ChartMoneyData(date, totals[STATUS_ACTIVE][key])); activeData.total += totals[STATUS_ACTIVE][key]; approvedData.rawSeries .add(ChartMoneyData(date, totals[STATUS_APPROVED][key])); approvedData.total += totals[STATUS_APPROVED][key]; unapprovedData.rawSeries .add(ChartMoneyData(date, totals[STATUS_UNAPPROVED][key])); unapprovedData.total += totals[STATUS_UNAPPROVED][key]; } else { activeData.rawSeries.add(ChartMoneyData(date, 0.0)); approvedData.rawSeries.add(ChartMoneyData(date, 0.0)); unapprovedData.rawSeries.add(ChartMoneyData(date, 0.0)); } date = date.add(Duration(days: 1)); } activeData.average = round(activeData.total ?? 0 / counts[STATUS_ACTIVE] ?? 0, 2); approvedData.average = round(approvedData.total ?? 0 / counts[STATUS_APPROVED] ?? 0, 2); unapprovedData.average = round(unapprovedData.total ?? 0 / counts[STATUS_UNAPPROVED] ?? 0, 2); final List data = [ activeData, approvedData, unapprovedData, ]; return data; } var memoizedChartPayments = memo5((CompanyEntity company, DashboardUIState settings, BuiltMap invoiceMap, BuiltMap clientMap, BuiltMap paymentMap) => chartPayments(company, settings, invoiceMap, clientMap, paymentMap)); List chartPayments( CompanyEntity company, DashboardUIState settings, BuiltMap invoiceMap, BuiltMap clientMap, BuiltMap paymentMap) { const STATUS_ACTIVE = 'active'; const STATUS_REFUNDED = 'refunded'; final Map counts = { STATUS_ACTIVE: 0, STATUS_REFUNDED: 0, }; final Map> totals = { STATUS_ACTIVE: {}, STATUS_REFUNDED: {}, }; paymentMap.forEach((int, payment) { final invoice = invoiceMap[payment.invoiceId] ?? InvoiceEntity(id: payment.invoiceId); final client = clientMap[invoice.clientId] ?? ClientEntity(id: invoice.clientId); final currencyId = client.currencyId > 0 ? client.currencyId : company.currencyId; if (payment.isDeleted || invoice.isDeleted || client.isDeleted) { // skip it } else if (!payment.isBetween( settings.startDate(company), settings.endDate(company))) { // skip it } else if (settings.currencyId > 0 && settings.currencyId != currencyId) { // skip it } else { if (totals[STATUS_ACTIVE][payment.paymentDate] == null) { totals[STATUS_ACTIVE][payment.paymentDate] = 0.0; totals[STATUS_REFUNDED][payment.paymentDate] = 0.0; } totals[STATUS_ACTIVE][payment.paymentDate] += payment.completedAmount; totals[STATUS_REFUNDED][payment.paymentDate] += payment.refunded; counts[STATUS_ACTIVE]++; if (payment.refunded > 0) { counts[STATUS_REFUNDED]++; } } }); final ChartDataGroup activeData = ChartDataGroup(STATUS_ACTIVE); final ChartDataGroup refundedData = ChartDataGroup(STATUS_REFUNDED); 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_ACTIVE].containsKey(key)) { activeData.rawSeries .add(ChartMoneyData(date, totals[STATUS_ACTIVE][key])); activeData.total += totals[STATUS_ACTIVE][key]; refundedData.rawSeries .add(ChartMoneyData(date, totals[STATUS_REFUNDED][key])); refundedData.total += totals[STATUS_REFUNDED][key]; } else { activeData.rawSeries.add(ChartMoneyData(date, 0.0)); refundedData.rawSeries.add(ChartMoneyData(date, 0.0)); } date = date.add(Duration(days: 1)); } activeData.average = round(activeData.total ?? 0 / counts[STATUS_ACTIVE] ?? 0, 2); refundedData.average = round(refundedData.total ?? 0 / counts[STATUS_REFUNDED] ?? 0, 2); final List data = [ activeData, refundedData, ]; return data; } var memoizedChartTasks = memo5((CompanyEntity company, DashboardUIState settings, BuiltMap taskMap, BuiltMap projectMap, BuiltMap clientMap) => chartTasks(company, settings, taskMap, projectMap, clientMap)); List chartTasks( CompanyEntity company, DashboardUIState settings, BuiltMap taskMap, BuiltMap projectMap, BuiltMap clientMap) { const STATUS_LOGGED = 'logged'; const STATUS_INVOICED = 'invoiced'; const STATUS_PAID = 'paid'; final Map counts = { STATUS_LOGGED: 0, STATUS_INVOICED: 0, STATUS_PAID: 0, }; final Map> totals = { STATUS_LOGGED: {}, STATUS_INVOICED: {}, STATUS_PAID: {}, }; taskMap.forEach((int, task) { final client = clientMap[task.clientId] ?? ClientEntity(id: task.clientId); final project = projectMap[task.projectId] ?? ProjectEntity(id: task.projectId); final currencyId = client.currencyId > 0 ? client.currencyId : company.currencyId; if (task.isDeleted || client.isDeleted || project.isDeleted) { // skip it print('Skip 1'); } else if (!task.isBetween( settings.startDate(company), settings.endDate(company))) { // skip it print('Skip 2'); } else if (settings.currencyId > 0 && settings.currencyId != currencyId) { // skip it print('Skip 3'); } else { task.taskTimes.forEach((taskTime) { taskTime.getParts(0).forEach((date, duration) { if (totals[STATUS_LOGGED][date] == null) { totals[STATUS_LOGGED][date] = 0.0; totals[STATUS_INVOICED][date] = 0.0; totals[STATUS_PAID][date] = 0.0; } print('Task: $task'); print('Task - date: $date'); final taskRate = taskRateSelector( company: company, project: project, client: client); final double amount = taskRate * round(duration.inSeconds / 3600, 3); if (false) { totals[STATUS_PAID][date] += amount; } else if (task.isInvoiced) { totals[STATUS_INVOICED][date] += amount; } else { totals[STATUS_LOGGED][date] += amount; } counts[STATUS_LOGGED]++; if (task.isInvoiced) { counts[STATUS_INVOICED]++; } }); }); } }); final ChartDataGroup loggedData = ChartDataGroup(STATUS_LOGGED); 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]; invoicedData.rawSeries .add(ChartMoneyData(date, totals[STATUS_INVOICED][key])); invoicedData.total += totals[STATUS_INVOICED][key]; } else { loggedData.rawSeries.add(ChartMoneyData(date, 0.0)); invoicedData.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); final List data = [ loggedData, invoicedData, paidData, ]; return data; }