mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-12 15:35:15 +00:00
Improve bill overview.
This commit is contained in:
@@ -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',
|
||||
]
|
||||
)
|
||||
;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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.',
|
||||
|
Reference in New Issue
Block a user