Merge branch 'develop' into patch-1

This commit is contained in:
mergify[bot]
2025-09-28 07:17:01 +00:00
committed by GitHub
214 changed files with 8175 additions and 7167 deletions

View File

@@ -402,16 +402,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.87.2",
"version": "v3.88.2",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992"
"reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992",
"reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99",
"reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99",
"shasum": ""
},
"require": {
@@ -438,12 +438,13 @@
"symfony/polyfill-mbstring": "^1.33",
"symfony/polyfill-php80": "^1.33",
"symfony/polyfill-php81": "^1.33",
"symfony/polyfill-php84": "^1.33",
"symfony/process": "^5.4.47 || ^6.4.24 || ^7.2",
"symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0"
},
"require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.7",
"infection/infection": "^0.29.14",
"infection/infection": "^0.31.0",
"justinrainbow/json-schema": "^6.5",
"keradus/cli-executor": "^2.2",
"mikey179/vfsstream": "^1.6.12",
@@ -451,7 +452,6 @@
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
"phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34",
"symfony/polyfill-php84": "^1.33",
"symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2",
"symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2"
},
@@ -494,7 +494,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.87.2"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2"
},
"funding": [
{
@@ -502,7 +502,7 @@
"type": "github"
}
],
"time": "2025-09-10T09:51:40+00:00"
"time": "2025-09-27T00:24:15+00:00"
},
{
"name": "psr/container",
@@ -2283,6 +2283,86 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php84",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php84.git",
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php84\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-06-24T13:30:11+00:00"
},
{
"name": "symfony/process",
"version": "v7.3.3",

View File

@@ -28,8 +28,10 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Http\JsonResponse;
/**
@@ -96,6 +98,7 @@ class PiggyBankController extends Controller
/** @var PiggyBank $piggy */
foreach ($piggies as $piggy) {
/** @var TransactionCurrency $currency */
$currency = $piggy->transactionCurrency;
$currentAmount = $this->piggyRepository->getCurrentAmount($piggy);
$objectGroup = $piggy->objectGroups()->first();
@@ -105,8 +108,8 @@ class PiggyBankController extends Controller
'name_with_balance' => sprintf(
'%s (%s / %s)',
$piggy->name,
app('amount')->formatAnything($currency, $currentAmount, false),
app('amount')->formatAnything($currency, $piggy->target_amount, false),
Amount::formatAnything($currency, $currentAmount, false),
Amount::formatAnything($currency, $piggy->target_amount, false),
),
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,

View File

@@ -67,7 +67,9 @@ class StoreController extends Controller
*/
public function store(StoreRequest $request): JsonResponse
{
$budget = $this->repository->store($request->getAll());
$data = $request->getAll();
$data['fire_webhooks'] ??= true;
$budget = $this->repository->store($data);
$budget->refresh();
$manager = $this->getManager();

View File

@@ -57,15 +57,10 @@ class UpdateController extends Controller
);
}
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/budgets/updateBudget
*
* Update a budget.
*/
public function update(UpdateRequest $request, Budget $budget): JsonResponse
{
$data = $request->getAll();
$data['fire_webhooks'] ??= true;
$budget = $this->repository->update($budget, $data);
$manager = $this->getManager();

View File

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

View File

@@ -77,6 +77,7 @@ class UpdateController extends Controller
throw new FireflyException('20028: The budget limit does not belong to the budget.');
}
$data = $request->getAll();
$data['fire_webhooks'] ??= true;
$data['budget_id'] = $budget->id;
$budgetLimit = $this->blRepository->update($budgetLimit, $data);
$manager = $this->getManager();

View File

@@ -72,7 +72,7 @@ class DestroyController extends Controller
public function destroySingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
{
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
if (null !== $exchangeRate) {
if ($exchangeRate instanceof CurrencyExchangeRate) {
$this->repository->deleteRate($exchangeRate);
}

View File

@@ -95,7 +95,7 @@ class ShowController extends Controller
$transformer->setParameters($this->parameters);
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
if (null === $exchangeRate) {
if (!$exchangeRate instanceof CurrencyExchangeRate) {
throw new NotFoundHttpException();
}

View File

@@ -74,7 +74,7 @@ class UpdateController extends Controller
public function updateByDate(UpdateRequest $request, TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
{
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
if (null === $exchangeRate) {
if (!$exchangeRate instanceof CurrencyExchangeRate) {
throw new NotFoundHttpException();
}
$date = $request->getDate();

View File

@@ -158,18 +158,23 @@ class ShowController extends Controller
Log::debug(sprintf('Now in triggerTransaction(%d, %d)', $webhook->id, $group->id));
Log::channel('audit')->info(sprintf('User triggers webhook #%d on transaction group #%d.', $webhook->id, $group->id));
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser(auth()->user());
// tell the generator which trigger it should look for
$engine->setTrigger(WebhookTrigger::tryFrom($webhook->trigger));
// tell the generator which objects to process
$engine->setObjects(new Collection()->push($group));
// set the webhook to trigger
$engine->setWebhooks(new Collection()->push($webhook));
// tell the generator to generate the messages
$engine->generateMessages();
/** @var \FireflyIII\Models\WebhookTrigger $trigger */
foreach ($webhook->webhookTriggers as $trigger) {
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser(auth()->user());
// tell the generator which trigger it should look for
$engine->setTrigger(WebhookTrigger::tryFrom((int)$trigger->key));
// tell the generator which objects to process
$engine->setObjects(new Collection()->push($group));
// set the webhook to trigger
$engine->setWebhooks(new Collection()->push($webhook));
// tell the generator to generate the messages
$engine->generateMessages();
}
// trigger event to send them:
Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()');

View File

@@ -48,17 +48,20 @@ class StoreRequest extends FormRequest
public function getAll(): array
{
$fields = [
'name' => ['name', 'convertString'],
'active' => ['active', 'boolean'],
'order' => ['active', 'convertInteger'],
'notes' => ['notes', 'convertString'],
'name' => ['name', 'convertString'],
'active' => ['active', 'boolean'],
'order' => ['active', 'convertInteger'],
'notes' => ['notes', 'convertString'],
// auto budget currency:
'currency_id' => ['auto_budget_currency_id', 'convertInteger'],
'currency_code' => ['auto_budget_currency_code', 'convertString'],
'auto_budget_type' => ['auto_budget_type', 'convertString'],
'auto_budget_amount' => ['auto_budget_amount', 'convertString'],
'auto_budget_period' => ['auto_budget_period', 'convertString'],
'currency_id' => ['auto_budget_currency_id', 'convertInteger'],
'currency_code' => ['auto_budget_currency_code', 'convertString'],
'auto_budget_type' => ['auto_budget_type', 'convertString'],
'auto_budget_amount' => ['auto_budget_amount', 'convertString'],
'auto_budget_period' => ['auto_budget_period', 'convertString'],
// webhooks
'fire_webhooks' => ['fire_webhooks', 'boolean'],
];
return $this->getAllData($fields);
@@ -70,15 +73,18 @@ class StoreRequest extends FormRequest
public function rules(): array
{
return [
'name' => 'required|min:1|max:255|uniqueObjectForUser:budgets,name',
'active' => [new IsBoolean()],
'currency_id' => 'exists:transaction_currencies,id',
'currency_code' => 'exists:transaction_currencies,code',
'notes' => 'nullable|min:1|max:32768',
'name' => 'required|min:1|max:255|uniqueObjectForUser:budgets,name',
'active' => [new IsBoolean()],
'currency_id' => 'exists:transaction_currencies,id',
'currency_code' => 'exists:transaction_currencies,code',
'notes' => 'nullable|min:1|max:32768',
// auto budget info
'auto_budget_type' => 'in:reset,rollover,adjusted,none',
'auto_budget_amount' => ['required_if:auto_budget_type,reset', 'required_if:auto_budget_type,rollover', 'required_if:auto_budget_type,adjusted', new IsValidPositiveAmount()],
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover|required_if:auto_budget_type,adjusted',
'auto_budget_type' => 'in:reset,rollover,adjusted,none',
'auto_budget_amount' => ['required_if:auto_budget_type,reset', 'required_if:auto_budget_type,rollover', 'required_if:auto_budget_type,adjusted', new IsValidPositiveAmount()],
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover|required_if:auto_budget_type,adjusted',
// webhooks
'fire_webhooks' => [new IsBoolean()],
];
}

View File

@@ -50,15 +50,18 @@ class UpdateRequest extends FormRequest
{
// this is the way:
$fields = [
'name' => ['name', 'convertString'],
'active' => ['active', 'boolean'],
'order' => ['order', 'convertInteger'],
'notes' => ['notes', 'convertString'],
'currency_id' => ['auto_budget_currency_id', 'convertInteger'],
'currency_code' => ['auto_budget_currency_code', 'convertString'],
'auto_budget_type' => ['auto_budget_type', 'convertString'],
'auto_budget_amount' => ['auto_budget_amount', 'convertString'],
'auto_budget_period' => ['auto_budget_period', 'convertString'],
'name' => ['name', 'convertString'],
'active' => ['active', 'boolean'],
'order' => ['order', 'convertInteger'],
'notes' => ['notes', 'convertString'],
'currency_id' => ['auto_budget_currency_id', 'convertInteger'],
'currency_code' => ['auto_budget_currency_code', 'convertString'],
'auto_budget_type' => ['auto_budget_type', 'convertString'],
'auto_budget_amount' => ['auto_budget_amount', 'convertString'],
'auto_budget_period' => ['auto_budget_period', 'convertString'],
// webhooks
'fire_webhooks' => ['fire_webhooks', 'boolean'],
];
$allData = $this->getAllData($fields);
if (array_key_exists('auto_budget_type', $allData)) {
@@ -83,14 +86,17 @@ class UpdateRequest extends FormRequest
$budget = $this->route()->parameter('budget');
return [
'name' => sprintf('min:1|max:100|uniqueObjectForUser:budgets,name,%d', $budget->id),
'active' => [new IsBoolean()],
'notes' => 'nullable|min:1|max:32768',
'auto_budget_type' => 'in:reset,rollover,adjusted,none',
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
'auto_budget_currency_code' => 'exists:transaction_currencies,code',
'auto_budget_amount' => ['nullable', new IsValidPositiveAmount()],
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
'name' => sprintf('min:1|max:100|uniqueObjectForUser:budgets,name,%d', $budget->id),
'active' => [new IsBoolean()],
'notes' => 'nullable|min:1|max:32768',
'auto_budget_type' => 'in:reset,rollover,adjusted,none',
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
'auto_budget_currency_code' => 'exists:transaction_currencies,code',
'auto_budget_amount' => ['nullable', new IsValidPositiveAmount()],
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
// webhooks
'fire_webhooks' => [new IsBoolean()],
];
}

View File

@@ -24,10 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\BudgetLimit;
use Carbon\Carbon;
use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
/**
* Class StoreRequest
@@ -49,6 +55,9 @@ class StoreRequest extends FormRequest
'currency_id' => $this->convertInteger('currency_id'),
'currency_code' => $this->convertString('currency_code'),
'notes' => $this->stringWithNewlines('notes'),
// for webhooks:
'fire_webhooks' => $this->boolean('fire_webhooks', true),
];
}
@@ -58,12 +67,59 @@ class StoreRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'required|before:end|date',
'end' => 'required|after:start|date',
'amount' => ['required', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'notes' => 'nullable|min:0|max:32768',
'start' => 'required|before:end|date',
'end' => 'required|after:start|date',
'amount' => ['required', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'notes' => 'nullable|min:0|max:32768',
// webhooks
'fire_webhooks' => [new IsBoolean()],
];
}
/**
* Configure the validator instance.
*/
public function withValidator(Validator $validator): void
{
$budget = $this->route()->parameter('budget');
$validator->after(
static function (Validator $validator) use ($budget): void {
if (0 !== count($validator->failed())) {
return;
}
$data = $validator->getData();
// if no currency has been provided, use the user's default currency:
/** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
if (null === $currency) {
$currency = Amount::getPrimaryCurrency();
}
$currency->enabled = true;
$currency->save();
// validator already concluded start and end are valid dates:
$start = Carbon::parse($data['start'], config('app.timezone'));
$end = Carbon::parse($data['end'], config('app.timezone'));
// find limit with same date range and currency.
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d'))
->where('budget_limits.end_date', $end->format('Y-m-d'))
->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*'])
;
if (null !== $limit) {
$validator->errors()->add('start', trans('validation.limit_exists'));
}
}
);
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', self::class), $validator->errors()->toArray());
}
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests\Models\BudgetLimit;
use FireflyIII\Rules\IsBoolean;
use Illuminate\Validation\Validator;
use Carbon\Carbon;
use FireflyIII\Rules\IsValidPositiveAmount;
@@ -46,12 +47,15 @@ class UpdateRequest extends FormRequest
public function getAll(): array
{
$fields = [
'start' => ['start', 'date'],
'end' => ['end', 'date'],
'amount' => ['amount', 'convertString'],
'currency_id' => ['currency_id', 'convertInteger'],
'currency_code' => ['currency_code', 'convertString'],
'notes' => ['notes', 'stringWithNewlines'],
'start' => ['start', 'date'],
'end' => ['end', 'date'],
'amount' => ['amount', 'convertString'],
'currency_id' => ['currency_id', 'convertInteger'],
'currency_code' => ['currency_code', 'convertString'],
'notes' => ['notes', 'stringWithNewlines'],
// webhooks
'fire_webhooks' => ['fire_webhooks', 'boolean'],
];
if (false === $this->has('notes')) {
// ignore notes, not submitted.
@@ -67,12 +71,15 @@ class UpdateRequest extends FormRequest
public function rules(): array
{
return [
'start' => 'date|after:1970-01-02|before:2038-01-17',
'end' => 'date|after:1970-01-02|before:2038-01-17',
'amount' => ['nullable', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'notes' => 'nullable|min:0|max:32768',
'start' => 'date|after:1970-01-02|before:2038-01-17',
'end' => 'date|after:1970-01-02|before:2038-01-17',
'amount' => ['nullable', new IsValidPositiveAmount()],
'currency_id' => 'numeric|exists:transaction_currencies,id',
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
'notes' => 'nullable|min:0|max:32768',
// webhooks
'fire_webhooks' => [new IsBoolean()],
];
}

View File

@@ -183,6 +183,7 @@ class StoreRequest extends FormRequest
// basic fields for group:
'group_title' => 'min:1|max:1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean()],
'fire_webhooks' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// location rules

View File

@@ -28,7 +28,6 @@ namespace FireflyIII\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
/**
* Class SeparateTimezoneCaster

View File

@@ -29,12 +29,15 @@ use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\AccountFactory;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Facades\Log;
class CorrectsAccountTypes extends Command
{
@@ -45,6 +48,7 @@ class CorrectsAccountTypes extends Command
private int $count;
private array $expected;
private AccountFactory $factory;
private AccountRepositoryInterface $repository;
/**
* Execute the console command.
@@ -110,7 +114,7 @@ class CorrectsAccountTypes extends Command
if ($resultSet->count() > 0) {
$this->friendlyLine(sprintf('Found %d journals that need to be fixed.', $resultSet->count()));
foreach ($resultSet as $entry) {
app('log')->debug(sprintf('Now fixing journal #%d', $entry->id));
Log::debug(sprintf('Now fixing journal #%d', $entry->id));
/** @var null|TransactionJournal $journal */
$journal = TransactionJournal::find($entry->id);
@@ -120,7 +124,7 @@ class CorrectsAccountTypes extends Command
}
}
if (0 !== $this->count) {
app('log')->debug(sprintf('%d journals had to be fixed.', $this->count));
Log::debug(sprintf('%d journals had to be fixed.', $this->count));
$this->friendlyInfo(sprintf('Acted on %d transaction(s)', $this->count));
}
@@ -134,10 +138,10 @@ class CorrectsAccountTypes extends Command
private function inspectJournal(TransactionJournal $journal): void
{
app('log')->debug(sprintf('Now inspecting journal #%d', $journal->id));
Log::debug(sprintf('Now inspecting journal #%d', $journal->id));
$transactions = $journal->transactions()->count();
if (2 !== $transactions) {
app('log')->debug(sprintf('Journal has %d transactions, so can\'t fix.', $transactions));
Log::debug(sprintf('Journal has %d transactions, so can\'t fix.', $transactions));
$this->friendlyError(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $transactions));
return;
@@ -151,20 +155,20 @@ class CorrectsAccountTypes extends Command
$destAccountType = $destAccount->accountType->type;
if (!array_key_exists($type, $this->expected)) {
app('log')->info(sprintf('No source/destination info for transaction type %s.', $type));
Log::info(sprintf('No source/destination info for transaction type %s.', $type));
$this->friendlyError(sprintf('No source/destination info for transaction type %s.', $type));
return;
}
if (!array_key_exists($sourceAccountType, $this->expected[$type])) {
app('log')->debug(sprintf('[a] Going to fix journal #%d', $journal->id));
Log::debug(sprintf('[a] Going to fix journal #%d', $journal->id));
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
return;
}
$expectedTypes = $this->expected[$type][$sourceAccountType];
if (!in_array($destAccountType, $expectedTypes, true)) {
app('log')->debug(sprintf('[b] Going to fix journal #%d', $journal->id));
Log::debug(sprintf('[b] Going to fix journal #%d', $journal->id));
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
}
}
@@ -181,13 +185,15 @@ class CorrectsAccountTypes extends Command
private function fixJournal(TransactionJournal $journal, string $transactionType, Transaction $source, Transaction $dest): void
{
app('log')->debug(sprintf('Going to fix journal #%d', $journal->id));
Log::debug(sprintf('Going to fix journal #%d', $journal->id));
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser($journal->user);
++$this->count;
// variables:
$sourceType = $source->account->accountType->type;
$destinationType = $dest->account->accountType->type;
$combination = sprintf('%s%s%s', $transactionType, $source->account->accountType->type, $dest->account->accountType->type);
app('log')->debug(sprintf('Combination is "%s"', $combination));
$sourceType = $source->account->accountType->type;
$destinationType = $dest->account->accountType->type;
$combination = sprintf('%s%s%s', $transactionType, $source->account->accountType->type, $dest->account->accountType->type);
Log::debug(sprintf('Combination is "%s"', $combination));
if ($this->shouldBeTransfer($transactionType, $sourceType, $destinationType)) {
$this->makeTransfer($journal);
@@ -211,37 +217,45 @@ class CorrectsAccountTypes extends Command
}
// transaction has no valid source.
$validSources = array_keys($this->expected[$transactionType]);
$canCreateSource = $this->canCreateSource($validSources);
$hasValidSource = $this->hasValidAccountType($validSources, $sourceType);
$validSources = array_keys($this->expected[$transactionType]);
$canCreateSource = $this->canCreateSource($validSources);
$hasValidSource = $this->hasValidAccountType($validSources, $sourceType);
if (!$hasValidSource && $canCreateSource) {
$this->giveNewRevenue($journal, $source);
return;
}
if (!$canCreateSource && !$hasValidSource) {
app('log')->debug('This transaction type has no source we can create. Just give error.');
Log::debug('This transaction type has no source we can create. Just give error.');
$message = sprintf('The source account of %s #%d cannot be of type "%s". Firefly III cannot fix this. You may have to remove the transaction yourself.', $transactionType, $journal->id, $source->account->accountType->type);
$this->friendlyError($message);
app('log')->debug($message);
Log::debug($message);
return;
}
/** @var array $validDestinations */
$validDestinations = $this->expected[$transactionType][$sourceType] ?? [];
$canCreateDestination = $this->canCreateDestination($validDestinations);
$hasValidDestination = $this->hasValidAccountType($validDestinations, $destinationType);
$validDestinations = $this->expected[$transactionType][$sourceType] ?? [];
$canCreateDestination = $this->canCreateDestination($validDestinations);
$hasValidDestination = $this->hasValidAccountType($validDestinations, $destinationType);
$alternativeDestination = $this->repository->findByName($dest->account->name, $validDestinations);
if (!$hasValidDestination && $canCreateDestination) {
$this->giveNewExpense($journal, $dest);
return;
}
if (!$canCreateDestination && !$hasValidDestination) {
app('log')->debug('This transaction type has no destination we can create. Just give error.');
if (!$canCreateDestination && !$hasValidDestination && null === $alternativeDestination) {
Log::debug('This transaction type has no destination we can create. Just give error.');
$message = sprintf('The destination account of %s #%d cannot be of type "%s". Firefly III cannot fix this. You may have to remove the transaction yourself.', $transactionType, $journal->id, $dest->account->accountType->type);
$this->friendlyError($message);
app('log')->debug($message);
Log::debug($message);
}
if (!$canCreateDestination && !$hasValidDestination && null !== $alternativeDestination) {
Log::debug('This transaction type has no destination we can create, but found alternative with the same name.');
$message = sprintf('The destination account of %s #%d cannot be of type "%s". Firefly III found an alternative account. Please make sure this transaction is correct.', $transactionType, $journal->transaction_group_id, $dest->account->accountType->type);
$this->friendlyInfo($message);
Log::debug($message);
$this->giveNewDestinationAccount($journal, $alternativeDestination);
}
}
@@ -263,7 +277,7 @@ class CorrectsAccountTypes extends Command
$journal->save();
$message = sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id);
$this->friendlyInfo($message);
app('log')->debug($message);
Log::debug($message);
// check it again:
$this->inspectJournal($journal);
}
@@ -281,7 +295,7 @@ class CorrectsAccountTypes extends Command
$journal->save();
$message = sprintf('Converted transaction #%d from a transfer to a deposit.', $journal->id);
$this->friendlyInfo($message);
app('log')->debug($message);
Log::debug($message);
// check it again:
$this->inspectJournal($journal);
}
@@ -308,7 +322,7 @@ class CorrectsAccountTypes extends Command
$result->name
);
$this->friendlyWarning($message);
app('log')->debug($message);
Log::debug($message);
$this->inspectJournal($journal);
}
@@ -335,7 +349,7 @@ class CorrectsAccountTypes extends Command
$result->name
);
$this->friendlyWarning($message);
app('log')->debug($message);
Log::debug($message);
$this->inspectJournal($journal);
}
@@ -354,14 +368,14 @@ class CorrectsAccountTypes extends Command
private function giveNewRevenue(TransactionJournal $journal, Transaction $source): void
{
app('log')->debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value));
Log::debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value));
$this->factory->setUser($journal->user);
$name = $source->account->name;
$newSource = $this->factory->findOrCreate($name, AccountTypeEnum::REVENUE->value);
$source->account()->associate($newSource);
$source->save();
$this->friendlyPositive(sprintf('Firefly III gave transaction #%d a new source %s: #%d ("%s").', $journal->transaction_group_id, AccountTypeEnum::REVENUE->value, $newSource->id, $newSource->name));
app('log')->debug(sprintf('Associated account #%d with transaction #%d', $newSource->id, $source->id));
Log::debug(sprintf('Associated account #%d with transaction #%d', $newSource->id, $source->id));
$this->inspectJournal($journal);
}
@@ -372,14 +386,33 @@ class CorrectsAccountTypes extends Command
private function giveNewExpense(TransactionJournal $journal, Transaction $destination): void
{
app('log')->debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value));
Log::debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value));
$this->factory->setUser($journal->user);
$name = $destination->account->name;
$newDestination = $this->factory->findOrCreate($name, AccountTypeEnum::EXPENSE->value);
$destination->account()->associate($newDestination);
$destination->save();
$this->friendlyPositive(sprintf('Firefly III gave transaction #%d a new destination %s: #%d ("%s").', $journal->transaction_group_id, AccountTypeEnum::EXPENSE->value, $newDestination->id, $newDestination->name));
app('log')->debug(sprintf('Associated account #%d with transaction #%d', $newDestination->id, $destination->id));
Log::debug(sprintf('Associated account #%d with transaction #%d', $newDestination->id, $destination->id));
$this->inspectJournal($journal);
}
private function giveNewDestinationAccount(TransactionJournal $journal, Account $newDestination): void
{
$destTransaction = $this->getDestinationTransaction($journal);
$oldDest = $destTransaction->account;
$destTransaction->account_id = $newDestination->id;
$destTransaction->save();
$message = sprintf(
'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").',
$journal->id,
$oldDest->id,
$oldDest->name,
$newDestination->id,
$newDestination->name
);
$this->friendlyInfo($message);
$journal->refresh();
Log::debug($message);
}
}

