API allows update/set of budget limit notes. https://github.com/firefly-iii/firefly-iii/issues/5523

This commit is contained in:
James Cole 2024-11-30 05:42:59 +01:00
parent c25c0d37c5
commit f5c56e02da
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
8 changed files with 187 additions and 144 deletions

View File

@ -69,6 +69,7 @@ class StoreController extends Controller
$data = $request->getAll(); $data = $request->getAll();
$data['start_date'] = $data['start']; $data['start_date'] = $data['start'];
$data['end_date'] = $data['end']; $data['end_date'] = $data['end'];
$data['notes'] = $data['notes'];
$data['budget_id'] = $budget->id; $data['budget_id'] = $budget->id;
$budgetLimit = $this->blRepository->store($data); $budgetLimit = $this->blRepository->store($data);

View File

@ -48,6 +48,7 @@ class StoreRequest extends FormRequest
'amount' => $this->convertString('amount'), 'amount' => $this->convertString('amount'),
'currency_id' => $this->convertInteger('currency_id'), 'currency_id' => $this->convertInteger('currency_id'),
'currency_code' => $this->convertString('currency_code'), 'currency_code' => $this->convertString('currency_code'),
'notes' => $this->stringWithNewlines('notes'),
]; ];
} }
@ -62,6 +63,7 @@ class StoreRequest extends FormRequest
'amount' => ['required', new IsValidPositiveAmount()], 'amount' => ['required', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id', 'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'notes' => 'nullable|min:0|max:32768',
]; ];
} }
} }

View File

@ -51,8 +51,12 @@ class UpdateRequest extends FormRequest
'amount' => ['amount', 'convertString'], 'amount' => ['amount', 'convertString'],
'currency_id' => ['currency_id', 'convertInteger'], 'currency_id' => ['currency_id', 'convertInteger'],
'currency_code' => ['currency_code', 'convertString'], 'currency_code' => ['currency_code', 'convertString'],
'notes' => ['notes', 'stringWithNewlines'],
]; ];
if(false === $this->has('notes')) {
// ignore notes, not submitted.
unset($fields['notes']);
}
return $this->getAllData($fields); return $this->getAllData($fields);
} }
@ -67,6 +71,7 @@ class UpdateRequest extends FormRequest
'amount' => ['nullable', new IsValidPositiveAmount()], 'amount' => ['nullable', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id', 'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'notes' => 'nullable|min:0|max:32768',
]; ];
} }
@ -84,7 +89,7 @@ class UpdateRequest extends FormRequest
$start = new Carbon($data['start']); $start = new Carbon($data['start']);
$end = new Carbon($data['end']); $end = new Carbon($data['end']);
if ($end->isBefore($start)) { if ($end->isBefore($start)) {
$validator->errors()->add('end', (string)trans('validation.date_after')); $validator->errors()->add('end', (string) trans('validation.date_after'));
} }
} }
} }

View File

@ -144,7 +144,7 @@ class Account extends Model
} }
/** /**
* Get all of the notes. * Get all the notes.
*/ */
public function notes(): MorphMany public function notes(): MorphMany
{ {

View File

@ -31,6 +31,7 @@ use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
@ -90,6 +91,14 @@ class BudgetLimit extends Model
return $this->belongsTo(TransactionCurrency::class); return $this->belongsTo(TransactionCurrency::class);
} }
/**
* Get all the notes.
*/
public function notes(): MorphMany
{
return $this->morphMany(Note::class, 'noteable');
}
/** /**
* Get the amount * Get the amount
*/ */

View File

