diff --git a/app/Api/V1/Controllers/Models/BudgetLimit/StoreController.php b/app/Api/V1/Controllers/Models/BudgetLimit/StoreController.php index c3b88d69f2..910502662c 100644 --- a/app/Api/V1/Controllers/Models/BudgetLimit/StoreController.php +++ b/app/Api/V1/Controllers/Models/BudgetLimit/StoreController.php @@ -67,26 +67,27 @@ class StoreController extends Controller */ public function store(StoreRequest $request, Budget $budget): JsonResponse { - $data = $request->getAll(); - $data['start_date'] = $data['start']; - $data['end_date'] = $data['end']; - $data['budget_id'] = $budget->id; + $data = $request->getAll(); + $data['start_date'] = $data['start']; + $data['end_date'] = $data['end']; + $data['fire_webhooks'] = $data['fire_webhooks'] ?? true; + $data['budget_id'] = $budget->id; - $budgetLimit = $this->blRepository->store($data); - $manager = $this->getManager(); + $budgetLimit = $this->blRepository->store($data); + $manager = $this->getManager(); // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new BudgetLimitEnrichment(); + $admin = auth()->user(); + $enrichment = new BudgetLimitEnrichment(); $enrichment->setUser($admin); - $budgetLimit = $enrichment->enrichSingle($budgetLimit); + $budgetLimit = $enrichment->enrichSingle($budgetLimit); /** @var BudgetLimitTransformer $transformer */ - $transformer = app(BudgetLimitTransformer::class); + $transformer = app(BudgetLimitTransformer::class); $transformer->setParameters($this->parameters); - $resource = new Item($budgetLimit, $transformer, 'budget_limits'); + $resource = new Item($budgetLimit, $transformer, 'budget_limits'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } diff --git a/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php b/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php index 48c77cf2a2..2afabc38f3 100644 --- a/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php +++ b/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php @@ -24,10 +24,18 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\BudgetLimit; +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionCurrencyFactory; +use FireflyIII\Models\Budget; +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 +57,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), ]; } @@ -64,6 +75,53 @@ class StoreRequest extends FormRequest '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()); + } + } } diff --git a/app/Handlers/Observer/BudgetLimitObserver.php b/app/Handlers/Observer/BudgetLimitObserver.php index ae6b1aac6a..c29b30dbde 100644 --- a/app/Handlers/Observer/BudgetLimitObserver.php +++ b/app/Handlers/Observer/BudgetLimitObserver.php @@ -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 diff --git a/app/Repositories/Budget/BudgetLimitRepository.php b/app/Repositories/Budget/BudgetLimitRepository.php index 8974613364..67378a92d4 100644 --- a/app/Repositories/Budget/BudgetLimitRepository.php +++ b/app/Repositories/Budget/BudgetLimitRepository.php @@ -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; } @@ -393,7 +399,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup ->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)); + Log::debug(sprintf('Found %d budget limits.', $limits)); // there might be a budget limit for these dates: /** @var null|BudgetLimit $limit */ @@ -405,7 +411,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup // 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)); + 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')) @@ -417,20 +423,20 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup // 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)); + 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)); + 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'); + Log::debug('No existing budget limit, create a new one'); // or create one and return it. $limit = new BudgetLimit(); $limit->budget()->associate($budget); @@ -440,7 +446,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup $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)); + Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $amount)); return $limit; } diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 9c9052b050..7c2134f5a2 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -24,6 +24,7 @@ declare(strict_types=1); return [ + 'limit_exists' => 'There is already a budget limit (amount) for this budget and currency in the given period.', 'invalid_sort_instruction' => 'The sort instruction is invalid for an object of type ":object".', 'invalid_sort_instruction_index' => 'The sort instruction at index #:index is invalid for an object of type ":object".', 'no_sort_instructions' => 'There are no sort instructions defined for an object of type ":object".',