diff --git a/app/SubBooking.php b/app/SubBooking.php index 693cb76..b8b4b6d 100644 --- a/app/SubBooking.php +++ b/app/SubBooking.php @@ -3,6 +3,8 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; +use Carbon\Carbon; class SubBooking extends Model { @@ -11,6 +13,189 @@ class SubBooking extends Model return $this->ioType->factor * $this->amount; } + /************************************************************************** + * SUMS OVER INCOMES AND EXPEDITURES, ACCOUNT BALANCES * + **************************************************************************/ + + /** + * Return the balance at a given date. + * + * @param string $date + * @return float + */ + public static function balanceAt($date) + { + $date = Carbon::parse($date)->format('Y-m-d'); + + $result = SubBooking::joinAccountBranch() + ->joinBookings() + ->where('bookings.date', '<=', $date) + ->selectRaw('sum(i_o_types.factor * sub_bookings.amount) as balance') + ->first()->balance; + + return is_null($result) ? 0.00 : $result; + } + + /** + * Return the sum of all expeditures in the given interval. + * + * Return the sum of all expeditures in the interval from $star to $end. + * If $group is true, the result is grouped by year and month, such that the + * collection contains for every month in the interval an model with the + * properties 'year', 'month' and 'expeditures'. + * Otherwise the result is a collection with a single model, containing the + * property 'expeditures'. + * + * @param string $start + * @param string $end + * @param string $group Toggle, if the result is grouped by year and month. + * @return Illuminate\Database\Eloquent\Collection + */ + public static function expedituresInInterval($start, $end, $group = false) + { + return SubBooking::cashFlowInInterval($start, $end, 'expeditures', $group); + } + + /** + * Return the sum of all expeditures in the given interval. + * + * Return the sum of all expeditures in the interval from $star to $end. + * If $group is true, the result is grouped by year and month, such that the + * collection contains for every month in the interval an model with the + * properties 'year', 'month' and 'incomes'. + * Otherwise the result is a collection with a single model, containing the + * property 'incomes'. + * + * @param string $start + * @param string $end + * @param string $group Toggle, if the result is grouped by year and month. + * @return Illuminate\Database\Eloquent\Collection + */ + public static function incomesInInterval($start, $end, $group = false) + { + return SubBooking::cashFlowInInterval($start, $end, 'incomes', $group); + } + + /** + * Since the incomesInInterval and expedituresInInterval methods are almost + * the same, this is the collectively used code. + * + * @param string $start + * @param string $end + * @param string $type Can be 'incomes' or 'expeditures' + * @param string $group Toggle, if the result is grouped by year and month. + * @return Illuminate\Database\Eloquent\collection + */ + private static function cashFlowInInterval($start, $end, $type, $group = false) + { + $parsedStart = Carbon::parse($start); + $parsedEnd = Carbon::parse($end); + + $typeName = ($type === 'incomes') ? 'Einnahme' : 'Ausgabe'; + + $start = $parsedStart->format('Y-m-d'); + $end = $parsedEnd->format('Y-m-d'); + + $result = SubBooking::joinAccountBranch() + ->interval($start, $end) + ->where('i_o_types.name', '=', $typeName) + ->selectRaw('sum(i_o_types.factor * sub_bookings.amount) as ' . $type); + + if ($group) { + // Group the result, first by month, then by year + $result = $result->groupBy('year', 'month') + ->selectRaw('sum(i_o_types.factor * sub_bookings.amount) as ' . $type . ', + YEAR(bookings.date) as year, + MONTH(bookings.date) as month') + ->get(); + + // Add a model with values = 0 for those months without any + // sub-booking. If we would not explicitly define a model for these + // cases, we would try toget a property from a model that does not + // exist. + $yearStart = $parsedStart->format('Y'); + $yearEnd = $parsedEnd->format('Y'); + + for ($year = $yearStart; $year <= $yearEnd; $year++) { + // Set the start and end month for a year, in case the given + // interval starts or ends in the current year. + $monthStart = ($year == $parsedStart->format('Y')) ? + $parsedStart->format('n') : + 1; + $monthEnd = ($year == $parsedEnd->format('Y')) ? + $parsedEnd->format('n') : + 12; + + for ($month = $monthStart; $month <= $monthEnd; $month++) { + if ($result->where('year', '=', $year) + ->where('month', '=', $month)->isEmpty()) { + $subBooking = new SubBooking(); + + $subBooking->$type = 0; + $subBooking->year = (int) $year; + $subBooking->month = (int) $month; + + $result->push($subBooking); + } + } + } + + return $result; + } + + return $result->get(); + } + + /************************************************************************** + * QUERY SCOPES * + **************************************************************************/ + + /** + * Consider only sub-bookings in a given interval. + * + * @return Illuminate\Database\Eloquent\Builder + */ + public function scopeInterval($query, $start, $end) + { + return $query->joinBookings() + ->where('bookings.date', '>=', Carbon::parse($start)->format('Y-m-d')) + ->where('bookings.date', '<=', Carbon::parse($end)->format('Y-m-d')); + } + + /** + * Join the 'bookings' table into the query. + * + * @return Illuminate\Database\Eloquent\Builder + */ + public function scopeJoinBookings($query) + { + return $query->join('bookings', 'bookings.id', '=', 'sub_bookings.booking_id'); + } + + /** + * Add a signed amount property to the table. + * + * @return Illuminate\Database\Eloquent\Builder + */ + public function scopeWithSignedAmount($query) + { + return $query->joinAccountBranch() + ->addSelect(DB::raw('sub_bookings.*, (i_o_types.factor * sub_bookings.amount) as signedAmount')); + } + + /** + * Join the tables 'i_o_types', 'account_classes' and 'account_types' into + * the query. + * + * @return Illuminate\Database\Eloquent\Builder + */ + public function scopeJoinAccountBranch($query) + { + return $query->join('account_types', 'account_types.id', '=', 'sub_bookings.account_type_id') + ->join('account_classes', 'account_classes.id', '=', 'account_types.account_class_id') + ->join('i_o_types', 'i_o_types.id', '=', 'account_classes.io_type_id'); + } + /************************************************************************** * BOOKINGS * **************************************************************************/