View File

@@ -75,7 +75,8 @@ class CorrectsDatabase extends Command
'correction:recalculates-liabilities',
'correction:preferences',
// 'correction:transaction-types', // resource heavy, disabled.
'correction:recalculate-pc-amounts', // not necessary, disabled.
'correction:recalculate-pc-amounts',
'correction:remove-links-to-deleted-objects',
'firefly-iii:report-integrity',
];
foreach ($commands as $command) {

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/*
* RemovesLinksToDeletedObjects.php
* Copyright (c) 2025 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class RemovesLinksToDeletedObjects extends Command
{
use ShowsFriendlyMessages;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'correction:remove-links-to-deleted-objects';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes deleted entries from intermediate tables.';
/**
* Execute the console command.
*/
public function handle(): void
{
$deletedTags = Tag::withTrashed()->whereNotNull('deleted_at')->get('tags.id')->pluck('id')->toArray();
$deletedJournals = TransactionJournal::withTrashed()->whereNotNull('deleted_at')->get('transaction_journals.id')->pluck('id')->toArray();
$deletedBudgets = Budget::withTrashed()->whereNotNull('deleted_at')->get('budgets.id')->pluck('id')->toArray();
$deletedCategories = Category::withTrashed()->whereNotNull('deleted_at')->get('categories.id')->pluck('id')->toArray();
if (count($deletedTags) > 0) {
$this->cleanupTags($deletedTags);
}
if (count($deletedJournals) > 0) {
$this->cleanupJournals($deletedJournals);
}
if (count($deletedBudgets) > 0) {
$this->cleanupBudgets($deletedBudgets);
}
if (count($deletedCategories) > 0) {
$this->cleanupCategories($deletedCategories);
}
$this->friendlyNeutral('Validated links to deleted objects.');
}
private function cleanupTags(array $tags): void
{
$count = DB::table('tag_transaction_journal')->whereIn('tag_id', $tags)->delete();
if ($count > 0) {
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories transactions and tags.', $count));
}
}
private function cleanupJournals(array $journals): void
{
$count = DB::table('tag_transaction_journal')->whereIn('transaction_journal_id', $journals)->delete();
if ($count > 0) {
$this->friendlyInfo(sprintf('Removed %d old relationship(s) between tags and transactions.', $count));
}
$count = DB::table('budget_transaction_journal')->whereIn('transaction_journal_id', $journals)->delete();
if ($count > 0) {
$this->friendlyInfo(sprintf('Removed %d old relationship(s) between budgets and transactions.', $count));
}
$count = DB::table('category_transaction_journal')->whereIn('transaction_journal_id', $journals)->delete();
if ($count > 0) {
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories and transactions.', $count));
}
}
private function cleanupBudgets(array $budgets): void
{
$count = DB::table('budget_transaction_journal')->whereIn('budget_id', $budgets)->delete();
if ($count > 0) {
$this->friendlyInfo(sprintf('Removed %d old relationship(s) between budgets and transactions.', $count));
}
}
private function cleanupCategories(array $categories): void
{
$count = DB::table('category_transaction_journal')->whereIn('category_id', $categories)->delete();
if ($count > 0) {
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories categories and transactions.', $count));
}
}
}

