$monthStart->month; foreach ([$windowStart, $monthStart, $monthEnd] as $cursor) { if ($openedLastYear) { $cursor->subYear(); } $cursor->month($openingMonth); } $windowStart->startOfMonth(); $monthStart->startOfMonth(); $monthEnd->endOfMonth(); if ($previousYear) { $windowStart->subYear()->startOfMonth(); $monthStart->subYear()->startOfMonth(); $monthEnd->subYear()->endOfMonth(); } $months = []; $invoiceTotals = []; $expenseTotals = []; $receiptTotals = []; $netProfits = []; for ($bucket = 0; $bucket < 12; $bucket++) { $bucketSpan = [$monthStart->format('Y-m-d'), $monthEnd->format('Y-m-d')]; $invoiceTotals[] = Invoice::query() ->whereBetween('invoice_date', $bucketSpan) ->whereCompany() ->whereCustomer($customer->id) ->sum('base_total'); $expenseTotals[] = Expense::query() ->whereBetween('expense_date', $bucketSpan) ->whereCompany() ->whereUser($customer->id) ->sum('base_amount'); $receiptTotals[] = Payment::query() ->whereBetween('payment_date', $bucketSpan) ->whereCompany() ->whereCustomer($customer->id) ->sum('base_amount'); // What was received less what was spent. Invoiced money is not in // it: a bill that has not been paid is not profit. $netProfits[] = $receiptTotals[$bucket] - $expenseTotals[$bucket]; $months[] = $monthStart->translatedFormat('M'); // Both cursors step off the first of their month, so a short month // can never drag the walk backwards. $monthEnd->startOfMonth()->addMonth()->endOfMonth(); $monthStart->addMonth()->startOfMonth(); } // Twelve steps left the walking cursor on the month after the window. // Back it on to the last month of the window and take that month's // final day as the right edge of the whole-window figures. $monthStart->subMonth()->endOfMonth(); $windowSpan = [$windowStart->format('Y-m-d'), $monthStart->format('Y-m-d')]; $salesTotal = Invoice::query() ->whereBetween('invoice_date', $windowSpan) ->whereCompany() ->whereCustomer($customer->id) ->sum('base_total'); $totalReceipts = Payment::query() ->whereBetween('payment_date', $windowSpan) ->whereCompany() ->whereCustomer($customer->id) ->sum('base_amount'); $totalExpenses = Expense::query() ->whereBetween('expense_date', $windowSpan) ->whereCompany() ->whereUser($customer->id) ->sum('base_amount'); return [ 'months' => $months, 'invoiceTotals' => $invoiceTotals, 'expenseTotals' => $expenseTotals, 'receiptTotals' => $receiptTotals, // KNOWN QUIRK: both sides are cut to whole units before the // subtraction, so the headline figure loses the cents that the // three totals beside it keep. 'netProfit' => (int) $totalReceipts - (int) $totalExpenses, 'netProfits' => $netProfits, 'salesTotal' => $salesTotal, 'totalReceipts' => $totalReceipts, 'totalExpenses' => $totalExpenses, ]; } }