This commit is contained in:
James Cole
2025-08-09 06:59:55 +02:00
parent 4b0597d19a
commit 6aab5fab05
6 changed files with 67 additions and 39 deletions

View File

@@ -57,7 +57,7 @@ class IndexController extends Controller
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
app('view')->share('title', (string) trans('firefly.bills')); app('view')->share('title', (string)trans('firefly.bills'));
app('view')->share('mainTitleIcon', 'fa-calendar-o'); app('view')->share('mainTitleIcon', 'fa-calendar-o');
$this->repository = app(BillRepositoryInterface::class); $this->repository = app(BillRepositoryInterface::class);
@@ -73,27 +73,26 @@ class IndexController extends Controller
{ {
$this->cleanupObjectGroups(); $this->cleanupObjectGroups();
$this->repository->correctOrder(); $this->repository->correctOrder();
$start = session('start'); $start = session('start');
$end = session('end'); $end = session('end');
$collection = $this->repository->getBills(); $collection = $this->repository->getBills();
$total = $collection->count(); $total = $collection->count();
$parameters = new ParameterBag();
$parameters = new ParameterBag();
// sub one day from temp start so the last paid date is one day before it should be. // sub one day from temp start so the last paid date is one day before it should be.
$tempStart = clone $start; $tempStart = clone $start;
// 2023-06-23 do not sub one day from temp start, fix is in BillTransformer::payDates instead // 2023-06-23 do not sub one day from temp start, fix is in BillTransformer::payDates instead
// $tempStart->subDay(); // $tempStart->subDay();
// enrich // enrich
/** @var User $admin */ /** @var User $admin */
$admin = auth()->user(); $admin = auth()->user();
$enrichment = new SubscriptionEnrichment(); $enrichment = new SubscriptionEnrichment();
$enrichment->setUser($admin); $enrichment->setUser($admin);
$enrichment->setStart($tempStart); $enrichment->setStart($tempStart);
$enrichment->setEnd($end); $enrichment->setEnd($end);
$collection = $enrichment->enrich($collection); $collection = $enrichment->enrich($collection);
$parameters->set('start', $tempStart); $parameters->set('start', $tempStart);
@@ -106,21 +105,21 @@ class IndexController extends Controller
$transformer->setParameters($parameters); $transformer->setParameters($parameters);
// loop all bills, convert to array and add rules and stuff. // loop all bills, convert to array and add rules and stuff.
$rules = $this->repository->getRulesForBills($collection); $rules = $this->repository->getRulesForBills($collection);
// make bill groups: // make bill groups:
$bills = [ $bills = [
0 => [ // the index is the order, not the ID. 0 => [ // the index is the order, not the ID.
'object_group_id' => 0, 'object_group_id' => 0,
'object_group_title' => (string) trans('firefly.default_group_title_name'), 'object_group_title' => (string)trans('firefly.default_group_title_name'),
'bills' => [], 'bills' => [],
], ],
]; ];
/** @var Bill $bill */ /** @var Bill $bill */
foreach ($collection as $bill) { foreach ($collection as $bill) {
$array = $transformer->transform($bill); $array = $transformer->transform($bill);
$groupOrder = (int) $array['object_group_order']; $groupOrder = (int)$array['object_group_order'];
// make group array if necessary: // make group array if necessary:
$bills[$groupOrder] ??= [ $bills[$groupOrder] ??= [
'object_group_id' => $array['object_group_id'], 'object_group_id' => $array['object_group_id'],
@@ -142,9 +141,9 @@ class IndexController extends Controller
ksort($bills); ksort($bills);
// summarise per currency / per group. // summarise per currency / per group.
$sums = $this->getSums($bills); $sums = $this->getSums($bills);
$totals = $this->getTotals($sums); $totals = $this->getTotals($sums);
$today = now()->startOfDay(); $today = now()->startOfDay();
return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today')); return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today'));
} }
@@ -165,7 +164,7 @@ class IndexController extends Controller
continue; continue;
} }
$currencyId = $bill['currency_id']; $currencyId = $bill['currency_id'];
$sums[$groupOrder][$currencyId] ??= [ $sums[$groupOrder][$currencyId] ??= [
'currency_id' => $currencyId, 'currency_id' => $currencyId,
'currency_code' => $bill['currency_code'], 'currency_code' => $bill['currency_code'],
@@ -173,16 +172,28 @@ class IndexController extends Controller
'currency_symbol' => $bill['currency_symbol'], 'currency_symbol' => $bill['currency_symbol'],
'currency_decimal_places' => $bill['currency_decimal_places'], 'currency_decimal_places' => $bill['currency_decimal_places'],
'avg' => '0', 'avg' => '0',
'total_left_to_pay' => '0',
'period' => $range, 'period' => $range,
'per_period' => '0', 'per_period' => '0',
]; ];
// only fill in avg when bill is active. // only fill in avg when bill is active.
if (null !== $bill['next_expected_match']) { if (null !== $bill['next_expected_match']) {
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2'); $avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
$avg = bcmul($avg, (string) count($bill['pay_dates'])); $avg = bcmul($avg, (string)count($bill['pay_dates']));
$sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg); $sums[$groupOrder][$currencyId]['avg'] = bcadd($sums[$groupOrder][$currencyId]['avg'], $avg);
} }
// only fill in total_left_to_pay when bill is not yet paid.
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
$avg = bcmul($avg, (string)$count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
}
}
// fill in per period regardless: // fill in per period regardless:
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range)); $sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range));
} }
@@ -193,7 +204,7 @@ class IndexController extends Controller
private function amountPerPeriod(array $bill, string $range): string private function amountPerPeriod(array $bill, string $range): string
{ {
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2'); $avg = bcdiv(bcadd((string)$bill['amount_min'], (string)$bill['amount_max']), '2');
app('log')->debug(sprintf('Amount per period for bill #%d "%s"', $bill['id'], $bill['name'])); app('log')->debug(sprintf('Amount per period for bill #%d "%s"', $bill['id'], $bill['name']));
app('log')->debug(sprintf('Average is %s', $avg)); app('log')->debug(sprintf('Average is %s', $avg));
@@ -206,11 +217,11 @@ class IndexController extends Controller
'weekly' => '52.17', 'weekly' => '52.17',
'daily' => '365.24', 'daily' => '365.24',
]; ];
$yearAmount = bcmul($avg, bcdiv($multiplies[$bill['repeat_freq']], (string) ($bill['skip'] + 1))); $yearAmount = bcmul($avg, bcdiv($multiplies[$bill['repeat_freq']], (string)($bill['skip'] + 1)));
app('log')->debug(sprintf('Amount per year is %s (%s * %s / %s)', $yearAmount, $avg, $multiplies[$bill['repeat_freq']], (string) ($bill['skip'] + 1))); app('log')->debug(sprintf('Amount per year is %s (%s * %s / %s)', $yearAmount, $avg, $multiplies[$bill['repeat_freq']], (string)($bill['skip'] + 1)));
// per period: // per period:
$division = [ $division = [
'1Y' => '1', '1Y' => '1',
'6M' => '2', '6M' => '2',
'3M' => '4', '3M' => '4',
@@ -225,7 +236,7 @@ class IndexController extends Controller
'last90' => '4', 'last90' => '4',
'last365' => '1', 'last365' => '1',
]; ];
$perPeriod = bcdiv($yearAmount, $division[$range]); $perPeriod = bcdiv($yearAmount, $division[$range]);
app('log')->debug(sprintf('Amount per %s is %s (%s / %s)', $range, $perPeriod, $yearAmount, $division[$range])); app('log')->debug(sprintf('Amount per %s is %s (%s / %s)', $range, $perPeriod, $yearAmount, $division[$range]));
@@ -244,11 +255,11 @@ class IndexController extends Controller
*/ */
foreach ($sums as $array) { foreach ($sums as $array) {
/** /**
* @var int $currencyId * @var int $currencyId
* @var array $entry * @var array $entry
*/ */
foreach ($array as $currencyId => $entry) { foreach ($array as $currencyId => $entry) {
$totals[$currencyId] ??= [ $totals[$currencyId] ??= [
'currency_id' => $currencyId, 'currency_id' => $currencyId,
'currency_code' => $entry['currency_code'], 'currency_code' => $entry['currency_code'],
'currency_name' => $entry['currency_name'], 'currency_name' => $entry['currency_name'],
@@ -258,8 +269,8 @@ class IndexController extends Controller
'period' => $entry['period'], 'period' => $entry['period'],
'per_period' => '0', 'per_period' => '0',
]; ];
$totals[$currencyId]['avg'] = bcadd($totals[$currencyId]['avg'], (string) $entry['avg']); $totals[$currencyId]['avg'] = bcadd($totals[$currencyId]['avg'], (string)$entry['avg']);
$totals[$currencyId]['per_period'] = bcadd($totals[$currencyId]['per_period'], (string) $entry['per_period']); $totals[$currencyId]['per_period'] = bcadd($totals[$currencyId]['per_period'], (string)$entry['per_period']);
} }
} }
@@ -271,8 +282,8 @@ class IndexController extends Controller
*/ */
public function setOrder(Request $request, Bill $bill): JsonResponse public function setOrder(Request $request, Bill $bill): JsonResponse
{ {
$objectGroupTitle = (string) $request->get('objectGroupTitle'); $objectGroupTitle = (string)$request->get('objectGroupTitle');
$newOrder = (int) $request->get('order'); $newOrder = (int)$request->get('order');
$this->repository->setOrder($bill, $newOrder); $this->repository->setOrder($bill, $newOrder);
if ('' !== $objectGroupTitle) { if ('' !== $objectGroupTitle) {
$this->repository->setObjectGroup($bill, $objectGroupTitle); $this->repository->setObjectGroup($bill, $objectGroupTitle);

View File

@@ -56,6 +56,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
$this->collectPaidDates(); $this->collectPaidDates();
$this->collectPayDates(); $this->collectPayDates();
// TODO clean me up. // TODO clean me up.
$notes = $this->notes; $notes = $this->notes;

View File

@@ -110,8 +110,8 @@ class BillDateCalculator
$currentStart = clone $nextExpectedMatch; $currentStart = clone $nextExpectedMatch;
++$loop; ++$loop;
if ($loop > 12) { if ($loop > 31) {
Log::debug('Loop is more than 12, so we break.'); Log::debug('Loop is more than 31, so we break.');
break; break;
} }

View File

@@ -1864,6 +1864,7 @@ return [
'remove_budgeted_amount' => 'Remove budgeted amount in :currency', 'remove_budgeted_amount' => 'Remove budgeted amount in :currency',
// bills: // bills:
'left_to_pay_active_bills' => 'active, expected and not yet paid subscriptions',
'left_to_pay_lc' => 'left to pay', 'left_to_pay_lc' => 'left to pay',
'less_than_expected' => 'less than expected', 'less_than_expected' => 'less than expected',
'more_than_expected' => 'more than expected', 'more_than_expected' => 'more than expected',

View File

@@ -158,8 +158,8 @@
<td style="width:33%;">{{ 'amount'|_ }}</td> <td style="width:33%;">{{ 'amount'|_ }}</td>
<td> <td>
{{ formatAmountBySymbol(limit.amount, limit.transactionCurrency.symbol, limit.transactionCurrency.decimal_places) }} {{ formatAmountBySymbol(limit.amount, limit.transactionCurrency.symbol, limit.transactionCurrency.decimal_places) }}
{% if convertToPrimary and 0 != limit.pc_amount %} {% if convertToPrimary and null != limit.native_amount %}
({{ formatAmountBySymbol(limit.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }}) ({{ formatAmountBySymbol(limit.native_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@@ -189,6 +189,21 @@
<td class="hidden-sm hidden-xs">&nbsp;</td><!-- repeats --> <td class="hidden-sm hidden-xs">&nbsp;</td><!-- repeats -->
</tr> </tr>
{% endif %} {% endif %}
{% if '0' != sum.total_left_to_pay %}
<tr>
<td class="hidden-sm hidden-xs">&nbsp;</td> <!-- handle -->
<td class="hidden-sm hidden-xs">&nbsp;</td> <!-- buttons -->
<td colspan="2" style="text-align: right;"> <!-- title -->
<small>{{ 'sum'|_ }} ({{ sum.currency_name }}) ({{ 'left_to_pay_active_bills'|_ }})</small>
</td>
<td style="text-align: right;"> <!-- amount -->
{{ formatAmountBySymbol(sum.total_left_to_pay, sum.currency_symbol, sum.currency_decimal_places) }}
</td>
<td>&nbsp;</td> <!-- paid in period -->
<td class="hidden-sm hidden-xs">&nbsp;</td> <!-- next expected match -->
<td class="hidden-sm hidden-xs">&nbsp;</td><!-- repeats -->
</tr>
{% endif %}
{% if '0' != sum.per_period %} {% if '0' != sum.per_period %}
<tr> <tr>
<td class="hidden-sm hidden-xs">&nbsp;</td> <!-- handle --> <td class="hidden-sm hidden-xs">&nbsp;</td> <!-- handle -->