View File

@@ -283,7 +283,7 @@ class ApplyRules extends Command
if (null !== $endString && '' !== $endString) {
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
}
if (null === $inputEnd || null === $inputStart) {
if (!$inputEnd instanceof Carbon || null === $inputStart) {
Log::error('Could not parse start or end date in verifyInputDate().');
return;

View File

@@ -86,6 +86,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
return $this->handleAttachment($request, $e);
case 'bills.show':
case 'subscriptions.show':
$request->session()->reflash();
return redirect(route('bills.index'));

View File

@@ -222,7 +222,7 @@ class TransactionJournalFactory
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount);
Log::debug('Done with getAccount(2x)');

View File

@@ -28,6 +28,7 @@ use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
@@ -36,6 +37,8 @@ use Illuminate\Support\Facades\Log;
/**
* Class StoredGroupEventHandler
*
* TODO migrate to observer?
*/
class StoredGroupEventHandler
{
@@ -44,6 +47,7 @@ class StoredGroupEventHandler
$this->processRules($event);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
$this->removePeriodStatistics($event);
}
/**
@@ -94,6 +98,26 @@ class StoredGroupEventHandler
$object->recalculate();
}
private function removePeriodStatistics(StoredTransactionGroup $event): void
{
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$repository->deleteStatisticsForModel($source->account, $journal->date);
$repository->deleteStatisticsForModel($dest->account, $journal->date);
foreach ($journal->categories as $category) {
$repository->deleteStatisticsForModel($category, $journal->date);
}
foreach ($journal->tags as $tag) {
$repository->deleteStatisticsForModel($tag, $journal->date);
}
}
}
/**
* This method processes all webhooks that respond to the "stored transaction group" trigger (100)
*/

View File

@@ -31,6 +31,7 @@ use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\Support\Models\AccountBalanceCalculator;
@@ -49,10 +50,35 @@ class UpdatedGroupEventHandler
$this->processRules($event);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
$this->removePeriodStatistics($event);
if ($event->runRecalculations) {
$this->updateRunningBalance($event);
}
}
/**
* TODO duplicate
*/
private function removePeriodStatistics(UpdatedTransactionGroup $event): void
{
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
/** @var TransactionJournal $journal */
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$repository->deleteStatisticsForModel($source->account, $journal->date);
$repository->deleteStatisticsForModel($dest->account, $journal->date);
foreach ($journal->categories as $category) {
$repository->deleteStatisticsForModel($category, $journal->date);
}
foreach ($journal->tags as $tag) {
$repository->deleteStatisticsForModel($tag, $journal->date);
}
}
}
/**

View File

@@ -62,5 +62,8 @@ class WebhookEventHandler
Log::debug(sprintf('Skip message #%d', $message->id));
}
}
// clean up sent messages table:
WebhookMessage::where('webhook_messages.sent', true)->where('webhook_messages.created_at', '<', now()->subDays(30))->delete();
}
}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Models\BudgetLimit;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -44,17 +45,24 @@ class BudgetLimitObserver
$this->updatePrimaryCurrencyAmount($budgetLimit);
$this->updateAvailableBudget($budgetLimit);
$user = $budgetLimit->budget->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budgetLimit));
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
$engine->generateMessages();
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
if (true === $singleton->getPreference('fire_webhooks_bl_store')) {
$user = $budgetLimit->budget->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budgetLimit));
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
}
}
private function updatePrimaryCurrencyAmount(BudgetLimit $budgetLimit): void
@@ -82,16 +90,21 @@ class BudgetLimitObserver
$this->updatePrimaryCurrencyAmount($budgetLimit);
$this->updateAvailableBudget($budgetLimit);
$user = $budgetLimit->budget->user;
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budgetLimit));
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
$engine->generateMessages();
if (true === $singleton->getPreference('fire_webhooks_bl_update')) {
$user = $budgetLimit->budget->user;
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budgetLimit));
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
}
}
}

View File

@@ -31,6 +31,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -45,32 +46,43 @@ class BudgetObserver
{
Log::debug(sprintf('Observe "created" of budget #%d ("%s").', $budget->id, $budget->name));
// fire event.
$user = $budget->user;
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::STORE_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
if (true === $singleton->getPreference('fire_webhooks_budget_create')) {
// fire event.
$user = $budget->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::STORE_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
}
}
public function updated(Budget $budget): void
{
Log::debug(sprintf('Observe "updated" of budget #%d ("%s").', $budget->id, $budget->name));
$user = $budget->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::UPDATE_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
if (true === $singleton->getPreference('fire_webhooks_budget_update')) {
$user = $budget->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::UPDATE_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
}
}
public function deleting(Budget $budget): void

View File

@@ -102,7 +102,7 @@ class ShowController extends Controller
// make sure dates are end of day and start of day:
$start->startOfDay();
$end->endOfDay();
$end->endOfDay()->milli(0);
$location = $this->repository->getLocation($account);
$attachments = $this->repository->getAttachments($account);

View File

@@ -122,6 +122,11 @@ class NotificationController extends Controller
public function testNotification(Request $request): RedirectResponse
{
if (true === auth()->user()->hasRole('demo')) {
session()->flash('error', (string) trans('firefly.not_available_demo_user'));
return redirect(route('settings.notification.index'));
}
$all = $request->all();
$channel = $all['test_submit'] ?? '';

View File

@@ -27,6 +27,7 @@ use Carbon\Carbon;
use FireflyIII\Helpers\Update\UpdateTrait;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -66,8 +67,8 @@ class UpdateController extends Controller
{
$subTitle = (string) trans('firefly.update_check_title');
$subTitleIcon = 'fa-star';
$permission = app('fireflyconfig')->get('permission_update_check', -1);
$channel = app('fireflyconfig')->get('update_channel', 'stable');
$permission = FireflyConfig::get('permission_update_check', -1);
$channel = FireflyConfig::get('update_channel', 'stable');
$selected = $permission->data;
$channelSelected = $channel->data;
$options = [
@@ -96,9 +97,9 @@ class UpdateController extends Controller
$channel = $request->get('update_channel');
$channel = in_array($channel, ['stable', 'beta', 'alpha'], true) ? $channel : 'stable';
app('fireflyconfig')->set('permission_update_check', $checkForUpdates);
app('fireflyconfig')->set('last_update_check', Carbon::now()->getTimestamp());
app('fireflyconfig')->set('update_channel', $channel);
FireflyConfig::set('permission_update_check', $checkForUpdates);
FireflyConfig::set('last_update_check', Carbon::now()->getTimestamp());
FireflyConfig::set('update_channel', $channel);
session()->flash('success', (string) trans('firefly.configuration_updated'));
return redirect(route('settings.update-check'));

View File

@@ -92,7 +92,7 @@ class ShowController extends Controller
// get first journal ever to set off the budget period overview.
$first = $this->journalRepos->firstNull();
$firstDate = $first instanceof TransactionJournal ? $first->date : $start;
$periods = $this->getNoBudgetPeriodOverview($firstDate, $end);
$periods = $this->getNoModelPeriodOverview('budget', $firstDate, $end);
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;

View File

@@ -35,6 +35,7 @@ use FireflyIII\Support\Http\Controllers\PeriodOverview;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
/**
@@ -74,7 +75,7 @@ class NoCategoryController extends Controller
*/
public function show(Request $request, ?Carbon $start = null, ?Carbon $end = null)
{
app('log')->debug('Start of noCategory()');
Log::debug('Start of noCategory()');
$start ??= session('start');
$end ??= session('end');
@@ -82,14 +83,12 @@ class NoCategoryController extends Controller
/** @var Carbon $end */
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview($start);
$subTitle = trans('firefly.without_category_between', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]);
$first = $this->journalRepos->firstNull()->date ?? clone $start;
$periods = $this->getNoModelPeriodOverview('category', $first, $end);
app('log')->debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
app('log')->debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
@@ -117,13 +116,13 @@ class NoCategoryController extends Controller
$periods = new Collection();
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
app('log')->debug('Start of noCategory()');
Log::debug('Start of noCategory()');
$subTitle = (string) trans('firefly.all_journals_without_category');
$first = $this->journalRepos->firstNull();
$start = $first instanceof TransactionJournal ? $first->date : new Carbon();
$end = today(config('app.timezone'));
app('log')->debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
app('log')->debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Controllers;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use FireflyIII\Support\Http\Controllers\UserNavigation;
@@ -133,7 +134,7 @@ abstract class Controller extends BaseController
$this->primaryCurrency = Amount::getPrimaryCurrency();
$language = Steam::getLanguage();
$locale = Steam::getLocale();
$darkMode = app('preferences')->get('darkMode', 'browser')->data;
$darkMode = Preferences::get('darkMode', 'browser')->data;
$this->convertToPrimary = Amount::convertToPrimary();
$page = $this->getPageName();
$shownDemo = $this->hasSeenDemo();

View File

@@ -30,6 +30,7 @@ use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
@@ -108,6 +109,8 @@ class DebugController extends Controller
Artisan::call('route:clear');
Artisan::call('view:clear');
PeriodStatistic::where('id', '>', 0)->delete();
// also do some recalculations.
Artisan::call('correction:recalculates-liabilities');
AccountBalanceCalculator::recalculateAll(false);

