diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index ffa70a0274..48bd57711b 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -312,11 +312,23 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString())); return $bill->transactionJournals() + ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id') + ->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id') + ->where('transactions.amount', '>', 0) ->before($end)->after($start)->get( [ 'transaction_journals.id', 'transaction_journals.date', 'transaction_journals.transaction_group_id', + 'transactions.transaction_currency_id', + 'currency.code AS transaction_currency_code', + 'currency.decimal_places AS transaction_currency_decimal_places', + 'transactions.foreign_currency_id', + 'foreign_currency.code AS foreign_currency_code', + 'foreign_currency.decimal_places AS foreign_currency_decimal_places', + 'transactions.amount', + 'transactions.foreign_amount', ] ) ; diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index fa1fcc8923..78cf2a99cd 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -32,8 +32,10 @@ use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Support\Facades\Amount; +use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Models\BillDateCalculator; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; /** * Class BillTransformer @@ -64,23 +66,23 @@ class BillTransformer extends AbstractTransformer */ public function transform(Bill $bill): array { - $default = $this->parameters->get('defaultCurrency') ?? $this->default; + $default = $this->parameters->get('defaultCurrency') ?? $this->default; - $paidData = $this->paidData($bill); - $lastPaidDate = $this->getLastPaidDate($paidData); - $start = $this->parameters->get('start') ?? today()->subYears(10); - $end = $this->parameters->get('end') ?? today()->addYears(10); - $payDates = $this->calculator->getPayDates($start, $end, $bill->date, $bill->repeat_freq, $bill->skip, $lastPaidDate); - $currency = $bill->transactionCurrency; - $notes = $this->repository->getNoteText($bill); - $notes = '' === $notes ? null : $notes; - $objectGroupId = null; - $objectGroupOrder = null; - $objectGroupTitle = null; + $paidData = $this->paidData($bill); + $lastPaidDate = $this->getLastPaidDate($paidData); + $start = $this->parameters->get('start') ?? today()->subYears(10); + $end = $this->parameters->get('end') ?? today()->addYears(10); + $payDates = $this->calculator->getPayDates($start, $end, $bill->date, $bill->repeat_freq, $bill->skip, $lastPaidDate); + $currency = $bill->transactionCurrency; + $notes = $this->repository->getNoteText($bill); + $notes = '' === $notes ? null : $notes; + $objectGroupId = null; + $objectGroupOrder = null; + $objectGroupTitle = null; $this->repository->setUser($bill->user); /** @var null|ObjectGroup $objectGroup */ - $objectGroup = $bill->objectGroups->first(); + $objectGroup = $bill->objectGroups->first(); if (null !== $objectGroup) { $objectGroupId = $objectGroup->id; $objectGroupOrder = $objectGroup->order; @@ -90,7 +92,7 @@ class BillTransformer extends AbstractTransformer $paidDataFormatted = []; $payDatesFormatted = []; foreach ($paidData as $object) { - $date = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone')); + $date = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone')); if (!$date instanceof Carbon) { $date = today(config('app.timezone')); } @@ -99,24 +101,24 @@ class BillTransformer extends AbstractTransformer } foreach ($payDates as $string) { - $date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone')); + $date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone')); if (!$date instanceof Carbon) { $date = today(config('app.timezone')); } $payDatesFormatted[] = $date->toAtomString(); } // next expected match - $nem = null; - $nemDate = null; - $nemDiff = trans('firefly.not_expected_period'); - $firstPayDate = $payDates[0] ?? null; + $nem = null; + $nemDate = null; + $nemDiff = trans('firefly.not_expected_period'); + $firstPayDate = $payDates[0] ?? null; if (null !== $firstPayDate) { $nemDate = Carbon::createFromFormat('!Y-m-d', $firstPayDate, config('app.timezone')); if (!$nemDate instanceof Carbon) { $nemDate = today(config('app.timezone')); } - $nem = $nemDate->toAtomString(); + $nem = $nemDate->toAtomString(); // nullify again when it's outside the current view range. if ( @@ -137,7 +139,7 @@ class BillTransformer extends AbstractTransformer $current = $payDatesFormatted[0] ?? null; if (null !== $current && !$nemDate->isToday()) { - $temp2 = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current); + $temp2 = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current); if (!$temp2 instanceof Carbon) { $temp2 = today(config('app.timezone')); } @@ -150,11 +152,11 @@ class BillTransformer extends AbstractTransformer 'id' => $bill->id, 'created_at' => $bill->created_at->toAtomString(), 'updated_at' => $bill->updated_at->toAtomString(), - 'currency_id' => (string) $bill->transaction_currency_id, + 'currency_id' => (string)$bill->transaction_currency_id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, - 'native_currency_id' => null === $default ? null : (string) $default->id, + 'native_currency_id' => null === $default ? null : (string)$default->id, 'native_currency_code' => $default?->code, 'native_currency_symbol' => $default?->symbol, 'native_currency_decimal_places' => $default?->decimal_places, @@ -171,7 +173,7 @@ class BillTransformer extends AbstractTransformer 'active' => $bill->active, 'order' => $bill->order, 'notes' => $notes, - 'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null, + 'object_group_id' => null !== $objectGroupId ? (string)$objectGroupId : null, 'object_group_order' => $objectGroupOrder, 'object_group_title' => $objectGroupTitle, @@ -183,7 +185,7 @@ class BillTransformer extends AbstractTransformer 'links' => [ [ 'rel' => 'self', - 'uri' => '/bills/'.$bill->id, + 'uri' => '/bills/' . $bill->id, ], ], ]; @@ -194,9 +196,9 @@ class BillTransformer extends AbstractTransformer */ protected function paidData(Bill $bill): array { - app('log')->debug(sprintf('Now in paidData for bill #%d', $bill->id)); + Log::debug(sprintf('Now in paidData for bill #%d', $bill->id)); if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) { - app('log')->debug('parameters are NULL, return empty array'); + Log::debug('parameters are NULL, return empty array'); return []; } @@ -205,39 +207,52 @@ class BillTransformer extends AbstractTransformer // 2023-07-18 this particular date is used to search for the last paid date. // 2023-07-18 the cloned $searchDate is used to grab the correct transactions. /** @var Carbon $start */ - $start = clone $this->parameters->get('start'); - $searchStart = clone $start; + $start = clone $this->parameters->get('start'); + $searchStart = clone $start; $start->subDay(); /** @var Carbon $end */ - $end = clone $this->parameters->get('end'); - $searchEnd = clone $end; + $end = clone $this->parameters->get('end'); + $searchEnd = clone $end; // move the search dates to the start of the day. $searchStart->startOfDay(); $searchEnd->endOfDay(); - app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); - app('log')->debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d'))); + Log::debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); + Log::debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d'))); // Get from database when bill was paid. - $set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd); - app('log')->debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count())); + $set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd); + Log::debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count())); // Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date. - app('log')->debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d'))); + Log::debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d'))); $lastPaidDate = $this->lastPaidDate($set, $start); - app('log')->debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d'))); + Log::debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d'))); // At this point the "next match" is exactly after the last time the bill was paid. - $result = []; + $result = []; foreach ($set as $entry) { - $result[] = [ - 'transaction_group_id' => (string) $entry->transaction_group_id, - 'transaction_journal_id' => (string) $entry->id, + $array = [ + 'transaction_group_id' => (string)$entry->transaction_group_id, + 'transaction_journal_id' => (string)$entry->id, 'date' => $entry->date->format('Y-m-d'), 'date_object' => $entry->date, + 'currency_id' => $entry->transaction_currency_id, + 'currency_code' => $entry->transaction_currency_code, + 'currency_decimal_places' => $entry->transaction_currency_decimal_places, + 'amount' => Steam::bcround($entry->amount,$entry->transaction_currency_decimal_places), ]; + if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) { + $array['foreign_currency_id'] = $entry->foreign_currency_id; + $array['foreign_currency_code'] = $entry->foreign_currency_code; + $array['foreign_currency_decimal_places'] = $entry->foreign_currency_decimal_places; + $array['foreign_amount'] = Steam::bcround($entry->foreign_amount,$entry->foreign_currency_decimal_places); + } + + $result[] = $array; + } return $result; @@ -265,7 +280,7 @@ class BillTransformer extends AbstractTransformer private function getLastPaidDate(array $paidData): ?Carbon { - app('log')->debug('getLastPaidDate()'); + Log::debug('getLastPaidDate()'); $return = null; foreach ($paidData as $entry) { if (null !== $return) { @@ -274,15 +289,15 @@ class BillTransformer extends AbstractTransformer if ($current->gt($return)) { $return = clone $current; } - app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); + Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); } if (null === $return) { /** @var Carbon $return */ $return = $entry['date_object']; - app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); + Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); } } - app('log')->debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d'))); + Log::debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d'))); return $return; } diff --git a/resources/assets/v2/src/pages/dashboard/subscriptions.js b/resources/assets/v2/src/pages/dashboard/subscriptions.js index 6b1f35da7a..131896f32d 100644 --- a/resources/assets/v2/src/pages/dashboard/subscriptions.js +++ b/resources/assets/v2/src/pages/dashboard/subscriptions.js @@ -31,6 +31,71 @@ let afterPromises = false; let apiData = []; let subscriptionData = {}; +function addObjectGroupInfo(data) { + let objectGroupId = parseInt(data.object_group_id); + if (!subscriptionData.hasOwnProperty(objectGroupId)) { + subscriptionData[objectGroupId] = { + id: objectGroupId, + title: null === data.object_group_title ? i18next.t('firefly.default_group_title_name_plain') : data.object_group_title, + order: parseInt(data.object_group_order), + payment_info: {}, + bills: [], + }; + } +} + +function parseBillInfo(data) { + let result = { + id: data.id, + name: data.attributes.name, + amount_min: data.attributes.amount_min, + amount_max: data.attributes.amount_max, + amount: (parseFloat(data.attributes.amount_max) + parseFloat(data.attributes.amount_min)) / 2, + currency_code: data.attributes.currency_code, + // paid transactions: + transactions: [], + // unpaid moments + pay_dates: data.attributes.pay_dates, + paid: data.attributes.paid_dates.length > 0, + }; + // set variables + result.expected_amount = formatMoney(result.amount, result.currency_code); + result.expected_times = i18next.t('firefly.subscr_expected_x_times', { + times: data.attributes.pay_dates.length, + amount: result.expected_amount + }); + return result; +} + +function parsePaidTransactions(paid_dates, bill) { + if( !paid_dates || paid_dates.length < 1) { + return []; + } + let result = []; + // add transactions (simpler version) + for (let i in paid_dates) { + if (paid_dates.hasOwnProperty(i)) { + const currentPayment = paid_dates[i]; + console.log(currentPayment); + // math: -100+(paid/expected)*100 + let percentage = Math.round(-100 + ((parseFloat(currentPayment.amount) ) / parseFloat(bill.amount)) * 100); + let currentTransaction = { + amount: formatMoney(currentPayment.amount, currentPayment.currency_code), + percentage: percentage, + date: format(new Date(currentPayment.date), 'PP'), + foreign_amount: null, + }; + if (null !== currentPayment.foreign_currency_code) { + currentTransaction.foreign_amount = currentPayment.foreign_amount; + currentTransaction.foreign_currency_code = currentPayment.foreign_currency_code; + } + + result.push(currentTransaction); + } + } + return result; +} + function downloadSubscriptions(params) { const getter = new Get(); return getter.list(params) @@ -41,80 +106,14 @@ function downloadSubscriptions(params) { for (let i in data) { if (data.hasOwnProperty(i)) { let current = data[i]; - //console.log(current); if (current.attributes.active && current.attributes.pay_dates.length > 0) { - let objectGroupId = null === current.attributes.object_group_id ? 0 : current.attributes.object_group_id; - let objectGroupTitle = null === current.attributes.object_group_title ? i18next.t('firefly.default_group_title_name_plain') : current.attributes.object_group_title; - let objectGroupOrder = null === current.attributes.object_group_order ? 0 : current.attributes.object_group_order; - if (!subscriptionData.hasOwnProperty(objectGroupId)) { - subscriptionData[objectGroupId] = { - id: objectGroupId, - title: objectGroupTitle, - order: objectGroupOrder, - payment_info: {}, - bills: [], - }; - } - // TODO this conversion needs to be inside some kind of a parsing class. - let bill = { - id: current.id, - name: current.attributes.name, - // amount - amount_min: current.attributes.amount_min, - amount_max: current.attributes.amount_max, - amount: (parseFloat(current.attributes.amount_max) + parseFloat(current.attributes.amount_min)) / 2, - currency_code: current.attributes.currency_code, + // create or update object group + let objectGroupId = parseInt(current.attributes.object_group_id); + addObjectGroupInfo(current.attributes); - // native amount - // native_amount_min: current.attributes.native_amount_min, - // native_amount_max: current.attributes.native_amount_max, - // native_amount: (parseFloat(current.attributes.native_amount_max) + parseFloat(current.attributes.native_amount_min)) / 2, - // native_currency_code: current.attributes.native_currency_code, - - // paid transactions: - transactions: [], - - // unpaid moments - pay_dates: current.attributes.pay_dates, - paid: current.attributes.paid_dates.length > 0, - }; - // set variables - bill.expected_amount = formatMoney(bill.amount, bill.currency_code); - bill.expected_times = i18next.t('firefly.subscr_expected_x_times', { - times: current.attributes.pay_dates.length, - amount: bill.expected_amount - }); - - // add transactions (simpler version) - for (let iii in current.attributes.paid_dates) { - if (current.attributes.paid_dates.hasOwnProperty(iii)) { - const currentPayment = current.attributes.paid_dates[iii]; - let percentage = 100; - // math: -100+(paid/expected)*100 - if (params.convertToNative) { - percentage = Math.round(-100 + ((parseFloat(currentPayment.native_amount) * -1) / parseFloat(bill.native_amount)) * 100); - } - if (!params.convertToNative) { - percentage = Math.round(-100 + ((parseFloat(currentPayment.amount) * -1) / parseFloat(bill.amount)) * 100); - } - // TODO fix me - currentPayment.currency_code = 'EUR'; - console.log('Currency code: "'+currentPayment+'"'); - console.log(currentPayment); - let currentTransaction = { - amount: formatMoney(currentPayment.amount, currentPayment.currency_code), - percentage: percentage, - date: format(new Date(currentPayment.date), 'PP'), - foreign_amount: null, - }; - if (null !== currentPayment.foreign_currency_code) { - currentTransaction.foreign_amount = currentPayment.foreign_amount; - currentTransaction.foreign_currency_code = currentPayment.foreign_currency_code; - } - - bill.transactions.push(currentTransaction); - } - } + // create and update the bill. + let bill = parseBillInfo(current); + bill.transactions = parsePaidTransactions(current.attributes.paid_dates, bill); subscriptionData[objectGroupId].bills.push(bill); if (0 === current.attributes.paid_dates.length) { diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 5c75d64b44..e7ccd93345 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1866,8 +1866,8 @@ return [ 'skip_help_text' => 'Use the skip field to create bi-monthly (skip = 1) or other custom intervals.', 'subscription' => 'Subscription', 'not_expected_period' => 'Not expected this period', - 'subscriptions_in_group' => 'Subscriptions in group "%{title}"', - 'subscr_expected_x_times' => 'Expect to pay %{amount} %{times} times this period', + 'subscriptions_in_group' => 'Subscriptions in group "{{title}}"', + 'subscr_expected_x_times' => 'Expect to pay {{amount}} {{times}} times this period', 'not_or_not_yet' => 'Not (yet)', 'visit_bill' => 'Visit subscription ":name" at Firefly III', 'match_between_amounts' => 'Subscription matches transactions between :low and :high.',