@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionCurrencyFactory; use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
@ -67,8 +68,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
} }
) );
;
} }
) )
->orWhere( ->orWhere(
@ -77,15 +77,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d')); $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d')); $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
} }
) );
;
} }
) )
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.active', true) ->where('budgets.active', true)
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id);
;
if (null !== $budgets && $budgets->count() > 0) { if (null !== $budgets && $budgets->count() > 0) {
$query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray()); $query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
} }
@ -140,16 +138,14 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
->with(['budget']) ->with(['budget'])
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->get(['budget_limits.*']) ->get(['budget_limits.*']);
;
} }
// one of the two is NULL. // one of the two is NULL.
if (null === $start xor null === $end) { if (null === $start xor null === $end) {
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id);
;
if (null !== $end) { if (null !== $end) {
// end date must be before $end. // end date must be before $end.
$query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
@ -182,8 +178,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
} }
) );
;
} }
) )
->orWhere( ->orWhere(
@ -192,11 +187,9 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d')); $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d')); $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
} }
) );
;
} }
)->get(['budget_limits.*']) )->get(['budget_limits.*']);
;
} }
public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection
@ -238,8 +231,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59'));
} }
) );
;
} }
) )
->orWhere( ->orWhere(
@ -248,14 +240,12 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59')); $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
} }
) );
;
} }
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']) )->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']);
;
} }
public function setUser(null|Authenticatable|User $user): void public function setUser(null | Authenticatable | User $user): void
{ {
if ($user instanceof User) { if ($user instanceof User) {
$this->user = $user; $this->user = $user;
@ -288,8 +278,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d')) ->where('budget_limits.start_date', $data['start_date']->format('Y-m-d'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d')) ->where('budget_limits.end_date', $data['end_date']->format('Y-m-d'))
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*']) ->first(['budget_limits.*']);
;
if (null !== $limit) { if (null !== $limit) {
throw new FireflyException('200027: Budget limit already exists.'); throw new FireflyException('200027: Budget limit already exists.');
} }
@ -303,18 +292,24 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$limit->amount = $data['amount']; $limit->amount = $data['amount'];
$limit->transaction_currency_id = $currency->id; $limit->transaction_currency_id = $currency->id;
$limit->save(); $limit->save();
$noteText = (string) ($data['notes'] ?? '');
if ('' !== $noteText) {
$this->setNoteText($limit, $noteText);
}
app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount'])); app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount']));
return $limit; return $limit;
} }
public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit
{ {
return $budget->budgetlimits() return $budget->budgetlimits()
->where('transaction_currency_id', $currency->id) ->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d')) ->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first() ->where('end_date', $end->format('Y-m-d'))->first();
;
} }
/** /**
@ -353,6 +348,11 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$budgetLimit->transaction_currency_id = $currency->id; $budgetLimit->transaction_currency_id = $currency->id;
$budgetLimit->save(); $budgetLimit->save();
// update notes if they exist.
if(array_key_exists('notes', $data)) {
$this->setNoteText($budgetLimit, (string)$data['notes']);
}
return $budgetLimit; return $budgetLimit;
} }
@ -362,8 +362,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$limits = $budget->budgetlimits() $limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*') ->count('budget_limits.*');
;
app('log')->debug(sprintf('Found %d budget limits.', $limits)); app('log')->debug(sprintf('Found %d budget limits.', $limits));
// there might be a budget limit for these dates: // there might be a budget limit for these dates:
@ -371,8 +370,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$limit = $budget->budgetlimits() $limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*']) ->first(['budget_limits.*']);
;
// if more than 1 limit found, delete the others: // if more than 1 limit found, delete the others:
if ($limits > 1 && null !== $limit) { if ($limits > 1 && null !== $limit) {
@ -380,8 +378,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$budget->budgetlimits() $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->where('budget_limits.id', '!=', $limit->id)->delete() ->where('budget_limits.id', '!=', $limit->id)->delete();
;
} }
// delete if amount is zero. // delete if amount is zero.
@ -415,4 +412,25 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
return $limit; return $limit;
} }
#[\Override] public function getNoteText(BudgetLimit $budgetLimit): string
{
return (string) $budgetLimit->notes()->first()?->text;
}
#[\Override] public function setNoteText(BudgetLimit $budgetLimit, string $text): void
{
$dbNote = $budgetLimit->notes()->first();
if ('' !== $text) {
if (null === $dbNote) {
$dbNote = new Note();
$dbNote->noteable()->associate($budgetLimit);
}
$dbNote->text = trim($text);
$dbNote->save();
return;
}
$dbNote?->delete();
}
} }

View File

@ -48,6 +48,9 @@ interface BudgetLimitRepositoryInterface
*/ */
public function destroyAll(): void; public function destroyAll(): void;
public function getNoteText(BudgetLimit $budgetLimit): string;
public function setNoteText(BudgetLimit $budgetLimit, string $text): void;
/** /**
* Destroy a budget limit. * Destroy a budget limit.
*/ */