View File

@@ -31,6 +31,7 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Services\Internal\Update\GroupCloneService;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
@@ -76,7 +77,7 @@ class CreateController extends Controller
// event!
event(new StoredTransactionGroup($newGroup, true, true));
app('preferences')->mark();
Preferences::mark();
$title = $newGroup->title ?? $newGroup->transactionJournals->first()->description;
$link = route('transactions.show', [$newGroup->id]);
@@ -103,7 +104,7 @@ class CreateController extends Controller
* */
public function create(?string $objectType)
{
app('preferences')->mark();
Preferences::mark();
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
@@ -114,7 +115,9 @@ class CreateController extends Controller
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
$optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
/** @var null|array $optionalFields */
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');

View File

@@ -146,25 +146,7 @@ class ShowController extends Controller
$attachments = $this->repository->getAttachments($transactionGroup);
$links = $this->repository->getLinks($transactionGroup);
return view(
'transactions.show',
compact(
'transactionGroup',
'amounts',
'first',
'type',
'logEntries',
'groupLogEntries',
'subTitle',
'splits',
'selectedGroup',
'groupArray',
'events',
'attachments',
'links',
'accounts',
)
);
return view('transactions.show', compact('transactionGroup', 'amounts', 'first', 'type', 'logEntries', 'groupLogEntries', 'subTitle', 'splits', 'selectedGroup', 'groupArray', 'events', 'attachments', 'links', 'accounts'));
}
private function getAmounts(array $group): array

View File

@@ -23,11 +23,13 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Handlers\Observer\AccountObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -40,6 +42,7 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([AccountObserver::class])]
class Account extends Model
{
use HasFactory;
@@ -60,7 +63,7 @@ class Account extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$accountId = (int) $value;
$accountId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -95,39 +98,6 @@ class Account extends Model
return $this->morphMany(Attachment::class, 'attachable');
}
/**
* Get the account number.
*/
protected function accountNumber(): Attribute
{
return Attribute::make(get: function () {
/** @var null|AccountMeta $metaValue */
$metaValue = $this->accountMeta()
->where('name', 'account_number')
->first()
;
return null !== $metaValue ? $metaValue->data : '';
});
}
public function accountMeta(): HasMany
{
return $this->hasMany(AccountMeta::class);
}
protected function editName(): Attribute
{
return Attribute::make(get: function () {
$name = $this->name;
if (AccountTypeEnum::CASH->value === $this->accountType->type) {
return '';
}
return $name;
});
}
public function locations(): MorphMany
{
return $this->morphMany(Location::class, 'locatable');
@@ -154,19 +124,9 @@ class Account extends Model
return $this->belongsToMany(PiggyBank::class);
}
#[Scope]
protected function accountTypeIn(EloquentBuilder $query, array $types): void
{
if (false === $this->joinedAccountTypes) {
$query->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id');
$this->joinedAccountTypes = true;
}
$query->whereIn('account_types.type', $types);
}
public function setVirtualBalanceAttribute(mixed $value): void
{
$value = (string) $value;
$value = (string)$value;
if ('' === $value) {
$value = null;
}
@@ -186,42 +146,49 @@ class Account extends Model
protected function accountId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
/**
* Get the account number.
*/
protected function accountNumber(): Attribute
{
return Attribute::make(get: function () {
/** @var null|AccountMeta $metaValue */
$metaValue = $this->accountMeta()
->where('name', 'account_number')
->first()
;
return null !== $metaValue ? $metaValue->data : '';
});
}
public function accountMeta(): HasMany
{
return $this->hasMany(AccountMeta::class);
}
/**
* Get the user ID
*/
protected function accountTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
protected function iban(): Attribute
#[Scope]
protected function accountTypeIn(EloquentBuilder $query, array $types): void
{
return Attribute::make(
get: static fn ($value) => null === $value ? null : trim(str_replace(' ', '', (string) $value)),
);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
/**
* Get the virtual balance
*/
protected function virtualBalance(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
if (false === $this->joinedAccountTypes) {
$query->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id');
$this->joinedAccountTypes = true;
}
$query->whereIn('account_types.type', $types);
}
protected function casts(): array
@@ -238,4 +205,47 @@ class Account extends Model
'native_virtual_balance' => 'string',
];
}
protected function editName(): Attribute
{
return Attribute::make(get: function () {
$name = $this->name;
if (AccountTypeEnum::CASH->value === $this->accountType->type) {
return '';
}
return $name;
});
}
protected function iban(): Attribute
{
return Attribute::make(
get: static fn ($value) => null === $value ? null : trim(str_replace(' ', '', (string)$value)),
);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
/**
* Get the virtual balance
*/
protected function virtualBalance(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
public function primaryPeriodStatistics(): MorphMany
{
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
}
}

View File

@@ -23,8 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -43,11 +43,6 @@ class AccountMeta extends Model
return $this->belongsTo(Account::class);
}
protected function data(): Attribute
{
return Attribute::make(get: fn (mixed $value) => (string) json_decode((string) $value, true), set: fn (mixed $value) => ['data' => json_encode($value)]);
}
protected function casts(): array
{
return [
@@ -55,4 +50,9 @@ class AccountMeta extends Model
'updated_at' => 'datetime',
];
}
protected function data(): Attribute
{
return Attribute::make(get: fn (mixed $value) => (string)json_decode((string)$value, true), set: fn (mixed $value) => ['data' => json_encode($value)]);
}
}

View File

@@ -32,46 +32,60 @@ class AccountType extends Model
{
use ReturnsIntegerIdTrait;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string ASSET = 'Asset account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string BENEFICIARY = 'Beneficiary account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string CASH = 'Cash account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string CREDITCARD = 'Credit card';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string DEBT = 'Debt';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string DEFAULT = 'Default account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string EXPENSE = 'Expense account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string IMPORT = 'Import account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string INITIAL_BALANCE = 'Initial balance account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string LOAN = 'Loan';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string MORTGAGE = 'Mortgage';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation account';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string REVENUE = 'Revenue account';
protected $casts

View File

@@ -23,9 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\AttachmentObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -34,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([AttachmentObserver::class])]
class Attachment extends Model
{
use ReturnsIntegerIdTrait;
@@ -50,7 +53,7 @@ class Attachment extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$attachmentId = (int) $value;
$attachmentId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -83,7 +86,7 @@ class Attachment extends Model
*/
public function fileName(): string
{
return sprintf('at-%s.data', (string) $this->id);
return sprintf('at-%s.data', (string)$this->id);
}
/**
@@ -97,7 +100,7 @@ class Attachment extends Model
protected function attachableId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}

View File

@@ -48,14 +48,7 @@ class AuditLogEntry extends Model
protected function auditableId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function changerId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
@@ -69,4 +62,11 @@ class AuditLogEntry extends Model
'deleted_at' => 'datetime',
];
}
protected function changerId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -25,24 +25,30 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Deprecated;
use FireflyIII\Handlers\Observer\AutoBudgetObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
#[ObservedBy([AutoBudgetObserver::class])]
class AutoBudget extends Model
{
use ReturnsIntegerIdTrait;
use SoftDeletes;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int AUTO_BUDGET_ADJUSTED = 3;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int AUTO_BUDGET_RESET = 1;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int AUTO_BUDGET_ROLLOVER = 2;
protected $casts
= [
@@ -64,14 +70,14 @@ class AutoBudget extends Model
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
get: static fn ($value) => (string)$value,
);
}
protected function budgetId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
@@ -85,7 +91,7 @@ class AutoBudget extends Model
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -24,15 +24,18 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\Handlers\Observer\AvailableBudgetObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([AvailableBudgetObserver::class])]
class AvailableBudget extends Model
{
use ReturnsIntegerIdTrait;
@@ -49,7 +52,7 @@ class AvailableBudget extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$availableBudgetId = (int) $value;
$availableBudgetId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -77,10 +80,26 @@ class AvailableBudget extends Model
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
get: static fn ($value) => (string)$value,
);
}
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'start_date' => 'date',
'end_date' => 'date',
'transaction_currency_id' => 'int',
'amount' => 'string',
'native_amount' => 'string',
'user_id' => 'integer',
'user_group_id' => 'integer',
];
}
protected function endDate(): Attribute
{
return Attribute::make(
@@ -100,23 +119,7 @@ class AvailableBudget extends Model
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'start_date' => 'date',
'end_date' => 'date',
'transaction_currency_id' => 'int',
'amount' => 'string',
'native_amount' => 'string',
'user_id' => 'integer',
'user_group_id' => 'integer',
];
}
}

View File

@@ -24,9 +24,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Handlers\Observer\BillObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -36,6 +38,7 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([BillObserver::class])]
class Bill extends Model
{
use ReturnsIntegerIdTrait;
@@ -75,7 +78,7 @@ class Bill extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$billId = (int) $value;
$billId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -121,7 +124,7 @@ class Bill extends Model
*/
public function setAmountMaxAttribute($value): void
{
$this->attributes['amount_max'] = (string) $value;
$this->attributes['amount_max'] = (string)$value;
}
/**
@@ -129,7 +132,7 @@ class Bill extends Model
*/
public function setAmountMinAttribute($value): void
{
$this->attributes['amount_min'] = (string) $value;
$this->attributes['amount_min'] = (string)$value;
}
public function transactionCurrency(): BelongsTo
@@ -148,7 +151,7 @@ class Bill extends Model
protected function amountMax(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
get: static fn ($value) => (string)$value,
);
}
@@ -158,31 +161,7 @@ class Bill extends Model
protected function amountMin(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
/**
* Get the skip
*/
protected function skip(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (string)$value,
);
}
@@ -206,4 +185,28 @@ class Bill extends Model
'native_amount_max' => 'string',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
/**
* Get the skip
*/
protected function skip(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -23,9 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\BudgetObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -35,6 +37,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([BudgetObserver::class])]
class Budget extends Model
{
use ReturnsIntegerIdTrait;
@@ -53,7 +56,7 @@ class Budget extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$budgetId = (int) $value;
$budgetId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -106,13 +109,6 @@ class Budget extends Model
return $this->belongsToMany(Transaction::class, 'budget_transaction', 'budget_id');
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -125,4 +121,11 @@ class Budget extends Model
'user_group_id' => 'integer',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -24,13 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Handlers\Observer\BudgetLimitObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([BudgetLimitObserver::class])]
class BudgetLimit extends Model
{
use ReturnsIntegerIdTrait;
@@ -45,7 +48,7 @@ class BudgetLimit extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$budgetLimitId = (int) $value;
$budgetLimitId = (int)$value;
$budgetLimit = self::where('budget_limits.id', $budgetLimitId)
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('budgets.user_id', auth()->user()->id)
@@ -83,21 +86,14 @@ class BudgetLimit extends Model
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
get: static fn ($value) => (string)$value,
);
}
protected function budgetId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
@@ -113,4 +109,11 @@ class BudgetLimit extends Model
'native_amount' => 'string',
];
}
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -24,9 +24,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\CategoryObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -34,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([CategoryObserver::class])]
class Category extends Model
{
use ReturnsIntegerIdTrait;
@@ -52,7 +55,7 @@ class Category extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$categoryId = (int) $value;
$categoryId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -106,4 +109,9 @@ class Category extends Model
'user_group_id' => 'integer',
];
}
public function primaryPeriodStatistics(): MorphMany
{
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
}
}

View File

@@ -23,8 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -38,14 +38,6 @@ class Configuration extends Model
protected $table = 'configuration';
/**
* TODO can be replaced with native laravel code.
*/
protected function data(): Attribute
{
return Attribute::make(get: fn ($value) => json_decode((string) $value), set: fn ($value) => ['data' => json_encode($value)]);
}
protected function casts(): array
{
return [
@@ -54,4 +46,12 @@ class Configuration extends Model
'deleted_at' => 'datetime',
];
}
/**
* TODO can be replaced with native laravel code.
*/
protected function data(): Attribute
{
return Attribute::make(get: fn ($value) => json_decode((string)$value), set: fn ($value) => ['data' => json_encode($value)]);
}
}

View File

@@ -37,7 +37,8 @@ class CurrencyExchangeRate extends Model
use ReturnsIntegerIdTrait;
use ReturnsIntegerUserIdTrait;
use SoftDeletes;
protected $fillable = ['user_id', 'from_currency_id', 'to_currency_id', 'date', 'date_tz', 'rate'];
protected $fillable = ['user_id', 'user_group_id', 'from_currency_id', 'to_currency_id', 'date', 'date_tz', 'rate'];
public function fromCurrency(): BelongsTo
{
@@ -54,34 +55,6 @@ class CurrencyExchangeRate extends Model
return $this->belongsTo(User::class);
}
protected function fromCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function rate(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function toCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function userRate(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function casts(): array
{
return [
@@ -96,4 +69,32 @@ class CurrencyExchangeRate extends Model
'user_rate' => 'string',
];
}
protected function fromCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function rate(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
protected function toCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function userRate(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
}

View File

@@ -53,13 +53,6 @@ class GroupMembership extends Model
return $this->belongsTo(UserRole::class);
}
protected function userRoleId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -69,4 +62,11 @@ class GroupMembership extends Model
'user_group_id' => 'integer',
];
}
protected function userRoleId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -36,6 +36,7 @@ class InvitedUser extends Model
{
use ReturnsIntegerIdTrait;
use ReturnsIntegerUserIdTrait;
protected $fillable = ['user_group_id', 'user_id', 'email', 'invite_code', 'expires', 'expires_tz', 'redeemed'];
/**
@@ -44,7 +45,7 @@ class InvitedUser extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$attemptId = (int) $value;
$attemptId = (int)$value;
/** @var null|InvitedUser $attempt */
$attempt = self::find($attemptId);

View File

@@ -44,7 +44,7 @@ class LinkType extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$linkTypeId = (int) $value;
$linkTypeId = (int)$value;
$linkType = self::find($linkTypeId);
if (null !== $linkType) {
return $linkType;

View File

@@ -66,13 +66,6 @@ class Location extends Model
return $this->morphMany(TransactionJournal::class, 'locatable');
}
protected function locatableId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -84,4 +77,11 @@ class Location extends Model
'longitude' => 'float',
];
}
protected function locatableId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -44,13 +44,6 @@ class Note extends Model
return $this->morphTo();
}
protected function noteableId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -59,4 +52,11 @@ class Note extends Model
'deleted_at' => 'datetime',
];
}
protected function noteableId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -37,6 +37,7 @@ class ObjectGroup extends Model
{
use ReturnsIntegerIdTrait;
use ReturnsIntegerUserIdTrait;
protected $fillable = ['title', 'order', 'user_id', 'user_group_id'];
/**
@@ -47,7 +48,7 @@ class ObjectGroup extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$objectGroupId = (int) $value;
$objectGroupId = (int)$value;
/** @var null|ObjectGroup $objectGroup */
$objectGroup = self::where('object_groups.id', $objectGroupId)
@@ -90,13 +91,6 @@ class ObjectGroup extends Model
return $this->morphedByMany(PiggyBank::class, 'object_groupable');
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -107,4 +101,11 @@ class ObjectGroup extends Model
'deleted_at' => 'datetime',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class PeriodStatistic extends Model
{
use ReturnsIntegerUserIdTrait;
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start' => SeparateTimezoneCaster::class,
'end' => SeparateTimezoneCaster::class,
];
}
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
protected function count(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
public function primaryStatable(): MorphTo
{
return $this->morphTo();
}
public function secondaryStatable(): MorphTo
{
return $this->morphTo();
}
public function tertiaryStatable(): MorphTo
{
return $this->morphTo();
}
}

View File

@@ -23,7 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\PiggyBankObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -34,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([PiggyBankObserver::class])]
class PiggyBank extends Model
{
use ReturnsIntegerIdTrait;
@@ -49,7 +52,7 @@ class PiggyBank extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$piggyBankId = (int) $value;
$piggyBankId = (int)$value;
$piggyBank = self::where('piggy_banks.id', $piggyBankId)
->leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
@@ -109,7 +112,7 @@ class PiggyBank extends Model
*/
public function setTargetAmountAttribute($value): void
{
$this->attributes['target_amount'] = (string) $value;
$this->attributes['target_amount'] = (string)$value;
}
public function transactionCurrency(): BelongsTo
@@ -120,24 +123,7 @@ class PiggyBank extends Model
protected function accountId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
/**
* Get the max amount
*/
protected function targetAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
get: static fn ($value) => (int)$value,
);
}
@@ -156,4 +142,21 @@ class PiggyBank extends Model
'native_target_amount' => 'string',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
/**
* Get the max amount
*/
protected function targetAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
}

View File

@@ -24,11 +24,14 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Handlers\Observer\PiggyBankEventObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[ObservedBy([PiggyBankEventObserver::class])]
class PiggyBankEvent extends Model
{
use ReturnsIntegerIdTrait;
@@ -47,7 +50,7 @@ class PiggyBankEvent extends Model
*/
public function setAmountAttribute($value): void
{
$this->attributes['amount'] = (string) $value;
$this->attributes['amount'] = (string)$value;
}
public function transactionJournal(): BelongsTo
@@ -61,14 +64,7 @@ class PiggyBankEvent extends Model
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function piggyBankId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (string)$value,
);
}
@@ -82,4 +78,11 @@ class PiggyBankEvent extends Model
'native_amount' => 'string',
];
}
protected function piggyBankId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -23,10 +23,10 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -43,12 +43,48 @@ class PiggyBankRepetition extends Model
return $this->belongsTo(PiggyBank::class);
}
/**
* @param mixed $value
*/
public function setCurrentAmountAttribute($value): void
{
$this->attributes['current_amount'] = (string)$value;
}
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start_date' => SeparateTimezoneCaster::class,
'target_date' => SeparateTimezoneCaster::class,
'virtual_balance' => 'string',
];
}
/**
* Get the amount
*/
protected function currentAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
#[Scope]
protected function onDates(EloquentBuilder $query, Carbon $start, Carbon $target): EloquentBuilder
{
return $query->where('start_date', $start->format('Y-m-d'))->where('target_date', $target->format('Y-m-d'));
}
protected function piggyBankId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
/**
* @return EloquentBuilder
*/
@@ -69,40 +105,4 @@ class PiggyBankRepetition extends Model
)
;
}
/**
* @param mixed $value
*/
public function setCurrentAmountAttribute($value): void
{
$this->attributes['current_amount'] = (string) $value;
}
/**
* Get the amount
*/
protected function currentAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function piggyBankId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'start_date' => SeparateTimezoneCaster::class,
'target_date' => SeparateTimezoneCaster::class,
'virtual_balance' => 'string',
];
}
}

View File

@@ -50,7 +50,7 @@ class Preference extends Model
// some preferences do not have an administration ID.
// some need it, to make sure the correct one is selected.
$userGroupId = (int) $user->user_group_id;
$userGroupId = (int)$user->user_group_id;
$userGroupId = 0 === $userGroupId ? null : $userGroupId;
/** @var null|Preference $preference */
@@ -67,7 +67,7 @@ class Preference extends Model
// try again with ID, but this time don't care about the preferred user_group_id
if (null === $preference) {
$preference = $user->preferences()->where('id', (int) $value)->first();
$preference = $user->preferences()->where('id', (int)$value)->first();
}
if (null !== $preference) {
/** @var Preference $preference */
@@ -78,7 +78,7 @@ class Preference extends Model
$preference = new self();
$preference->name = $value;
$preference->data = $default[$value];
$preference->user_id = (int) $user->id;
$preference->user_id = (int)$user->id;
$preference->user_group_id = in_array($value, $items, true) ? $userGroupId : null;
$preference->save();

View File

@@ -25,9 +25,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Handlers\Observer\RecurrenceObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -36,6 +38,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([RecurrenceObserver::class])]
class Recurrence extends Model
{
use ReturnsIntegerIdTrait;
@@ -55,7 +58,7 @@ class Recurrence extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$recurrenceId = (int) $value;
$recurrenceId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -113,13 +116,6 @@ class Recurrence extends Model
return $this->belongsTo(TransactionType::class);
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -139,4 +135,11 @@ class Recurrence extends Model
'user_group_id' => 'integer',
];
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -44,13 +44,6 @@ class RecurrenceMeta extends Model
return $this->belongsTo(Recurrence::class);
}
protected function recurrenceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -61,4 +54,11 @@ class RecurrenceMeta extends Model
'value' => 'string',
];
}
protected function recurrenceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -36,16 +36,20 @@ class RecurrenceRepetition extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int WEEKEND_DO_NOTHING = 1;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int WEEKEND_SKIP_CREATION = 2;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int WEEKEND_TO_FRIDAY = 3;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const int WEEKEND_TO_MONDAY = 4;
protected $casts
@@ -78,21 +82,21 @@ class RecurrenceRepetition extends Model
protected function recurrenceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
protected function repetitionSkip(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
protected function weekend(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -24,13 +24,16 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\RecurrenceTransactionObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
#[ObservedBy([RecurrenceTransactionObserver::class])]
class RecurrenceTransaction extends Model
{
use ReturnsIntegerIdTrait;
@@ -88,49 +91,7 @@ class RecurrenceTransaction extends Model
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function destinationId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function foreignAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function recurrenceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function sourceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function userId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (string)$value,
);
}
@@ -145,4 +106,46 @@ class RecurrenceTransaction extends Model
'description' => 'string',
];
}
protected function destinationId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function foreignAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
protected function recurrenceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function sourceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function transactionCurrencyId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function userId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -44,13 +44,6 @@ class RecurrenceTransactionMeta extends Model
return $this->belongsTo(RecurrenceTransaction::class, 'rt_id');
}
protected function rtId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -61,4 +54,11 @@ class RecurrenceTransactionMeta extends Model
'value' => 'string',
];
}
protected function rtId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -23,9 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\RuleObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -33,6 +35,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([RuleObserver::class])]
class Rule extends Model
{
use ReturnsIntegerIdTrait;
@@ -49,7 +52,7 @@ class Rule extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$ruleId = (int) $value;
$ruleId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -84,30 +87,11 @@ class Rule extends Model
return $this->hasMany(RuleTrigger::class);
}
protected function description(): Attribute
{
return Attribute::make(set: fn ($value) => ['description' => e($value)]);
}
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function ruleGroupId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -123,4 +107,23 @@ class Rule extends Model
'user_group_id' => 'integer',
];
}
protected function description(): Attribute
{
return Attribute::make(set: fn ($value) => ['description' => e($value)]);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function ruleGroupId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -42,7 +42,7 @@ class RuleAction extends Model
if (false === config('firefly.feature_flags.expression_engine')) {
Log::debug('Expression engine is disabled, returning action value as string.');
return (string) $this->action_value;
return (string)$this->action_value;
}
if (true === config('firefly.feature_flags.expression_engine') && str_starts_with($this->action_value, '\=')) {
// return literal string.
@@ -54,7 +54,7 @@ class RuleAction extends Model
$result = $expr->evaluate($journal);
} catch (SyntaxError $e) {
Log::error(sprintf('Expression engine failed to evaluate expression "%s" with error "%s".', $this->action_value, $e->getMessage()));
$result = (string) $this->action_value;
$result = (string)$this->action_value;
}
Log::debug(sprintf('Expression engine is enabled, result of expression "%s" is "%s".', $this->action_value, $result));
@@ -66,20 +66,6 @@ class RuleAction extends Model
return $this->belongsTo(Rule::class);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function ruleId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -90,4 +76,18 @@ class RuleAction extends Model
'stop_processing' => 'boolean',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function ruleId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -23,9 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\RuleGroupObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -33,6 +35,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([RuleGroupObserver::class])]
class RuleGroup extends Model
{
use ReturnsIntegerIdTrait;
@@ -49,7 +52,7 @@ class RuleGroup extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$ruleGroupId = (int) $value;
$ruleGroupId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -74,13 +77,6 @@ class RuleGroup extends Model
return $this->hasMany(Rule::class);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -94,4 +90,11 @@ class RuleGroup extends Model
'user_group_id' => 'integer',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -39,20 +39,6 @@ class RuleTrigger extends Model
return $this->belongsTo(Rule::class);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function ruleId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -63,4 +49,18 @@ class RuleTrigger extends Model
'stop_processing' => 'boolean',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function ruleId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -24,9 +24,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Handlers\Observer\TagObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -34,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([TagObserver::class])]
class Tag extends Model
{
use ReturnsIntegerIdTrait;
@@ -42,7 +45,7 @@ class Tag extends Model
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tag_mode'];
protected $hidden = ['zoomLevel', 'latitude', 'longitude'];
protected $hidden = ['zoomLevel', 'zoom_level', 'latitude', 'longitude'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
@@ -52,7 +55,7 @@ class Tag extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$tagId = (int) $value;
$tagId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -101,4 +104,9 @@ class Tag extends Model
'user_group_id' => 'integer',
];
}
public function primaryPeriodStatistics(): MorphMany
{
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
}
}