View File

@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Transformers; namespace FireflyIII\Transformers;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepository; use FireflyIII\Repositories\Budget\OperationsRepository;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
@ -55,7 +56,9 @@ class BudgetLimitTransformer extends AbstractTransformer
public function transform(BudgetLimit $budgetLimit): array public function transform(BudgetLimit $budgetLimit): array
{ {
$repository = app(OperationsRepository::class); $repository = app(OperationsRepository::class);
$limitRepos = app(BudgetLimitRepositoryInterface::class);
$repository->setUser($budgetLimit->budget->user); $repository->setUser($budgetLimit->budget->user);
$limitRepos->setUser($budgetLimit->budget->user);
$expenses = $repository->sumExpenses( $expenses = $repository->sumExpenses(
$budgetLimit->start_date, $budgetLimit->start_date,
$budgetLimit->end_date, $budgetLimit->end_date,
@ -65,6 +68,7 @@ class BudgetLimitTransformer extends AbstractTransformer
); );
$currency = $budgetLimit->transactionCurrency; $currency = $budgetLimit->transactionCurrency;
$amount = $budgetLimit->amount; $amount = $budgetLimit->amount;
$notes = $limitRepos->getNoteText($budgetLimit);
$currencyDecimalPlaces = 2; $currencyDecimalPlaces = 2;
$currencyId = null; $currencyId = null;
$currencyName = null; $currencyName = null;
@ -81,13 +85,13 @@ class BudgetLimitTransformer extends AbstractTransformer
$amount = app('steam')->bcround($amount, $currencyDecimalPlaces); $amount = app('steam')->bcround($amount, $currencyDecimalPlaces);
return [ return [
'id' => (string)$budgetLimit->id, 'id' => (string) $budgetLimit->id,
'created_at' => $budgetLimit->created_at->toAtomString(), 'created_at' => $budgetLimit->created_at->toAtomString(),
'updated_at' => $budgetLimit->updated_at->toAtomString(), 'updated_at' => $budgetLimit->updated_at->toAtomString(),
'start' => $budgetLimit->start_date->toAtomString(), 'start' => $budgetLimit->start_date->toAtomString(),
'end' => $budgetLimit->end_date->endOfDay()->toAtomString(), 'end' => $budgetLimit->end_date->endOfDay()->toAtomString(),
'budget_id' => (string)$budgetLimit->budget_id, 'budget_id' => (string) $budgetLimit->budget_id,
'currency_id' => (string)$currencyId, 'currency_id' => (string) $currencyId,
'currency_code' => $currencyCode, 'currency_code' => $currencyCode,
'currency_name' => $currencyName, 'currency_name' => $currencyName,
'currency_decimal_places' => $currencyDecimalPlaces, 'currency_decimal_places' => $currencyDecimalPlaces,
@ -95,10 +99,11 @@ class BudgetLimitTransformer extends AbstractTransformer
'amount' => $amount, 'amount' => $amount,
'period' => $budgetLimit->period, 'period' => $budgetLimit->period,
'spent' => $expenses[$currencyId]['sum'] ?? '0', 'spent' => $expenses[$currencyId]['sum'] ?? '0',
'notes' => '' === $notes ? null : $notes,
'links' => [ 'links' => [
[ [
'rel' => 'self', 'rel' => 'self',
'uri' => '/budgets/limits/'.$budgetLimit->id, 'uri' => '/budgets/limits/' . $budgetLimit->id,
], ],
], ],
]; ];