View File

@@ -23,9 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use FireflyIII\Handlers\Observer\TransactionObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -34,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
#[ObservedBy([TransactionObserver::class])]
class Transaction extends Model
{
use HasFactory;
@@ -89,6 +92,31 @@ class Transaction extends Model
return $this->belongsTo(TransactionCurrency::class, 'foreign_currency_id');
}
/**
* @param mixed $value
*/
public function setAmountAttribute($value): void
{
$this->attributes['amount'] = (string)$value;
}
public function transactionCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class);
}
public function transactionJournal(): BelongsTo
{
return $this->belongsTo(TransactionJournal::class);
}
protected function accountId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
/**
* Check for transactions AFTER a specified date.
*/
@@ -117,6 +145,23 @@ class Transaction extends Model
return false;
}
/**
* Get the amount
*/
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
protected function balanceDirty(): Attribute
{
return Attribute::make(
get: static fn ($value) => 1 === (int)$value,
);
}
/**
* Check for transactions BEFORE the specified date.
*/
@@ -129,78 +174,6 @@ class Transaction extends Model
$query->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'));
}
#[Scope]
protected function transactionTypes(Builder $query, array $types): void
{
if (!self::isJoined($query, 'transaction_journals')) {
$query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
}
if (!self::isJoined($query, 'transaction_types')) {
$query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
}
$query->whereIn('transaction_types.type', $types);
}
/**
* @param mixed $value
*/
public function setAmountAttribute($value): void
{
$this->attributes['amount'] = (string) $value;
}
public function transactionCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class);
}
public function transactionJournal(): BelongsTo
{
return $this->belongsTo(TransactionJournal::class);
}
protected function accountId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
/**
* Get the amount
*/
protected function amount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function balanceDirty(): Attribute
{
return Attribute::make(
get: static fn ($value) => 1 === (int) $value,
);
}
/**
* Get the foreign amount
*/
protected function foreignAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string) $value,
);
}
protected function transactionJournalId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -221,4 +194,34 @@ class Transaction extends Model
'native_foreign_amount' => 'string',
];
}
/**
* Get the foreign amount
*/
protected function foreignAmount(): Attribute
{
return Attribute::make(
get: static fn ($value) => (string)$value,
);
}
protected function transactionJournalId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
#[Scope]
protected function transactionTypes(Builder $query, array $types): void
{
if (!self::isJoined($query, 'transaction_journals')) {
$query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
}
if (!self::isJoined($query, 'transaction_types')) {
$query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
}
$query->whereIn('transaction_types.type', $types);
}
}

View File

@@ -50,7 +50,7 @@ class TransactionCurrency extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$currencyId = (int) $value;
$currencyId = (int)$value;
$currency = self::find($currencyId);
if (null !== $currency) {
$currency->refreshForUser(auth()->user());
@@ -101,13 +101,6 @@ class TransactionCurrency extends Model
return $this->belongsToMany(User::class)->withTimestamps()->withPivot('user_default');
}
protected function decimalPlaces(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -118,4 +111,11 @@ class TransactionCurrency extends Model
'enabled' => 'bool',
];
}
protected function decimalPlaces(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -23,15 +23,18 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\TransactionGroupObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([TransactionGroupObserver::class])]
class TransactionGroup extends Model
{
use ReturnsIntegerIdTrait;
@@ -49,7 +52,7 @@ class TransactionGroup extends Model
{
app('log')->debug(sprintf('Now in %s("%s")', __METHOD__, $value));
if (auth()->check()) {
$groupId = (int) $value;
$groupId = (int)$value;
/** @var User $user */
$user = auth()->user();

View File

@@ -23,13 +23,15 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Carbon\Carbon;
use FireflyIII\Casts\SeparateTimezoneCaster;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Handlers\Observer\TransactionJournalObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -46,6 +48,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method EloquentBuilder|static after()
* @method static EloquentBuilder|static query()
*/
#[ObservedBy([TransactionJournalObserver::class])]
class TransactionJournal extends Model
{
use HasFactory;
@@ -78,7 +81,7 @@ class TransactionJournal extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$journalId = (int) $value;
$journalId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -165,32 +168,6 @@ class TransactionJournal extends Model
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'));
}
#[Scope]
protected function transactionTypes(EloquentBuilder $query, array $types): void
{
if (!self::isJoined($query, 'transaction_types')) {
$query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
}
if (0 !== count($types)) {
$query->whereIn('transaction_types.type', $types);
}
}
/**
* Checks if tables are joined.
*/
public static function isJoined(EloquentBuilder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
public function sourceJournalLinks(): HasMany
{
return $this->hasMany(TransactionJournalLink::class, 'source_id');
@@ -231,20 +208,6 @@ class TransactionJournal extends Model
return $this->belongsTo(UserGroup::class);
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -263,4 +226,44 @@ class TransactionJournal extends Model
'user_group_id' => 'integer',
];
}
protected function order(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function transactionTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
#[Scope]
protected function transactionTypes(EloquentBuilder $query, array $types): void
{
if (!self::isJoined($query, 'transaction_types')) {
$query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
}
if (0 !== count($types)) {
$query->whereIn('transaction_types.type', $types);
}
}
/**
* Checks if tables are joined.
*/
public static function isJoined(EloquentBuilder $query, string $table): bool
{
$joins = $query->getQuery()->joins;
foreach ($joins as $join) {
if ($join->table === $table) {
return true;
}
}
return false;
}
}

View File

@@ -44,7 +44,7 @@ class TransactionJournalLink extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$linkId = (int) $value;
$linkId = (int)$value;
$link = self::where('journal_links.id', $linkId)
->leftJoin('transaction_journals as t_a', 't_a.id', '=', 'source_id')
->leftJoin('transaction_journals as t_b', 't_b.id', '=', 'destination_id')
@@ -83,27 +83,6 @@ class TransactionJournalLink extends Model
return $this->belongsTo(TransactionJournal::class, 'source_id');
}
protected function destinationId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function linkTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function sourceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -111,4 +90,25 @@ class TransactionJournalLink extends Model
'updated_at' => 'datetime',
];
}
protected function destinationId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function linkTypeId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
protected function sourceId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -41,27 +41,11 @@ class TransactionJournalMeta extends Model
protected $table = 'journal_meta';
protected function data(): Attribute
{
return Attribute::make(get: fn ($value) => json_decode((string) $value, false), set: function ($value) {
$data = json_encode($value);
return ['data' => $data, 'hash' => hash('sha256', $data)];
});
}
public function transactionJournal(): BelongsTo
{
return $this->belongsTo(TransactionJournal::class);
}
protected function transactionJournalId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -70,4 +54,20 @@ class TransactionJournalMeta extends Model
'deleted_at' => 'datetime',
];
}
protected function data(): Attribute
{
return Attribute::make(get: fn ($value) => json_decode((string)$value, false), set: function ($value) {
$data = json_encode($value);
return ['data' => $data, 'hash' => hash('sha256', $data)];
});
}
protected function transactionJournalId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -36,25 +36,32 @@ class TransactionType extends Model
use ReturnsIntegerIdTrait;
use SoftDeletes;
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string DEPOSIT = 'Deposit';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string INVALID = 'Invalid';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string LIABILITY_CREDIT = 'Liability credit';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string OPENING_BALANCE = 'Opening balance';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string RECONCILIATION = 'Reconciliation';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string TRANSFER = 'Transfer';
#[Deprecated] /** @deprecated */
#[Deprecated]
/** @deprecated */
public const string WITHDRAWAL = 'Withdrawal';
protected $casts

View File

@@ -47,7 +47,7 @@ class UserGroup extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$userGroupId = (int) $value;
$userGroupId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -76,6 +76,14 @@ class UserGroup extends Model
return $this->hasMany(Account::class);
}
/**
* Link to accounts.
*/
public function periodStatistics(): HasMany
{
return $this->hasMany(PeriodStatistic::class);
}
/**
* Link to attachments.
*/

View File

@@ -27,9 +27,11 @@ namespace FireflyIII\Models;
use FireflyIII\Enums\WebhookDelivery as WebhookDeliveryEnum;
use FireflyIII\Enums\WebhookResponse as WebhookResponseEnum;
use FireflyIII\Enums\WebhookTrigger as WebhookTriggerEnum;
use FireflyIII\Handlers\Observer\WebhookObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -37,6 +39,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([WebhookObserver::class])]
class Webhook extends Model
{
use ReturnsIntegerIdTrait;
@@ -151,16 +154,16 @@ class Webhook extends Model
return $this->belongsTo(User::class);
}
public function webhookMessages(): HasMany
{
return $this->hasMany(WebhookMessage::class);
}
public function webhookDeliveries(): BelongsToMany
{
return $this->belongsToMany(WebhookDelivery::class);
}
public function webhookMessages(): HasMany
{
return $this->hasMany(WebhookMessage::class);
}
public function webhookResponses(): BelongsToMany
{
return $this->belongsToMany(WebhookResponse::class);

View File

@@ -45,7 +45,7 @@ class WebhookAttempt extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$attemptId = (int) $value;
$attemptId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -68,7 +68,7 @@ class WebhookAttempt extends Model
protected function webhookMessageId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -41,7 +41,7 @@ class WebhookDelivery extends Model
protected function key(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -24,14 +24,17 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Handlers\Observer\WebhookMessageObserver;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
#[ObservedBy([WebhookMessageObserver::class])]
class WebhookMessage extends Model
{
use ReturnsIntegerIdTrait;
@@ -44,7 +47,7 @@ class WebhookMessage extends Model
public static function routeBinder(string $value): self
{
if (auth()->check()) {
$messageId = (int) $value;
$messageId = (int)$value;
/** @var User $user */
$user = auth()->user();
@@ -69,23 +72,6 @@ class WebhookMessage extends Model
return $this->hasMany(WebhookAttempt::class);
}
/**
* Get the amount
*/
protected function sent(): Attribute
{
return Attribute::make(
get: static fn ($value) => (bool) $value,
);
}
protected function webhookId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
);
}
protected function casts(): array
{
return [
@@ -96,4 +82,21 @@ class WebhookMessage extends Model
'logs' => 'json',
];
}
/**
* Get the amount
*/
protected function sent(): Attribute
{
return Attribute::make(
get: static fn ($value) => (bool)$value,
);
}
protected function webhookId(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -41,7 +41,7 @@ class WebhookResponse extends Model
protected function key(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -41,7 +41,7 @@ class WebhookTrigger extends Model
protected function key(): Attribute
{
return Attribute::make(
get: static fn ($value) => (int) $value,
get: static fn ($value) => (int)$value,
);
}
}

View File

@@ -57,46 +57,6 @@ use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Handlers\Observer\AccountObserver;
use FireflyIII\Handlers\Observer\AttachmentObserver;
use FireflyIII\Handlers\Observer\AutoBudgetObserver;
use FireflyIII\Handlers\Observer\AvailableBudgetObserver;
use FireflyIII\Handlers\Observer\BillObserver;
use FireflyIII\Handlers\Observer\BudgetLimitObserver;
use FireflyIII\Handlers\Observer\BudgetObserver;
use FireflyIII\Handlers\Observer\CategoryObserver;
use FireflyIII\Handlers\Observer\PiggyBankEventObserver;
use FireflyIII\Handlers\Observer\PiggyBankObserver;
use FireflyIII\Handlers\Observer\RecurrenceObserver;
use FireflyIII\Handlers\Observer\RecurrenceTransactionObserver;
use FireflyIII\Handlers\Observer\RuleGroupObserver;
use FireflyIII\Handlers\Observer\RuleObserver;
use FireflyIII\Handlers\Observer\TagObserver;
use FireflyIII\Handlers\Observer\TransactionGroupObserver;
use FireflyIII\Handlers\Observer\TransactionJournalObserver;
use FireflyIII\Handlers\Observer\TransactionObserver;
use FireflyIII\Handlers\Observer\WebhookMessageObserver;
use FireflyIII\Handlers\Observer\WebhookObserver;
use FireflyIII\Models\Account;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Category;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Webhook;
use FireflyIII\Models\WebhookMessage;
use Illuminate\Auth\Events\Login;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Passport\Events\AccessTokenCreated;
@@ -256,32 +216,5 @@ class EventServiceProvider extends ServiceProvider
* Register any events for your application.
*/
#[Override]
public function boot(): void
{
$this->registerObservers();
}
private function registerObservers(): void
{
Attachment::observe(new AttachmentObserver());
Account::observe(new AccountObserver());
AutoBudget::observe(new AutoBudgetObserver());
AvailableBudget::observe(new AvailableBudgetObserver());
Bill::observe(new BillObserver());
Budget::observe(new BudgetObserver());
BudgetLimit::observe(new BudgetLimitObserver());
Category::observe(new CategoryObserver());
PiggyBank::observe(new PiggyBankObserver());
PiggyBankEvent::observe(new PiggyBankEventObserver());
Recurrence::observe(new RecurrenceObserver());
RecurrenceTransaction::observe(new RecurrenceTransactionObserver());
Rule::observe(new RuleObserver());
RuleGroup::observe(new RuleGroupObserver());
Tag::observe(new TagObserver());
Transaction::observe(new TransactionObserver());
TransactionJournal::observe(new TransactionJournalObserver());
TransactionGroup::observe(new TransactionGroupObserver());
Webhook::observe(new WebhookObserver());
WebhookMessage::observe(new WebhookMessageObserver());
}
public function boot(): void {}
}

View File

@@ -43,6 +43,8 @@ use FireflyIII\Repositories\AuditLogEntry\ALERepository;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepository;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepository;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepository;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use FireflyIII\Repositories\User\UserRepository;
@@ -174,6 +176,18 @@ class FireflyServiceProvider extends ServiceProvider
}
);
$this->app->bind(
static function (Application $app): PeriodStatisticRepositoryInterface {
/** @var PeriodStatisticRepository $repository */
$repository = app(PeriodStatisticRepository::class);
if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth)
$repository->setUser(auth()->user());
}
return $repository;
}
);
$this->app->bind(
static function (Application $app): WebhookRepositoryInterface {
/** @var WebhookRepository $repository */

View File

@@ -45,6 +45,7 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Override;
@@ -150,18 +151,18 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
$query->whereIn('account_types.type', $types);
}
app('log')->debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]);
Log::debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]);
$query->where('accounts.name', $name);
/** @var null|Account $account */
$account = $query->first(['accounts.*']);
if (null === $account) {
app('log')->debug(sprintf('There is no account with name "%s" of types', $name), $types);
Log::debug(sprintf('There is no account with name "%s" of types', $name), $types);
return null;
}
app('log')->debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
return $account;
}
@@ -465,14 +466,14 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
];
if (array_key_exists(ucfirst($type), $sets)) {
$order = (int) $this->getAccountsByType($sets[ucfirst($type)])->max('order');
app('log')->debug(sprintf('Return max order of "%s" set: %d', $type, $order));
Log::debug(sprintf('Return max order of "%s" set: %d', $type, $order));
return $order;
}
$specials = [AccountTypeEnum::CASH->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::RECONCILIATION->value];
$order = (int) $this->getAccountsByType($specials)->max('order');
app('log')->debug(sprintf('Return max order of "%s" set (specials!): %d', $type, $order));
Log::debug(sprintf('Return max order of "%s" set (specials!): %d', $type, $order));
return $order;
}
@@ -545,6 +546,8 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
#[Override]
public function periodCollection(Account $account, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('periodCollection(#%d, %s, %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
return $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
@@ -599,7 +602,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
continue;
}
if ($index !== (int) $account->order) {
app('log')->debug(sprintf('Account #%d ("%s"): order should %d be but is %d.', $account->id, $account->name, $index, $account->order));
Log::debug(sprintf('Account #%d ("%s"): order should %d be but is %d.', $account->id, $account->name, $index, $account->order));
$account->order = $index;
$account->save();
}

View File

@@ -31,8 +31,10 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -271,7 +273,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
if (null === $currency) {
$currency = app('amount')->getPrimaryCurrencyByUserGroup($this->user->userGroup);
$currency = Amount::getPrimaryCurrencyByUserGroup($this->user->userGroup);
}
$currency->enabled = true;
$currency->save();
@@ -293,7 +295,11 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
if (null !== $limit) {
throw new FireflyException('200027: Budget limit already exists.');
}
app('log')->debug('No existing budget limit, create a new one');
Log::debug('No existing budget limit, create a new one');
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
$singleton->setPreference('fire_webhooks_bl_store', $data['fire_webhooks'] ?? true);
// or create one and return it.
$limit = new BudgetLimit();
@@ -309,7 +315,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
$this->setNoteText($limit, $noteText);
}
app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount']));
Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount']));
return $limit;
}
@@ -369,11 +375,15 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
}
// catch unexpected null:
if (null === $currency) {
$currency = $budgetLimit->transactionCurrency ?? app('amount')->getPrimaryCurrencyByUserGroup($this->user->userGroup);
$currency = $budgetLimit->transactionCurrency ?? Amount::getPrimaryCurrencyByUserGroup($this->user->userGroup);
}
$currency->enabled = true;
$currency->save();
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
$singleton->setPreference('fire_webhooks_bl_update', $data['fire_webhooks'] ?? true);
$budgetLimit->transaction_currency_id = $currency->id;
$budgetLimit->save();
@@ -385,63 +395,63 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
return $budgetLimit;
}
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{
// count the limits:
$limits = $budget->budgetlimits()
->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'))
->count('budget_limits.*')
;
app('log')->debug(sprintf('Found %d budget limits.', $limits));
// there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits()
->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'))
->first(['budget_limits.*'])
;
// if more than 1 limit found, delete the others:
if ($limits > 1 && null !== $limit) {
app('log')->debug(sprintf('Found more than 1, delete all except #%d', $limit->id));
$budget->budgetlimits()
->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.id', '!=', $limit->id)->delete()
;
}
// delete if amount is zero.
// Returns 0 if the two operands are equal,
// 1 if the left_operand is larger than the right_operand, -1 otherwise.
if (null !== $limit && bccomp($amount, '0') <= 0) {
app('log')->debug(sprintf('%s is zero, delete budget limit #%d', $amount, $limit->id));
$limit->delete();
return null;
}
// update if exists:
if (null !== $limit) {
app('log')->debug(sprintf('Existing budget limit is #%d, update this to amount %s', $limit->id, $amount));
$limit->amount = $amount;
$limit->save();
return $limit;
}
app('log')->debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e');
$limit->end_date = $end->startOfDay();
$limit->end_date_tz = $end->format('e');
$limit->amount = $amount;
$limit->save();
app('log')->debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount));
return $limit;
}
// public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
// {
// // count the limits:
// $limits = $budget->budgetlimits()
// ->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'))
// ->count('budget_limits.*')
// ;
// Log::debug(sprintf('Found %d budget limits.', $limits));
//
// // there might be a budget limit for these dates:
// /** @var null|BudgetLimit $limit */
// $limit = $budget->budgetlimits()
// ->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'))
// ->first(['budget_limits.*'])
// ;
//
// // if more than 1 limit found, delete the others:
// if ($limits > 1 && null !== $limit) {
// Log::debug(sprintf('Found more than 1, delete all except #%d', $limit->id));
// $budget->budgetlimits()
// ->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.id', '!=', $limit->id)->delete()
// ;
// }
//
// // delete if amount is zero.
// // Returns 0 if the two operands are equal,
// // 1 if the left_operand is larger than the right_operand, -1 otherwise.
// if (null !== $limit && bccomp($amount, '0') <= 0) {
// Log::debug(sprintf('%s is zero, delete budget limit #%d', $amount, $limit->id));
// $limit->delete();
//
// return null;
// }
// // update if exists:
// if (null !== $limit) {
// Log::debug(sprintf('Existing budget limit is #%d, update this to amount %s', $limit->id, $amount));
// $limit->amount = $amount;
// $limit->save();
//
// return $limit;
// }
// Log::debug('No existing budget limit, create a new one');
// // or create one and return it.
// $limit = new BudgetLimit();
// $limit->budget()->associate($budget);
// $limit->start_date = $start->startOfDay();
// $limit->start_date_tz = $start->format('e');
// $limit->end_date = $end->startOfDay();
// $limit->end_date_tz = $end->format('e');
// $limit->amount = $amount;
// $limit->save();
// Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount));
//
// return $limit;
// }
}

View File

@@ -81,5 +81,5 @@ interface BudgetLimitRepositoryInterface
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit;
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit;
// public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit;
}

View File

@@ -44,6 +44,7 @@ use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
@@ -85,7 +86,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function budgetedInPeriod(Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in budgetedInPeriod("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d')));
Log::debug(sprintf('Now in budgetedInPeriod("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d')));
$return = [];
/** @var BudgetLimitRepository $limitRepository */
@@ -97,12 +98,12 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
/** @var Budget $budget */
foreach ($budgets as $budget) {
app('log')->debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name));
Log::debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name));
$limits = $limitRepository->getBudgetLimits($budget, $start, $end);
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
app('log')->debug(sprintf('Budget limit #%d', $limit->id));
Log::debug(sprintf('Budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency;
$rate = $converter->getCurrencyRate($currency, $primaryCurrency, $end);
$currencyCode = $currency->code;
@@ -124,7 +125,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)) {
$return[$currencyCode]['sum'] = bcadd($return[$currencyCode]['sum'], (string) $limit->amount);
$return[$currencyCode]['pc_sum'] = bcmul($rate, $return[$currencyCode]['sum']);
app('log')->debug(sprintf('Add full amount [1]: %s', $limit->amount));
Log::debug(sprintf('Add full amount [1]: %s', $limit->amount));
continue;
}
@@ -132,7 +133,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
if ($start->lte($limit->start_date) && $end->gte($limit->end_date)) {
$return[$currencyCode]['sum'] = bcadd($return[$currencyCode]['sum'], (string) $limit->amount);
$return[$currencyCode]['pc_sum'] = bcmul($rate, $return[$currencyCode]['sum']);
app('log')->debug(sprintf('Add full amount [2]: %s', $limit->amount));
Log::debug(sprintf('Add full amount [2]: %s', $limit->amount));
continue;
}
@@ -141,7 +142,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$amount = bcmul(bcdiv((string) $limit->amount, (string) $total), (string) $days);
$return[$currencyCode]['sum'] = bcadd($return[$currencyCode]['sum'], $amount);
$return[$currencyCode]['pc_sum'] = bcmul($rate, $return[$currencyCode]['sum']);
app('log')->debug(
Log::debug(
sprintf(
'Amount per day: %s (%s over %d days). Total amount for %d days: %s',
bcdiv((string) $limit->amount, (string) $total),
@@ -202,19 +203,19 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in budgetedInPeriod(#%d, "%s", "%s")', $budget->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
Log::debug(sprintf('Now in budgetedInPeriod(#%d, "%s", "%s")', $budget->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$return = [];
/** @var BudgetLimitRepository $limitRepository */
$limitRepository = app(BudgetLimitRepository::class);
$limitRepository->setUser($this->user);
app('log')->debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name));
Log::debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name));
$limits = $limitRepository->getBudgetLimits($budget, $start, $end);
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
app('log')->debug(sprintf('Budget limit #%d', $limit->id));
Log::debug(sprintf('Budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency;
$return[$currency->id] ??= [
'id' => (string) $currency->id,
@@ -227,14 +228,14 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// same period
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)) {
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string) $limit->amount);
app('log')->debug(sprintf('Add full amount [1]: %s', $limit->amount));
Log::debug(sprintf('Add full amount [1]: %s', $limit->amount));
continue;
}
// limit is inside of date range
if ($start->lte($limit->start_date) && $end->gte($limit->end_date)) {
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string) $limit->amount);
app('log')->debug(sprintf('Add full amount [2]: %s', $limit->amount));
Log::debug(sprintf('Add full amount [2]: %s', $limit->amount));
continue;
}
@@ -242,7 +243,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$days = $this->daysInOverlap($limit, $start, $end);
$amount = bcmul(bcdiv((string) $limit->amount, (string) $total), (string) $days);
$return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount);
app('log')->debug(
Log::debug(
sprintf(
'Amount per day: %s (%s over %d days). Total amount for %d days: %s',
bcdiv((string) $limit->amount, (string) $total),
@@ -282,7 +283,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
*/
public function update(Budget $budget, array $data): Budget
{
app('log')->debug('Now in update()');
Log::debug('Now in update()');
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
$singleton->setPreference('fire_webhooks_budget_update', $data['fire_webhooks'] ?? true);
$oldName = $budget->name;
if (array_key_exists('name', $data)) {
@@ -330,13 +335,13 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
->where('rule_actions.action_value', $oldName)
->get(['rule_actions.*'])
;
app('log')->debug(sprintf('Found %d actions to update.', $actions->count()));
Log::debug(sprintf('Found %d actions to update.', $actions->count()));
/** @var RuleAction $action */
foreach ($actions as $action) {
$action->action_value = $newName;
$action->save();
app('log')->debug(sprintf('Updated action %d: %s', $action->id, $action->action_value));
Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value));
}
}
@@ -349,13 +354,13 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
->where('rule_triggers.trigger_value', $oldName)
->get(['rule_triggers.*'])
;
app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count()));
Log::debug(sprintf('Found %d triggers to update.', $triggers->count()));
/** @var RuleTrigger $trigger */
foreach ($triggers as $trigger) {
$trigger->trigger_value = $newName;
$trigger->save();
app('log')->debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value));
Log::debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value));
}
}
@@ -486,17 +491,17 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function findBudget(?int $budgetId, ?string $budgetName): ?Budget
{
app('log')->debug('Now in findBudget()');
app('log')->debug(sprintf('Searching for budget with ID #%d...', $budgetId));
Log::debug('Now in findBudget()');
Log::debug(sprintf('Searching for budget with ID #%d...', $budgetId));
$result = $this->find((int) $budgetId);
if (!$result instanceof Budget && null !== $budgetName && '' !== $budgetName) {
app('log')->debug(sprintf('Searching for budget with name %s...', $budgetName));
Log::debug(sprintf('Searching for budget with name %s...', $budgetName));
$result = $this->findByName($budgetName);
}
if ($result instanceof Budget) {
app('log')->debug(sprintf('Found budget #%d: %s', $result->id, $result->name));
Log::debug(sprintf('Found budget #%d: %s', $result->id, $result->name));
}
app('log')->debug(sprintf('Found result is null? %s', var_export(!$result instanceof Budget, true)));
Log::debug(sprintf('Found result is null? %s', var_export(!$result instanceof Budget, true)));
return $result;
}
@@ -593,7 +598,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function spentInPeriod(Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$start->startOfDay();
$end->endOfDay();
@@ -655,7 +660,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function spentInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$start->startOfDay();
$end->endOfDay();
@@ -724,6 +729,10 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
{
$order = $this->getMaxOrder();
// this is a lame trick to communicate with the observer.
$singleton = PreferencesSingleton::getInstance();
$singleton->setPreference('fire_webhooks_budget_create', $data['fire_webhooks'] ?? true);
try {
$newBudget = Budget::create(
[
@@ -735,8 +744,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
]
);
} catch (QueryException $e) {
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException('400002: Could not store budget.', 0, $e);
}

View File

@@ -314,7 +314,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
#[Override]
public function collectExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null): array
{
Log::debug(sprintf('Start of %s(date, date, array, array, "%s").', __METHOD__, $currency?->code));
Log::debug(sprintf('Start of %s(%s, %s, array, array, "%s").', __METHOD__, $start->toW3cString(), $end->toW3cString(), $currency?->code));
// this collector excludes all transfers TO liabilities (which are also withdrawals)
// because those expenses only become expenses once they move from the liability to the friend.
// 2024-12-24 disable the exclusion for now.

View File

@@ -358,4 +358,43 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
return $service->update($category, $data);
}
public function periodCollection(Category $category, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('periodCollection(#%d, %s, %s)', $category->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
return $category->transactionJournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', '=', 'transactions.foreign_currency_id')
->where('transaction_journals.date', '>=', $start)
->where('transaction_journals.date', '<=', $end)
->where('transactions.amount', '>', 0)
->get([
// currencies
'transaction_currencies.id as currency_id',
'transaction_currencies.code as currency_code',
'transaction_currencies.name as currency_name',
'transaction_currencies.symbol as currency_symbol',
'transaction_currencies.decimal_places as currency_decimal_places',
// foreign
'foreign_currencies.id as foreign_currency_id',
'foreign_currencies.code as foreign_currency_code',
'foreign_currencies.name as foreign_currency_name',
'foreign_currencies.symbol as foreign_currency_symbol',
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
// fields
'transaction_journals.date',
'transaction_types.type',
'transaction_journals.transaction_currency_id',
'transactions.amount',
'transactions.native_amount as pc_amount',
'transactions.foreign_amount',
])
->toArray()
;
}
}

View File

@@ -48,6 +48,8 @@ interface CategoryRepositoryInterface
public function categoryStartsWith(string $query, int $limit): Collection;
public function periodCollection(Category $category, Carbon $start, Carbon $end): array;
public function destroy(Category $category): bool;
/**

View File

@@ -510,9 +510,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$summarizer->setConvertToPrimary($convertToPrimary);
// filter $journals by range AND currency if it is present.
$expenses = array_filter($expenses, static function (array $expense) use ($category): bool {
return $expense['category_id'] === $category->id;
});
$expenses = array_filter($expenses, static fn (array $expense): bool => $expense['category_id'] === $category->id);
return $summarizer->groupByCurrencyId($expenses, $method, false);
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
/*
* PeriodStatisticRepository.php
* Copyright (c) 2025 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, UserGroupInterface
{
use UserGroupTrait;
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection
{
return $model->primaryPeriodStatistics()
->where('start', $start)
->where('end', $end)
->whereIn('type', $types)
->get();
}
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection
{
return $model->primaryPeriodStatistics()
->where('start', $start)
->where('end', $end)
->where('type', $type)
->get();
}
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic
{
$stat = new PeriodStatistic();
$stat->primaryStatable()->associate($model);
$stat->transaction_currency_id = $currencyId;
$stat->user_group_id = $this->getUserGroup()->id;
$stat->start = $start;
$stat->start_tz = $start->format('e');
$stat->end = $end;
$stat->end_tz = $end->format('e');
$stat->amount = $amount;
$stat->count = $count;
$stat->type = $type;
$stat->save();
Log::debug(sprintf(
'Saved #%d [currency #%d, Model %s #%d, %s to %s, %d, %s] as new statistic.',
$stat->id,
$model::class,
$model->id,
$stat->transaction_currency_id,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
return $stat;
}
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection
{
return $model->primaryPeriodStatistics()->where('start', '>=', $start)->where('end', '<=', $end)->get();
}
public function deleteStatisticsForModel(Model $model, Carbon $date): void
{
$model->primaryPeriodStatistics()->where('start', '<=', $date)->where('end', '>=', $date)->delete();
}
#[\Override]
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection
{
return $this->userGroup->periodStatistics()
->where('type', 'LIKE', sprintf('%s%%', $prefix))
->where('start', '>=', $start)->where('end', '<=', $end)->get();
}
#[\Override]
public function savePrefixedStatistic(string $prefix, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic
{
$stat = new PeriodStatistic();
$stat->transaction_currency_id = $currencyId;
$stat->user_group_id = $this->getUserGroup()->id;
$stat->start = $start;
$stat->start_tz = $start->format('e');
$stat->end = $end;
$stat->end_tz = $end->format('e');
$stat->amount = $amount;
$stat->count = $count;
$stat->type = sprintf('%s_%s',$prefix, $type);
$stat->save();
Log::debug(sprintf(
'Saved #%d [currency #%d, type "%s", %s to %s, %d, %s] as new statistic.',
$stat->id,
$stat->transaction_currency_id,
$stat->type,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
return $stat;
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* PeriodStatisticRepositoryInterface.php
* Copyright (c) 2025 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
interface PeriodStatisticRepositoryInterface
{
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection;
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection;
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
public function savePrefixedStatistic(string $prefix, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection;
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection;
public function deleteStatisticsForModel(Model $model, Carbon $date): void;
}

View File

@@ -67,7 +67,7 @@ trait ModifiesPiggyBanks
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcsub($currentAmount, $amount);
$pivot->current_amount = bcsub((string) $currentAmount, $amount);
$pivot->native_current_amount = null;
// also update native_current_amount.
@@ -90,7 +90,7 @@ trait ModifiesPiggyBanks
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcadd($currentAmount, $amount);
$pivot->current_amount = bcadd((string) $currentAmount, $amount);
$pivot->native_current_amount = null;
// also update native_current_amount.
@@ -122,13 +122,13 @@ trait ModifiesPiggyBanks
if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
$leftToSave = bcsub($piggyBank->target_amount, (string) $savedSoFar);
$maxAmount = 1 === bccomp((string) $leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
Log::debug(sprintf('Left to save: %s', $leftToSave));
Log::debug(sprintf('Maximum amount: %s', $maxAmount));
}
$compare = bccomp($amount, $maxAmount);
$compare = bccomp($amount, (string) $maxAmount);
$result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
@@ -140,7 +140,7 @@ trait ModifiesPiggyBanks
{
$savedSoFar = $this->getCurrentAmount($piggyBank, $account);
return bccomp($amount, $savedSoFar) <= 0;
return bccomp($amount, (string) $savedSoFar) <= 0;
}
/**
@@ -234,9 +234,9 @@ trait ModifiesPiggyBanks
// if the piggy bank is now smaller than the sum of the money saved,
// remove money from all accounts until the piggy bank is the right amount.
$currentAmount = $this->getCurrentAmount($piggyBank);
if (1 === bccomp($currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
if (1 === bccomp((string) $currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
Log::debug(sprintf('Current amount is %s, target amount is %s', $currentAmount, $piggyBank->target_amount));
$difference = bcsub((string)$piggyBank->target_amount, $currentAmount);
$difference = bcsub((string)$piggyBank->target_amount, (string) $currentAmount);
// an amount will be removed, create "negative" event:
// Log::debug(sprintf('ChangedAmount: is triggered with difference "%s"', $difference));

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Tag;
use Override;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Factory\TagFactory;
@@ -379,4 +380,44 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
/** @var null|Location */
return $tag->locations()->first();
}
#[Override]
public function periodCollection(Tag $tag, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('periodCollection(#%d, %s, %s)', $tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
return $tag->transactionJournals()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', '=', 'transactions.foreign_currency_id')
->where('transaction_journals.date', '>=', $start)
->where('transaction_journals.date', '<=', $end)
->where('transactions.amount', '>', 0)
->get([
// currencies
'transaction_currencies.id as currency_id',
'transaction_currencies.code as currency_code',
'transaction_currencies.name as currency_name',
'transaction_currencies.symbol as currency_symbol',
'transaction_currencies.decimal_places as currency_decimal_places',
// foreign
'foreign_currencies.id as foreign_currency_id',
'foreign_currencies.code as foreign_currency_code',
'foreign_currencies.name as foreign_currency_name',
'foreign_currencies.symbol as foreign_currency_symbol',
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
// fields
'transaction_journals.date',
'transaction_types.type',
'transaction_journals.transaction_currency_id',
'transactions.amount',
'transactions.native_amount as pc_amount',
'transactions.foreign_amount',
])
->toArray()
;
}
}

View File

@@ -51,6 +51,8 @@ interface TagRepositoryInterface
*/
public function destroy(Tag $tag): bool;
public function periodCollection(Tag $tag, Carbon $start, Carbon $end): array;
/**
* Destroy all tags.
*/

View File

@@ -370,8 +370,11 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
public function getTagObjects(int $journalId): Collection
{
/** @var TransactionJournal $journal */
/** @var null|TransactionJournal $journal */
$journal = $this->user->transactionJournals()->find($journalId);
if (null === $journal) {
return new Collection();
}
return $journal->tags()->whereNull('deleted_at')->get();
}

Some files were not shown because too many files have changed in this diff Show More