From 1e9eb843c02728d72789d2fb71ceff2031f40a95 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 3 Jun 2018 19:14:03 +0200 Subject: [PATCH 001/134] Still not sure what's causing the empty strings to appear. --- app/Factory/TransactionFactory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index c9c787d092..5d1cf3ecef 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -56,6 +56,7 @@ class TransactionFactory $currencyId = $data['currency_id'] ?? null; $currencyId = isset($data['currency']) ? $data['currency']->id : $currencyId; if ('' === $data['amount']) { + Log::error('Empty string in data.', $data); throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); // @codeCoverageIgnore } if (null === $currencyId) { From 08d06cf4658e0a3b61731723ea9f1b183287978d Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 5 Jun 2018 07:57:18 +0200 Subject: [PATCH 002/134] Small updates to meta files [skip ci] --- .github/ISSUE_TEMPLATE/Bug_report.md | 2 +- .github/ISSUE_TEMPLATE/Custom.md | 14 ++++++++------ .github/ISSUE_TEMPLATE/Feature_request.md | 3 ++- .github/stale.yml | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 1312de943a..12ba47695e 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -13,7 +13,7 @@ I am running Firefly III version x.x.x What do you need to do to trigger this bug? **Extra info** -Please add extra info here, such as OS, browser, and the output from the `/debug`-page of your Firefly III installation (click the version at the bottom). +Please add extra info here, such as OS, browser, and the output from the /debug page of your Firefly III installation (click the version at the bottom). **Bonus points** Earn bonus points by: diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md index f943c935cb..0db426cdf3 100644 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -1,25 +1,27 @@ --- name: I have a question or a problem -about: Ask away +about: Ask away! --- I am running Firefly III version x.x.x -**Description of my issue:** +**Description** + **Steps to reproduce** +(if relevant of course) + -(please include if this problem also exists on the demo site: https://demo.firefly-iii.org/ ) **Extra info** - Please add extra info here, such as OS, browser, and the output from the `/debug`-page of your Firefly III installation (click the version at the bottom). + + **Bonus points** Earn bonus points by: -- Post a stacktrace from your log files - Add a screenshot -- Post nginx or Apache configuration \ No newline at end of file +- Replicate the problem on the demo site https://demo.firefly-iii.org/ \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 7dfae270ae..3bdd3d861e 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -8,7 +8,8 @@ about: Suggest an idea or feature for Firefly III Please describe your feature request: - I would like Firefly III to do X. -- What if you would add Y? +- What if you would add feature Y? +- Firefly III doesn't do Z. **Solution** Describe what your feature would add to Firefly III. diff --git a/.github/stale.yml b/.github/stale.yml index f9e5be7b89..5acff1e12e 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 30 +daysUntilStale: 14 # Number of days of inactivity before a stale Issue or Pull Request is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. From 073dedd4831cc0072f46e9e88fadd97a0e575e26 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 6 Jun 2018 05:54:24 +0200 Subject: [PATCH 003/134] Add debug info --- app/Factory/TransactionJournalFactory.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index fbee73ea96..d7c169e2e0 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -53,7 +53,7 @@ class TransactionJournalFactory $type = $this->findTransactionType($data['type']); $defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user); Log::debug(sprintf('Going to store a %s', $type->type)); - $journal = TransactionJournal::create( + $journal = TransactionJournal::create( [ 'user_id' => $data['user'], 'transaction_type_id' => $type->id, @@ -67,6 +67,10 @@ class TransactionJournalFactory ] ); + if (isset($data['transactions'][0]['amount']) && '' === $data['transactions'][0]['amount']) { + Log::error('Empty amount in data', $data); + } + // store basic transactions: /** @var TransactionFactory $factory */ $factory = app(TransactionFactory::class); @@ -95,7 +99,7 @@ class TransactionJournalFactory // store date meta fields (if present): $fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date', - 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash','importHashV2', 'external_id']; + 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash', 'importHashV2', 'external_id']; foreach ($fields as $field) { $this->storeMeta($journal, $data, $field); From 20044427b4e98c79eac06bb27fbf4cd2cc22ffa4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 6 Jun 2018 21:20:21 +0200 Subject: [PATCH 004/134] Use Guzzle, not Requests library. --- app/Helpers/Help/Help.php | 16 ++-- app/Services/Currency/FixerIO.php | 96 ------------------- app/Services/Currency/FixerIOv2.php | 28 ++++-- app/Services/Github/Request/GithubRequest.php | 2 +- app/Services/Github/Request/UpdateRequest.php | 22 +++-- app/Services/IP/IpifyOrg.php | 14 +-- app/Services/Password/PwndVerifier.php | 56 ----------- app/Services/Password/PwndVerifierV2.php | 20 ++-- .../Spectre/Request/SpectreRequest.php | 51 +++++----- 9 files changed, 90 insertions(+), 215 deletions(-) delete mode 100644 app/Services/Currency/FixerIO.php delete mode 100644 app/Services/Password/PwndVerifier.php diff --git a/app/Helpers/Help/Help.php b/app/Helpers/Help/Help.php index 7c9358e4bb..a9fcd1869a 100644 --- a/app/Helpers/Help/Help.php +++ b/app/Helpers/Help/Help.php @@ -24,9 +24,10 @@ namespace FireflyIII\Helpers\Help; use Cache; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use League\CommonMark\CommonMarkConverter; use Log; -use Requests; use Route; /** @@ -64,20 +65,21 @@ class Help implements HelpInterface { $uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route); Log::debug(sprintf('Trying to get %s...', $uri)); - $opt = ['useragent' => $this->userAgent]; + $opt = ['headers' => ['User-Agent' => $this->userAgent]]; $content = ''; try { - $result = Requests::get($uri, [], $opt); - } catch (Exception $e) { + $client = new Client; + $res = $client->request('GET', $uri, $opt); + } catch (GuzzleException|Exception $e) { Log::error($e); return ''; } - Log::debug(sprintf('Status code is %d', $result->status_code)); + Log::debug(sprintf('Status code is %d', $res->getStatusCode())); - if (200 === $result->status_code) { - $content = trim($result->body); + if (200 === $res->getStatusCode()) { + $content = trim($res->getBody()->getContents()); } if (\strlen($content) > 0) { diff --git a/app/Services/Currency/FixerIO.php b/app/Services/Currency/FixerIO.php deleted file mode 100644 index 561cac7315..0000000000 --- a/app/Services/Currency/FixerIO.php +++ /dev/null @@ -1,96 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Services\Currency; - -use Carbon\Carbon; -use Exception; -use FireflyIII\Models\CurrencyExchangeRate; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\User; -use Log; -use Requests; - -/** - * Class FixerIO. - */ -class FixerIO implements ExchangeRateInterface -{ - /** @var User */ - protected $user; - - /** - * @param TransactionCurrency $fromCurrency - * @param TransactionCurrency $toCurrency - * @param Carbon $date - * - * @return CurrencyExchangeRate - */ - public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate - { - $uri = sprintf('https://api.fixer.io/%s?base=%s&symbols=%s', $date->format('Y-m-d'), $fromCurrency->code, $toCurrency->code); - $statusCode = -1; - try { - $result = Requests::get($uri); - $statusCode = $result->status_code; - $body = $result->body; - } catch (Exception $e) { - // don't care about error - $body = sprintf('Requests_Exception: %s', $e->getMessage()); - } - // Requests_Exception - $rate = 1.0; - $content = null; - if (200 !== $statusCode) { - Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $statusCode, $body)); - } - // get rate from body: - if (200 === $statusCode) { - $content = json_decode($body, true); - } - if (null !== $content) { - $code = $toCurrency->code; - $rate = $content['rates'][$code] ?? '1'; - } - - // create new currency exchange rate object: - $exchangeRate = new CurrencyExchangeRate; - $exchangeRate->user()->associate($this->user); - $exchangeRate->fromCurrency()->associate($fromCurrency); - $exchangeRate->toCurrency()->associate($toCurrency); - $exchangeRate->date = $date; - $exchangeRate->rate = $rate; - $exchangeRate->save(); - - return $exchangeRate; - } - - /** - * @param User $user - * - * @return mixed|void - */ - public function setUser(User $user) - { - $this->user = $user; - } -} diff --git a/app/Services/Currency/FixerIOv2.php b/app/Services/Currency/FixerIOv2.php index 67d4a77c82..f44e917eb4 100644 --- a/app/Services/Currency/FixerIOv2.php +++ b/app/Services/Currency/FixerIOv2.php @@ -27,8 +27,9 @@ use Exception; use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\TransactionCurrency; use FireflyIII\User; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; /** * Class FixerIOv2. @@ -60,7 +61,9 @@ class FixerIOv2 implements ExchangeRateInterface $apiKey = env('FIXER_API_KEY', ''); // if no API key, return unsaved exchange rate. - if (\strlen($apiKey) === 0) { + if ('' === $apiKey) { + Log::warning('No fixer.IO API key, will not do conversion.'); + return $exchangeRate; } @@ -72,31 +75,36 @@ class FixerIOv2 implements ExchangeRateInterface $statusCode = -1; Log::debug(sprintf('Going to request exchange rate using URI %s', str_replace($apiKey, 'xxxx', $uri))); try { - $result = Requests::get($uri); - $statusCode = $result->status_code; - $body = $result->body; + $client = new Client; + $res = $client->request('GET', $uri); + $statusCode = $res->getStatusCode(); + $body = $res->getBody()->getContents(); Log::debug(sprintf('Result status code is %d', $statusCode)); - } catch (Exception $e) { + Log::debug(sprintf('Result body is: %s', $body)); + } catch (GuzzleException|Exception $e) { // don't care about error - $body = sprintf('Requests_Exception: %s', $e->getMessage()); + $body = sprintf('Guzzle exception: %s', $e->getMessage()); } - // Requests_Exception $content = null; if (200 !== $statusCode) { Log::error(sprintf('Something went wrong. Received error code %d and body "%s" from FixerIO.', $statusCode, $body)); } + $success = false; // get rate from body: if (200 === $statusCode) { $content = json_decode($body, true); + $success = $content['success'] ?? false; } - if (null !== $content) { + if (null !== $content && true === $success) { $code = $toCurrency->code; $rate = (float)($content['rates'][$code] ?? 0); + Log::debug('Got the following rates from Fixer: ', $content['rates'] ?? []); } - Log::debug('Got the following rates from Fixer: ', $content['rates'] ?? []); + $exchangeRate->rate = $rate; if ($rate !== 0.0) { + Log::debug('Rate is not zero, save it!'); $exchangeRate->save(); } diff --git a/app/Services/Github/Request/GithubRequest.php b/app/Services/Github/Request/GithubRequest.php index 3e7a5ca5d6..fc3552a18c 100644 --- a/app/Services/Github/Request/GithubRequest.php +++ b/app/Services/Github/Request/GithubRequest.php @@ -29,6 +29,6 @@ namespace FireflyIII\Services\Github\Request; */ interface GithubRequest { - public function call(); + public function call(): void; } diff --git a/app/Services/Github/Request/UpdateRequest.php b/app/Services/Github/Request/UpdateRequest.php index 9fb0f212dd..fd2d173049 100644 --- a/app/Services/Github/Request/UpdateRequest.php +++ b/app/Services/Github/Request/UpdateRequest.php @@ -26,7 +26,9 @@ namespace FireflyIII\Services\Github\Request; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Services\Github\Object\Release; -use Requests; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Log; use SimpleXMLElement; /** @@ -41,30 +43,34 @@ class UpdateRequest implements GithubRequest * * @throws FireflyException */ - public function call() + public function call(): void { $uri = 'https://github.com/firefly-iii/firefly-iii/releases.atom'; + Log::debug(sprintf('Going to call %s', $uri)); try { - $response = Requests::get($uri); - } catch (Exception $e) { + $client = new Client(); + $res = $client->request('GET', $uri); + } catch (GuzzleException|Exception $e) { throw new FireflyException(sprintf('Response error from Github: %s', $e->getMessage())); } - if ($response->status_code !== 200) { - throw new FireflyException(sprintf('Returned code %d, error: %s', $response->status_code, $response->body)); + if ($res->getStatusCode() !== 200) { + throw new FireflyException(sprintf('Returned code %d, error: %s', $res->getStatusCode(), $res->getBody()->getContents())); } - $releaseXml = new SimpleXMLElement($response->body, LIBXML_NOCDATA); + $releaseXml = new SimpleXMLElement($res->getBody()->getContents(), LIBXML_NOCDATA); //fetch the products for each category if (isset($releaseXml->entry)) { + Log::debug(sprintf('Count of entries is: %d', \count($releaseXml->entry))); foreach ($releaseXml->entry as $entry) { - $array = [ + $array = [ 'id' => (string)$entry->id, 'updated' => (string)$entry->updated, 'title' => (string)$entry->title, 'content' => (string)$entry->content, ]; + Log::debug(sprintf('Found version %s', $entry->title)); $this->releases[] = new Release($array); } } diff --git a/app/Services/IP/IpifyOrg.php b/app/Services/IP/IpifyOrg.php index 2e72e23c9c..0c0f8a3404 100644 --- a/app/Services/IP/IpifyOrg.php +++ b/app/Services/IP/IpifyOrg.php @@ -24,8 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Services\IP; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; /** * Class IpifyOrg @@ -42,19 +43,20 @@ class IpifyOrg implements IPRetrievalInterface { $result = null; try { - $response = Requests::get('https://api.ipify.org'); - } catch (Exception $e) { + $client = new Client; + $res = $client->request('GET', 'https://api.ipify.org'); + } catch (GuzzleException|Exception $e) { Log::warning(sprintf('The ipify.org service could not retrieve external IP: %s', $e->getMessage())); Log::warning($e->getTraceAsString()); return null; } - if (200 !== $response->status_code) { - Log::warning(sprintf('Could not retrieve external IP: %d %s', $response->status_code, $response->body)); + if (200 !== $res->getStatusCode()) { + Log::warning(sprintf('Could not retrieve external IP: %d %s', $res->getStatusCode(), $res->getBody()->getContents())); return null; } - return (string)$response->body; + return (string)$res->getBody()->getContents(); } } diff --git a/app/Services/Password/PwndVerifier.php b/app/Services/Password/PwndVerifier.php deleted file mode 100644 index d5bc349253..0000000000 --- a/app/Services/Password/PwndVerifier.php +++ /dev/null @@ -1,56 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Services\Password; - -use Exception; -use Log; -use Requests; - -/** - * Class PwndVerifier. - */ -class PwndVerifier implements Verifier -{ - /** - * Verify the given password against (some) service. - * - * @param string $password - * - * @return bool - */ - public function validPassword(string $password): bool - { - $hash = sha1($password); - $uri = sprintf('https://haveibeenpwned.com/api/v2/pwnedpassword/%s', $hash); - $opt = ['useragent' => 'Firefly III v' . config('firefly.version'), 'timeout' => 2]; - - try { - $result = Requests::get($uri, ['originalPasswordIsAHash' => 'true'], $opt); - } catch (Exception $e) { - return true; - } - Log::debug(sprintf('Status code returned is %d', $result->status_code)); - - return 404 === $result->status_code; - } -} diff --git a/app/Services/Password/PwndVerifierV2.php b/app/Services/Password/PwndVerifierV2.php index bf3cd330cf..014fbbcbe6 100644 --- a/app/Services/Password/PwndVerifierV2.php +++ b/app/Services/Password/PwndVerifierV2.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Services\Password; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; /** * Class PwndVerifierV2. @@ -44,27 +45,30 @@ class PwndVerifierV2 implements Verifier $prefix = substr($hash, 0, 5); $rest = substr($hash, 5); $uri = sprintf('https://api.pwnedpasswords.com/range/%s', $prefix); - $opt = ['useragent' => 'Firefly III v' . config('firefly.version'), 'timeout' => 2]; + $opt = [ + 'headers' => ['User-Agent' => 'Firefly III v' . config('firefly.version')], + 'timeout' => 2]; Log::debug(sprintf('hash prefix is %s', $prefix)); Log::debug(sprintf('rest is %s', $rest)); try { - $result = Requests::get($uri, $opt); - } catch (Exception $e) { + $client = new Client(); + $res = $client->request('GET', $uri, $opt); + } catch (GuzzleException|Exception $e) { return true; } - Log::debug(sprintf('Status code returned is %d', $result->status_code)); - if (404 === $result->status_code) { + Log::debug(sprintf('Status code returned is %d', $res->getStatusCode())); + if (404 === $res->getStatusCode()) { return true; } - $strpos = stripos($result->body, $rest); + $strpos = stripos($res->getBody()->getContents(), $rest); if ($strpos === false) { Log::debug(sprintf('%s was not found in result body. Return true.', $rest)); return true; } - Log::debug('Could not find %s, return FALSE.'); + Log::debug(sprintf('Could not find %s, return FALSE.', $rest)); return false; } diff --git a/app/Services/Spectre/Request/SpectreRequest.php b/app/Services/Spectre/Request/SpectreRequest.php index 6b735de91b..1fafadf23c 100644 --- a/app/Services/Spectre/Request/SpectreRequest.php +++ b/app/Services/Spectre/Request/SpectreRequest.php @@ -25,9 +25,9 @@ namespace FireflyIII\Services\Spectre\Request; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; use Log; -use Requests; -use Requests_Response; /** * Class SpectreRequest @@ -195,23 +195,24 @@ abstract class SpectreRequest } $headers = $this->getDefaultHeaders(); - $body = json_encode($data); + $sendBody = json_encode($data); // OK $fullUri = $this->server . $uri; - $signature = $this->generateSignature('get', $fullUri, $body); + $signature = $this->generateSignature('get', $fullUri, $sendBody); $headers['Signature'] = $signature; Log::debug('Final headers for spectre signed get request:', $headers); try { - $response = Requests::get($fullUri, $headers); - } catch (Exception $e) { - throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage())); + $client = new Client; + $res = $client->request('GET', $fullUri, ['headers' => $headers]); + } catch (GuzzleException|Exception $e) { + throw new FireflyException(sprintf('Guzzle Exception: %s', $e->getMessage())); } - $this->detectError($response); - $statusCode = (int)$response->status_code; + $statusCode = $res->getStatusCode(); + $returnBody = $res->getBody()->getContents(); + $this->detectError($returnBody, $statusCode); - $body = $response->body; - $array = json_decode($body, true); - $responseHeaders = $response->headers->getAll(); + $array = json_decode($returnBody, true); + $responseHeaders = $res->getHeaders(); $array['ResponseHeaders'] = $responseHeaders; $array['ResponseStatusCode'] = $statusCode; @@ -220,6 +221,7 @@ abstract class SpectreRequest throw new FireflyException(sprintf('Error of class %s: %s', $array['error_class'], $message)); } + return $array; } @@ -245,28 +247,32 @@ abstract class SpectreRequest Log::debug('Final headers for spectre signed POST request:', $headers); try { - $response = Requests::post($fullUri, $headers, $body); - } catch (Exception $e) { + $client = new Client; + $res = $client->request('POST', $fullUri, ['headers' => $headers, 'body' => $body]); + } catch (GuzzleException|Exception $e) { throw new FireflyException(sprintf('Request Exception: %s', $e->getMessage())); } - $this->detectError($response); - $body = $response->body; + $body = $res->getBody()->getContents(); + $statusCode = $res->getStatusCode(); + $this->detectError($body, $statusCode); + $array = json_decode($body, true); - $responseHeaders = $response->headers->getAll(); + $responseHeaders = $res->getHeaders(); $array['ResponseHeaders'] = $responseHeaders; - $array['ResponseStatusCode'] = $response->status_code; + $array['ResponseStatusCode'] = $statusCode; return $array; } /** - * @param Requests_Response $response + * @param string $body + * + * @param int $statusCode * * @throws FireflyException */ - private function detectError(Requests_Response $response): void + private function detectError(string $body, int $statusCode): void { - $body = $response->body; $array = json_decode($body, true); if (isset($array['error_class'])) { $message = $array['error_message'] ?? '(no message)'; @@ -279,9 +285,8 @@ abstract class SpectreRequest throw new FireflyException(sprintf('Error of class %s: %s', $errorClass, $message)); } - $statusCode = (int)$response->status_code; if (200 !== $statusCode) { - throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $response->body)); + throw new FireflyException(sprintf('Status code %d: %s', $statusCode, $body)); } } } From 4a12d4d1568298bd6d7d9d8d4e74c9f8b46b9b03 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 6 Jun 2018 21:23:00 +0200 Subject: [PATCH 005/134] Code cleanup [skip ci] --- app/Api/V1/Controllers/Controller.php | 2 - app/Api/V1/Controllers/CurrencyController.php | 3 +- app/Api/V1/Requests/BillRequest.php | 4 +- app/Console/Commands/UpgradeDatabase.php | 2 +- app/Console/Commands/VerifyDatabase.php | 8 +- app/Exceptions/Handler.php | 26 ++-- app/Export/ExpandedProcessor.php | 2 +- app/Factory/TransactionCurrencyFactory.php | 1 + app/Factory/TransactionFactory.php | 4 +- app/Handlers/Events/APIEventHandler.php | 1 - .../Attachments/AttachmentHelperInterface.php | 4 +- app/Http/Controllers/BudgetController.php | 2 +- app/Http/Controllers/DebugController.php | 10 +- app/Http/Controllers/RuleController.php | 14 ++- .../Transaction/BulkController.php | 2 +- .../Transaction/ConvertController.php | 5 +- .../Transaction/MassController.php | 9 +- app/Http/Requests/PiggyBankFormRequest.php | 12 +- app/Http/Requests/SplitJournalFormRequest.php | 34 ++--- app/Import/Converter/RabobankDebitCredit.php | 2 +- .../JobConfiguration/FakeJobConfiguration.php | 4 +- .../JobConfiguration/FileJobConfiguration.php | 4 +- .../Prerequisites/BunqPrerequisites.php | 2 +- .../Prerequisites/FakePrerequisites.php | 1 - .../Prerequisites/PrerequisitesInterface.php | 1 - app/Import/Routine/SpectreRoutine.php | 2 +- app/Import/Storage/ImportArrayStorage.php | 2 +- app/Models/Account.php | 6 +- app/Models/AccountMeta.php | 1 - app/Models/AccountType.php | 2 +- app/Models/Attachment.php | 2 +- app/Models/AvailableBudget.php | 3 +- app/Models/Budget.php | 5 +- app/Models/BudgetLimit.php | 1 - app/Models/Category.php | 4 +- app/Models/PiggyBank.php | 4 - app/Models/PiggyBankEvent.php | 2 - app/Models/PiggyBankRepetition.php | 2 +- app/Models/Preference.php | 4 +- app/Models/Role.php | 2 +- app/Models/Rule.php | 12 +- app/Models/RuleAction.php | 1 - app/Models/RuleGroup.php | 4 +- app/Models/Tag.php | 9 +- app/Models/Transaction.php | 2 +- app/Models/TransactionCurrency.php | 3 +- app/Models/TransactionJournal.php | 13 +- app/Models/TransactionJournalMeta.php | 4 +- app/Models/TransactionType.php | 2 +- .../Account/FindAccountsTrait.php | 1 + .../Budget/BudgetRepositoryInterface.php | 18 +-- .../ImportJob/ImportJobRepository.php | 1 + .../ImportJobRepositoryInterface.php | 118 +++++++++--------- .../Journal/JournalRepository.php | 1 - .../Journal/JournalRepositoryInterface.php | 19 +-- .../PiggyBank/PiggyBankRepository.php | 4 +- app/Repositories/Tag/TagRepository.php | 41 +++--- .../Tag/TagRepositoryInterface.php | 26 ++-- app/Services/Spectre/Object/Attempt.php | 4 +- app/Support/Binder/SimpleJournalList.php | 3 +- .../Bunq/BunqJobConfigurationInterface.php | 1 + .../File/ConfigureUploadHandler.php | 104 ++++++++------- .../File/NewFileJobHandler.php | 1 + .../Spectre/ChooseLoginHandler.php | 5 - .../Import/Placeholder/ColumnValue.php | 1 + .../Import/Routine/File/CSVProcessor.php | 2 +- .../Routine/File/FileProcessorInterface.php | 1 + .../Import/Routine/File/LineReader.php | 1 + app/Support/Preferences.php | 2 +- app/Support/Steam.php | 8 +- app/Support/Twig/Extension/Transaction.php | 2 +- app/Support/Twig/General.php | 2 +- app/TransactionRules/Actions/LinkToBill.php | 4 +- .../Actions/SetDestinationAccount.php | 4 +- app/User.php | 35 +++--- 75 files changed, 319 insertions(+), 341 deletions(-) diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index a212ad3029..3b5b3d23b0 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -26,8 +26,6 @@ namespace FireflyIII\Api\V1\Controllers; use Carbon\Carbon; use Carbon\Exceptions\InvalidDateException; -use FireflyConfig; -use FireflyIII\Exceptions\FireflyException; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; diff --git a/app/Api/V1/Controllers/CurrencyController.php b/app/Api/V1/Controllers/CurrencyController.php index 176986afb8..42cf032f8c 100644 --- a/app/Api/V1/Controllers/CurrencyController.php +++ b/app/Api/V1/Controllers/CurrencyController.php @@ -24,7 +24,6 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use FireflyIII\Api\V1\Requests\BillRequest; use FireflyIII\Api\V1\Requests\CurrencyRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionCurrency; @@ -178,7 +177,7 @@ class CurrencyController extends Controller /** - * @param CurrencyRequest $request + * @param CurrencyRequest $request * @param TransactionCurrency $currency * * @return JsonResponse diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php index 9f68a656cd..f58c977737 100644 --- a/app/Api/V1/Requests/BillRequest.php +++ b/app/Api/V1/Requests/BillRequest.php @@ -86,8 +86,8 @@ class BillRequest extends Request break; case 'PUT': case 'PATCH': - $bill = $this->route()->parameter('bill'); - $rules['name'] .= ',' . $bill->id; + $bill = $this->route()->parameter('bill'); + $rules['name'] .= ',' . $bill->id; break; } diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index c4f31d21d2..a533aedf2f 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -184,7 +184,7 @@ class UpgradeDatabase extends Command ] ); } - if($bill->amount_max === $bill->amount_min) { + if ($bill->amount_max === $bill->amount_min) { RuleTrigger::create( [ 'rule_id' => $rule->id, diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 08ec12f3df..00920aaf7f 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -164,7 +164,8 @@ class VerifyDatabase extends Command if (isset($results[$key]) && $results[$key] !== $category) { $this->error( sprintf( - 'Transaction #%d referred to the wrong category. Was category #%d but is fixed to be category #%d.', $obj->transaction_journal_id, $category, $results[$key] + 'Transaction #%d referred to the wrong category. Was category #%d but is fixed to be category #%d.', $obj->transaction_journal_id, + $category, $results[$key] ) ); DB::table('category_transaction')->where('id', $obj->ct_id)->update(['category_id' => $results[$key]]); @@ -184,14 +185,15 @@ class VerifyDatabase extends Command ->get(['transactions.id', 'transaction_journal_id', 'identifier', 'budget_transaction.budget_id', 'budget_transaction.id as ct_id']); $results = []; foreach ($set as $obj) { - $key = $obj->transaction_journal_id . '-' . $obj->identifier; + $key = $obj->transaction_journal_id . '-' . $obj->identifier; $budget = (int)$obj->budget_id; // value exists and is not budget: if (isset($results[$key]) && $results[$key] !== $budget) { $this->error( sprintf( - 'Transaction #%d referred to the wrong budget. Was budget #%d but is fixed to be budget #%d.', $obj->transaction_journal_id, $budget, $results[$key] + 'Transaction #%d referred to the wrong budget. Was budget #%d but is fixed to be budget #%d.', $obj->transaction_journal_id, $budget, + $results[$key] ) ); DB::table('budget_transaction')->where('id', $obj->ct_id)->update(['budget_id' => $results[$key]]); diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index d66d42a274..59b59b8bdd 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -132,20 +132,20 @@ class Handler extends ExceptionHandler $doMailError = env('SEND_ERROR_MESSAGE', true); if ( // if the user wants us to mail: - $doMailError === true && - (( - // and if is one of these error instances - $exception instanceof FireflyException - || $exception instanceof ErrorException - || $exception instanceof OAuthServerException + $doMailError === true + && (( + // and if is one of these error instances + $exception instanceof FireflyException + || $exception instanceof ErrorException + || $exception instanceof OAuthServerException - ) - || ( - // or this one, but it's a JSON exception. - $exception instanceof AuthenticationException - && Request::expectsJson() === true - )) - ) { + ) + || ( + // or this one, but it's a JSON exception. + $exception instanceof AuthenticationException + && Request::expectsJson() === true + )) + ) { // then, send email $userData = [ 'id' => 0, diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php index d8ad004f9f..3c964efeba 100644 --- a/app/Export/ExpandedProcessor.php +++ b/app/Export/ExpandedProcessor.php @@ -36,12 +36,12 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\ExportJob; use FireflyIII\Models\Note; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; use Log; use Storage; use ZipArchive; -use FireflyIII\Models\TransactionJournal; /** * Class ExpandedProcessor. diff --git a/app/Factory/TransactionCurrencyFactory.php b/app/Factory/TransactionCurrencyFactory.php index 3cee423c42..e57e1f81d1 100644 --- a/app/Factory/TransactionCurrencyFactory.php +++ b/app/Factory/TransactionCurrencyFactory.php @@ -71,6 +71,7 @@ class TransactionCurrencyFactory if ('' === $currencyCode && $currencyId === 0) { Log::warning('Cannot find anything on empty currency code and empty currency ID!'); + return null; } diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 5d1cf3ecef..d9199b0ab1 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -97,12 +97,12 @@ class TransactionFactory $description = $journal->description === $data['description'] ? null : $data['description']; // type of source account depends on journal type: - $sourceType = $this->accountType($journal, 'source'); + $sourceType = $this->accountType($journal, 'source'); Log::debug(sprintf('Expect source account to be of type %s', $sourceType)); $sourceAccount = $this->findAccount($sourceType, $data['source_id'], $data['source_name']); // same for destination account: - $destinationType = $this->accountType($journal, 'destination'); + $destinationType = $this->accountType($journal, 'destination'); Log::debug(sprintf('Expect source destination to be of type %s', $destinationType)); $destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']); // first make a "negative" (source) transaction based on the data in the array. diff --git a/app/Handlers/Events/APIEventHandler.php b/app/Handlers/Events/APIEventHandler.php index 64aa286420..e73b151e86 100644 --- a/app/Handlers/Events/APIEventHandler.php +++ b/app/Handlers/Events/APIEventHandler.php @@ -28,7 +28,6 @@ use Exception; use FireflyIII\Mail\AccessTokenCreatedMail; use FireflyIII\Repositories\User\UserRepositoryInterface; use Laravel\Passport\Events\AccessTokenCreated; -use Laravel\Passport\Token; use Log; use Mail; use Request; diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php index 276d312a27..03fd095727 100644 --- a/app/Helpers/Attachments/AttachmentHelperInterface.php +++ b/app/Helpers/Attachments/AttachmentHelperInterface.php @@ -37,14 +37,14 @@ interface AttachmentHelperInterface * * @return string */ - public function getAttachmentLocation(Attachment $attachment): string; + public function getAttachmentContent(Attachment $attachment): string; /** * @param Attachment $attachment * * @return string */ - public function getAttachmentContent(Attachment $attachment): string; + public function getAttachmentLocation(Attachment $attachment): string; /** * @return Collection diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index ed199483d5..e8c5f00b19 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -96,7 +96,7 @@ class BudgetController extends Controller // if today is between start and end, use the diff in days between end and today (days left) // otherwise, use diff between start and end. $today = new Carbon; - Log::debug(sprintf('Start is %s, end is %s, today is %s', $start->format('Y-m-d'), $end->format('Y-m-d'),$today->format('Y-m-d'))); + Log::debug(sprintf('Start is %s, end is %s, today is %s', $start->format('Y-m-d'), $end->format('Y-m-d'), $today->format('Y-m-d'))); if ($today->gte($start) && $today->lte($end)) { $days = $end->diffInDays($today); $daysInMonth = $start->diffInDays($today); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 11f52acf5c..3b0a444d23 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -111,10 +111,12 @@ class DebugController extends Controller return view( 'debug', compact( - 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'appLog', 'appLogLevel', 'now', 'packages', 'drivers', 'currentDriver', - 'userAgent', 'displayErrors', 'errorReporting', 'phpOs', 'interface', 'logContent', 'cacheDriver', 'isDocker', 'isSandstorm', 'trustedProxies', - 'toSandbox' - ) + 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'appLog', 'appLogLevel', 'now', 'packages', 'drivers', + 'currentDriver', + 'userAgent', 'displayErrors', 'errorReporting', 'phpOs', 'interface', 'logContent', 'cacheDriver', 'isDocker', 'isSandstorm', + 'trustedProxies', + 'toSandbox' + ) ); } diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index 98a781017c..4838a5cc93 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -23,13 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use ExpandedForm; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Requests\RuleFormRequest; use FireflyIII\Http\Requests\SelectTransactionsRequest; use FireflyIII\Http\Requests\TestRuleFormRequest; use FireflyIII\Jobs\ExecuteRuleOnExistingTransactions; -use FireflyIII\Models\AccountType; use FireflyIII\Models\Bill; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleAction; @@ -331,11 +329,11 @@ class RuleController extends Controller public function selectTransactions(Rule $rule) { // does the user have shared accounts? - $first = session('first')->format('Y-m-d'); - $today = Carbon::create()->format('Y-m-d'); - $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); + $first = session('first')->format('Y-m-d'); + $today = Carbon::create()->format('Y-m-d'); + $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); - return view('rules.rule.select-transactions', compact( 'first', 'today', 'rule', 'subTitle')); + return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle')); } /** @@ -429,6 +427,7 @@ class RuleController extends Controller Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd return response()->json(['html' => $view, 'warning' => $warning]); @@ -490,6 +489,7 @@ class RuleController extends Controller Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); } + // @codeCoverageIgnoreEnd return response()->json(['html' => $view, 'warning' => $warning]); @@ -600,6 +600,7 @@ class RuleController extends Controller Log::error(sprintf('Throwable was thrown in getActionsForBill(): %s', $e->getMessage())); Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd return $actions; @@ -809,6 +810,7 @@ class RuleController extends Controller Log::debug(sprintf('Throwable was thrown in getTriggersForBill(): %s', $e->getMessage())); Log::debug($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd return $triggers; diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php index b1dcce8f4e..aeac8035ea 100644 --- a/app/Http/Controllers/Transaction/BulkController.php +++ b/app/Http/Controllers/Transaction/BulkController.php @@ -87,7 +87,7 @@ class BulkController extends Controller /** - * @param BulkEditJournalRequest $request + * @param BulkEditJournalRequest $request * * @return mixed */ diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 1b09ccc760..b6ffe94897 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -98,8 +98,9 @@ class ConvertController extends Controller return view( 'transactions.convert', compact( - 'sourceType', 'destinationType', 'journal', 'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType', 'subTitle', 'subTitleIcon' - ) + 'sourceType', 'destinationType', 'journal', 'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType', + 'subTitle', 'subTitleIcon' + ) ); } diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 8b78a9ccb9..198cfc64ba 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -24,7 +24,6 @@ namespace FireflyIII\Http\Controllers\Transaction; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\TransactionViewFilter; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\MassDeleteJournalRequest; @@ -37,10 +36,9 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Transformers\TransactionTransformer; use Illuminate\Support\Collection; +use Illuminate\View\View as IlluminateView; use Preferences; use Symfony\Component\HttpFoundation\ParameterBag; -use View; -use Illuminate\View\View as IlluminateView; /** * Class MassController. @@ -155,10 +153,11 @@ class MassController extends Controller // transform to array $transactions = $collection->map( function (Transaction $transaction) use ($transformer) { - $transaction= $transformer->transform($transaction); + $transaction = $transformer->transform($transaction); // make sure amount is positive: - $transaction['amount'] = app('steam')->positive((string)$transaction['amount']); + $transaction['amount'] = app('steam')->positive((string)$transaction['amount']); $transaction['foreign_amount'] = app('steam')->positive((string)$transaction['foreign_amount']); + return $transaction; } ); diff --git a/app/Http/Requests/PiggyBankFormRequest.php b/app/Http/Requests/PiggyBankFormRequest.php index fae23428f7..bd6488edd5 100644 --- a/app/Http/Requests/PiggyBankFormRequest.php +++ b/app/Http/Requests/PiggyBankFormRequest.php @@ -62,12 +62,12 @@ class PiggyBankFormRequest extends Request } $rules = [ - 'name' => $nameRule, - 'account_id' => 'required|belongsToUser:accounts', - 'targetamount' => 'required|numeric|more:0', - 'startdate' => 'date', - 'targetdate' => 'date|nullable', - 'order' => 'integer|min:1', + 'name' => $nameRule, + 'account_id' => 'required|belongsToUser:accounts', + 'targetamount' => 'required|numeric|more:0', + 'startdate' => 'date', + 'targetdate' => 'date|nullable', + 'order' => 'integer|min:1', ]; return $rules; diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index ce99023a1f..207754df5b 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -109,23 +109,23 @@ class SplitJournalFormRequest extends Request public function rules(): array { return [ - 'what' => 'required|in:withdrawal,deposit,transfer', - 'journal_description' => 'required|between:1,255', - 'id' => 'numeric|belongsToUser:transaction_journals,id', - 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id', - 'journal_source_account_name.*' => 'between:1,255', - 'journal_currency_id' => 'required|exists:transaction_currencies,id', - 'date' => 'required|date', - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'transactions.*.transaction_description' => 'required|between:1,255', - 'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id', - 'transactions.*.destination_name' => 'between:1,255|nullable', - 'transactions.*.amount' => 'required|numeric', - 'transactions.*.budget_id' => 'belongsToUser:budgets,id', - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.piggy_bank_id' => 'between:1,255|nullable', + 'what' => 'required|in:withdrawal,deposit,transfer', + 'journal_description' => 'required|between:1,255', + 'id' => 'numeric|belongsToUser:transaction_journals,id', + 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id', + 'journal_source_account_name.*' => 'between:1,255', + 'journal_currency_id' => 'required|exists:transaction_currencies,id', + 'date' => 'required|date', + 'interest_date' => 'date|nullable', + 'book_date' => 'date|nullable', + 'process_date' => 'date|nullable', + 'transactions.*.transaction_description' => 'required|between:1,255', + 'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id', + 'transactions.*.destination_name' => 'between:1,255|nullable', + 'transactions.*.amount' => 'required|numeric', + 'transactions.*.budget_id' => 'belongsToUser:budgets,id', + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.piggy_bank_id' => 'between:1,255|nullable', ]; } diff --git a/app/Import/Converter/RabobankDebitCredit.php b/app/Import/Converter/RabobankDebitCredit.php index d426e52b5b..764a7f907e 100644 --- a/app/Import/Converter/RabobankDebitCredit.php +++ b/app/Import/Converter/RabobankDebitCredit.php @@ -44,7 +44,7 @@ class RabobankDebitCredit implements ConverterInterface return -1; } // old format: - if('A' === $value) { + if ('A' === $value) { Log::debug('Return -1'); return -1; diff --git a/app/Import/JobConfiguration/FakeJobConfiguration.php b/app/Import/JobConfiguration/FakeJobConfiguration.php index 81a96adfd7..3812a78d59 100644 --- a/app/Import/JobConfiguration/FakeJobConfiguration.php +++ b/app/Import/JobConfiguration/FakeJobConfiguration.php @@ -106,6 +106,7 @@ class FakeJobConfiguration implements JobConfigurationInterface /** * Return the data required for the next step in the job configuration. + * * @codeCoverageIgnore * @return array */ @@ -144,6 +145,7 @@ class FakeJobConfiguration implements JobConfigurationInterface if (strtolower($album) !== 'station to station' && $this->importJob->stage !== 'new') { return 'import.fake.enter-album'; } + return 'impossible-view'; // @codeCoverageIgnore } @@ -152,7 +154,7 @@ class FakeJobConfiguration implements JobConfigurationInterface */ public function setImportJob(ImportJob $importJob): void { - $this->importJob = $importJob; + $this->importJob = $importJob; $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($importJob->user); } diff --git a/app/Import/JobConfiguration/FileJobConfiguration.php b/app/Import/JobConfiguration/FileJobConfiguration.php index cfc1d18966..5820b38951 100644 --- a/app/Import/JobConfiguration/FileJobConfiguration.php +++ b/app/Import/JobConfiguration/FileJobConfiguration.php @@ -28,10 +28,10 @@ namespace FireflyIII\Import\JobConfiguration; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\JobConfiguration\File\FileConfigurationInterface; use FireflyIII\Support\Import\JobConfiguration\File\ConfigureMappingHandler; use FireflyIII\Support\Import\JobConfiguration\File\ConfigureRolesHandler; use FireflyIII\Support\Import\JobConfiguration\File\ConfigureUploadHandler; +use FireflyIII\Support\Import\JobConfiguration\File\FileConfigurationInterface; use FireflyIII\Support\Import\JobConfiguration\File\NewFileJobHandler; use Illuminate\Support\MessageBag; @@ -120,7 +120,7 @@ class FileJobConfiguration implements JobConfigurationInterface */ public function setImportJob(ImportJob $importJob): void { - $this->importJob = $importJob; + $this->importJob = $importJob; $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($importJob->user); } diff --git a/app/Import/Prerequisites/BunqPrerequisites.php b/app/Import/Prerequisites/BunqPrerequisites.php index ea39453f3a..96fd3ad672 100644 --- a/app/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Import/Prerequisites/BunqPrerequisites.php @@ -119,7 +119,7 @@ class BunqPrerequisites implements PrerequisitesInterface try { /** @var ApiContext $object */ - $object = app(ApiContext::class); + $object = app(ApiContext::class); $apiContext = $object->create($environment, $apiKey, $deviceDescription, $permittedIps); } catch (FireflyException $e) { $messages = new MessageBag(); diff --git a/app/Import/Prerequisites/FakePrerequisites.php b/app/Import/Prerequisites/FakePrerequisites.php index ac8c07affb..86642e8066 100644 --- a/app/Import/Prerequisites/FakePrerequisites.php +++ b/app/Import/Prerequisites/FakePrerequisites.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Import\Prerequisites; use FireflyIII\User; -use Illuminate\Http\Request; use Illuminate\Support\MessageBag; /** diff --git a/app/Import/Prerequisites/PrerequisitesInterface.php b/app/Import/Prerequisites/PrerequisitesInterface.php index 588821eeca..f3f82ae5be 100644 --- a/app/Import/Prerequisites/PrerequisitesInterface.php +++ b/app/Import/Prerequisites/PrerequisitesInterface.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Import\Prerequisites; use FireflyIII\User; -use Illuminate\Http\Request; use Illuminate\Support\MessageBag; /** diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 1c51d8d697..83aea2664a 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -25,8 +25,8 @@ namespace FireflyIII\Import\Routine; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\Routine\Spectre\StageImportDataHandler; use FireflyIII\Support\Import\Routine\Spectre\StageAuthenticatedHandler; +use FireflyIII\Support\Import\Routine\Spectre\StageImportDataHandler; use FireflyIII\Support\Import\Routine\Spectre\StageNewHandler; use Log; diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 01315fbe9f..2754ad94df 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -215,7 +215,7 @@ class ImportArrayStorage $collector = app(JournalCollectorInterface::class); $collector->setUser($this->importJob->user); $collector->setAllAssetAccounts() - ->ignoreCache() + ->ignoreCache() ->setTypes([TransactionType::TRANSFER]) ->withOpposingAccount(); $collector->removeFilter(InternalTransferFilter::class); diff --git a/app/Models/Account.php b/app/Models/Account.php index 1e79b0aa2c..3a0897258c 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -38,9 +38,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Account. * - * @property int $id - * @property string $name - * @property string $iban + * @property int $id + * @property string $name + * @property string $iban * @property AccountType $accountType */ class Account extends Model diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index aa5bb522fd..cd087f969b 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -24,7 +24,6 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use FireflyIII\Models\Account; /** * Class AccountMeta. diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index 9729d5a34f..de1d5794cd 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -24,10 +24,10 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; -use FireflyIII\Models\Account; /** * Class AccountType. + * * @property string $type * */ diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 2c1a1e61b6..044283cbdb 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -23,12 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; /** * Class Attachment. diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 1e74d00af1..2f023f4ad5 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -22,11 +22,10 @@ declare(strict_types=1); namespace FireflyIII\Models; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; -use FireflyIII\User; -use FireflyIII\Models\TransactionCurrency; /** * Class AvailableBudget. diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 6fc3e3d6f4..60f80da5f3 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -23,14 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\BudgetLimit; /** * Class Budget. diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index 615c2d90ee..2934a5b6f5 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -24,7 +24,6 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\Budget; /** * Class BudgetLimit. diff --git a/app/Models/Category.php b/app/Models/Category.php index 2e2f0aaf26..068c12934b 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -23,13 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; /** * Class Category. diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 4e13b540c5..e5b8c30a37 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -29,10 +29,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Steam; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\PiggyBankRepetition; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Note; -use FireflyIII\Models\Account; /** * Class PiggyBank. diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index 9463fcc39c..0f9f364766 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\PiggyBank; /** * Class PiggyBankEvent. diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 3efac08652..42494f1f9e 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -25,10 +25,10 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\PiggyBank; /** * Class PiggyBankRepetition. + * * @property string $currentamount */ class PiggyBankRepetition extends Model diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 3a873f7086..7182c9728e 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -25,15 +25,15 @@ namespace FireflyIII\Models; use Crypt; use Exception; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Model; use Log; -use FireflyIII\User; /** * Class Preference. * - * @property mixed $data + * @property mixed $data * @property string $name */ class Preference extends Model diff --git a/app/Models/Role.php b/app/Models/Role.php index 02fcfb5a5d..ee735d4605 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -22,9 +22,9 @@ declare(strict_types=1); namespace FireflyIII\Models; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use FireflyIII\User; /** * Class Role. diff --git a/app/Models/Rule.php b/app/Models/Rule.php index e6e65ba73e..7f38e31824 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -22,18 +22,16 @@ declare(strict_types=1); namespace FireflyIII\Models; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\RuleAction; /** * Class Rule. - * @property bool $stop_processing - * @property int $id + * + * @property bool $stop_processing + * @property int $id * @property \Illuminate\Support\Collection $ruleTriggers */ class Rule extends Model @@ -56,7 +54,7 @@ class Rule extends Model 'strict' => 'boolean', ]; /** @var array */ - protected $fillable = ['rule_group_id', 'order', 'active', 'title', 'description', 'user_id','strict']; + protected $fillable = ['rule_group_id', 'order', 'active', 'title', 'description', 'user_id', 'strict']; /** * @param string $value diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index d498ee11f6..da9e6c048a 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; -use FireflyIII\Models\Rule; /** * Class RuleAction. diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index cfe4ae2c35..149b12fc4d 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -22,14 +22,14 @@ declare(strict_types=1); namespace FireflyIII\Models; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\Rule; /** * Class RuleGroup. + * * @property bool $active */ class RuleGroup extends Model diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 9a085b0325..a60b07449b 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -23,19 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\User; -use FireflyIII\Models\TransactionJournal; /** * Class Tag. * - * @property Collection $transactionJournals - * @property string $tag - * @property int $id + * @property Collection $transactionJournals + * @property string $tag + * @property int $id * @property \Carbon\Carbon $date */ class Tag extends Model diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index b591f1d956..df124b1041 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -71,7 +71,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $description * @property bool $is_split * @property int $attachmentCount - * @property int $transaction_currency_id + * @property int $transaction_currency_id */ class Transaction extends Model { diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 439962a257..829d5e9876 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -25,13 +25,12 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; /** * Class TransactionCurrency. * * @property string $code - * @property int $decimal_places + * @property int $decimal_places * */ class TransactionCurrency extends Model diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 40595f644d..e54c69266f 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -35,23 +35,12 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Log; use Preferences; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; -use FireflyIII\Models\TransactionJournalMeta; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\Tag; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Note; -use FireflyIII\Models\Category; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Bill; -use FireflyIII\Models\Attachment; /** * Class TransactionJournal. * * @property User $user - * @property int $bill_id + * @property int $bill_id */ class TransactionJournal extends Model { diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index bef13e7fa8..e6acea708f 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -25,12 +25,12 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; -use FireflyIII\Models\TransactionJournal; /** * Class TransactionJournalMeta. + * * @property string $name - * @property int $transaction_journal_id + * @property int $transaction_journal_id */ class TransactionJournalMeta extends Model { diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index 67a1b906e1..756921cbf7 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -25,10 +25,10 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; /** * Class TransactionType. + * * @property string $type */ class TransactionType extends Model diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php index 79451ff6e4..de9b338488 100644 --- a/app/Repositories/Account/FindAccountsTrait.php +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -58,6 +58,7 @@ trait FindAccountsTrait /** * @param string $number * @param array $types + * * @return Account|null */ public function findByAccountNumber(string $number, array $types): ?Account diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index a929e92484..e2197b76ba 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -35,15 +35,6 @@ use Illuminate\Support\Collection; interface BudgetRepositoryInterface { - /** - * Get all budgets with these ID's. - * - * @param array $budgetIds - * - * @return Collection - */ - public function getByIds(array $budgetIds): Collection; - /** * A method that returns the amount of money budgeted per day for this budget, * on average. @@ -174,6 +165,15 @@ interface BudgetRepositoryInterface */ public function getBudgets(): Collection; + /** + * Get all budgets with these ID's. + * + * @param array $budgetIds + * + * @return Collection + */ + public function getByIds(array $budgetIds): Collection; + /** * @return Collection */ diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 4d42698aec..7bf69d8a18 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -360,6 +360,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $newConfig = array_merge($currentConfig, $configuration); $job->configuration = $newConfig; $job->save(); + //Log::debug(sprintf('Set config of job "%s" to: ', $job->key), $newConfig); return $job; diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index baf1d51ce1..fd5ccd1d5e 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -36,52 +36,13 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; interface ImportJobRepositoryInterface { /** - * Return all attachments for job. - * * @param ImportJob $job - * - * @return Collection - */ - public function getAttachments(ImportJob $job): Collection; - - /** - * Handle upload for job. - * - * @param ImportJob $job - * @param string $name - * @param UploadedFile $file - * - * @return MessageBag - * @throws FireflyException - */ - public function storeFileUpload(ImportJob $job, string $name, UploadedFile $file): MessageBag; - - /** - * Store file. - * - * @param ImportJob $job - * @param string $name - * @param string $fileName - * - * @return MessageBag - */ - public function storeCLIUpload(ImportJob $job, string $name, string $fileName): MessageBag; - - /** - * @param ImportJob $job - * @param array $transactions + * @param int $index + * @param string $error * * @return ImportJob */ - public function setTransactions(ImportJob $job, array $transactions): ImportJob; - - /** - * @param ImportJob $job - * @param Tag $tag - * - * @return ImportJob - */ - public function setTag(ImportJob $job, Tag $tag): ImportJob; + public function addError(ImportJob $job, int $index, string $error): ImportJob; /** * Add message to job. @@ -93,15 +54,6 @@ interface ImportJobRepositoryInterface */ public function addErrorMessage(ImportJob $job, string $error): ImportJob; - /** - * @param ImportJob $job - * @param int $index - * @param string $error - * - * @return ImportJob - */ - public function addError(ImportJob $job, int $index, string $error): ImportJob; - /** * @param ImportJob $job * @param int $steps @@ -141,6 +93,15 @@ interface ImportJobRepositoryInterface */ public function findByKey(string $key): ImportJob; + /** + * Return all attachments for job. + * + * @param ImportJob $job + * + * @return Collection + */ + public function getAttachments(ImportJob $job): Collection; + /** * Return configuration of job. * @@ -198,14 +159,6 @@ interface ImportJobRepositoryInterface */ public function setExtendedStatus(ImportJob $job, array $array): ImportJob; - /** - * @param ImportJob $job - * @param string $status - * - * @return ImportJob - */ - public function setStatus(ImportJob $job, string $status): ImportJob; - /** * @param ImportJob $job * @param string $stage @@ -214,6 +167,14 @@ interface ImportJobRepositoryInterface */ public function setStage(ImportJob $job, string $stage): ImportJob; + /** + * @param ImportJob $job + * @param string $status + * + * @return ImportJob + */ + public function setStatus(ImportJob $job, string $status): ImportJob; + /** * @param ImportJob $job * @param int $steps @@ -222,6 +183,14 @@ interface ImportJobRepositoryInterface */ public function setStepsDone(ImportJob $job, int $steps): ImportJob; + /** + * @param ImportJob $job + * @param Tag $tag + * + * @return ImportJob + */ + public function setTag(ImportJob $job, Tag $tag): ImportJob; + /** * @param ImportJob $job * @param int $count @@ -230,11 +199,42 @@ interface ImportJobRepositoryInterface */ public function setTotalSteps(ImportJob $job, int $count): ImportJob; + /** + * @param ImportJob $job + * @param array $transactions + * + * @return ImportJob + */ + public function setTransactions(ImportJob $job, array $transactions): ImportJob; + /** * @param User $user */ public function setUser(User $user); + /** + * Store file. + * + * @param ImportJob $job + * @param string $name + * @param string $fileName + * + * @return MessageBag + */ + public function storeCLIUpload(ImportJob $job, string $name, string $fileName): MessageBag; + + /** + * Handle upload for job. + * + * @param ImportJob $job + * @param string $name + * @param UploadedFile $file + * + * @return MessageBag + * @throws FireflyException + */ + public function storeFileUpload(ImportJob $job, string $name, UploadedFile $file): MessageBag; + /** * @param ImportJob $job * @param string $status diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index e5dc693a60..6f71187a31 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; -use DB; use Exception; use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Factory\TransactionJournalMetaFactory; diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 33ea68806d..1c55997a76 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -38,15 +38,6 @@ use Illuminate\Support\MessageBag; */ interface JournalRepositoryInterface { - /** - * Find a journal by its hash. - * - * @param string $hash - * - * @return TransactionJournalMeta|null - */ - public function findByHash(string $hash): ?TransactionJournalMeta; - /** * @param TransactionJournal $journal * @param TransactionType $type @@ -77,12 +68,22 @@ interface JournalRepositoryInterface * Find a specific journal. * * @param int $journalId + * * @deprecated * * @return TransactionJournal */ public function find(int $journalId): TransactionJournal; + /** + * Find a journal by its hash. + * + * @param string $hash + * + * @return TransactionJournalMeta|null + */ + public function findByHash(string $hash): ?TransactionJournalMeta; + /** * Find a specific journal. * diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 677b48c23e..401d53d4b1 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -334,8 +334,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function getSuggestedMonthlyAmount(PiggyBank $piggyBank): string { $savePerMonth = '0'; - $repetition = $this->getRepetition($piggyBank); - if(null === $repetition) { + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { return $savePerMonth; } if (null !== $piggyBank->targetdate && $repetition->currentamount < $piggyBank->targetamount) { diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 90c545cb58..2cefe02b68 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -136,6 +136,16 @@ class TagRepository implements TagRepositoryInterface return new Tag; } + /** + * @param int $tagId + * + * @return Tag|null + */ + public function findNull(int $tagId): ?Tag + { + return $this->user->tags()->find($tagId); + } + /** * @param Tag $tag * @@ -192,6 +202,16 @@ class TagRepository implements TagRepositoryInterface return new Carbon; } + /** + * Will return the newest tag (if known) or NULL. + * + * @return Tag|null + */ + public function newestTag(): ?Tag + { + return $this->user->tags()->whereNotNull('date')->orderBy('date', 'DESC')->first(); + } + /** * @return Tag */ @@ -453,23 +473,4 @@ class TagRepository implements TagRepositoryInterface return (int)($range[0] + $extra); } - - /** - * @param int $tagId - * - * @return Tag|null - */ - public function findNull(int $tagId): ?Tag - { - return $this->user->tags()->find($tagId); - } - - /** - * Will return the newest tag (if known) or NULL. - * - * @return Tag|null - */ - public function newestTag(): ?Tag - { - return $this->user->tags()->whereNotNull('date')->orderBy('date', 'DESC')->first(); -}} +} diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index e4e8b44ba5..3b6b7cd19a 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -69,12 +69,6 @@ interface TagRepositoryInterface /** * @param int $tagId * - * @return Tag|null - */ - public function findNull(int $tagId): ?Tag; - - /** - * @param int $tagId * @deprecated * @return Tag */ @@ -87,6 +81,13 @@ interface TagRepositoryInterface */ public function findByTag(string $tag): Tag; + /** + * @param int $tagId + * + * @return Tag|null + */ + public function findNull(int $tagId): ?Tag; + /** * @param Tag $tag * @@ -117,15 +118,18 @@ interface TagRepositoryInterface /** * Will return the newest tag (if known) or NULL. + * + * @return Tag|null + */ + public function newestTag(): ?Tag; + + /** + * Will return the newest tag (if known) or NULL. + * * @return Tag|null */ public function oldestTag(): ?Tag; - /** - * Will return the newest tag (if known) or NULL. - * @return Tag|null - */ - public function newestTag(): ?Tag; /** * @param User $user */ diff --git a/app/Services/Spectre/Object/Attempt.php b/app/Services/Spectre/Object/Attempt.php index 6aecd25246..495fa9566e 100644 --- a/app/Services/Spectre/Object/Attempt.php +++ b/app/Services/Spectre/Object/Attempt.php @@ -87,9 +87,9 @@ class Attempt extends SpectreObject private $successAt; /** @var Carbon */ private $toDate; - /** @var Carbon */ + /** @var Carbon */ private $updatedAt; // undocumented -/** @var string */ + /** @var string */ private $userAgent; /** diff --git a/app/Support/Binder/SimpleJournalList.php b/app/Support/Binder/SimpleJournalList.php index c356331b48..070aa6dd06 100644 --- a/app/Support/Binder/SimpleJournalList.php +++ b/app/Support/Binder/SimpleJournalList.php @@ -28,7 +28,6 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Routing\Route; use Illuminate\Support\Collection; -use Session; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -58,7 +57,7 @@ class SimpleJournalList implements BinderInterface // prep some vars $messages = []; - $final = new Collection; + $final = new Collection; /** @var JournalRepositoryInterface $repository */ $repository = app(JournalRepositoryInterface::class); diff --git a/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php b/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php index 3a52f6af99..dc85d3b541 100644 --- a/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php +++ b/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\JobConfiguration\Bunq; + use FireflyIII\Models\ImportJob; use Illuminate\Support\MessageBag; diff --git a/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php b/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php index 144d472f86..76962d0132 100644 --- a/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php +++ b/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php @@ -35,61 +35,12 @@ use Log; */ class ConfigureUploadHandler implements FileConfigurationInterface { - /** @var ImportJob */ - private $importJob; - - /** @var ImportJobRepositoryInterface */ - private $repository; - /** @var AccountRepositoryInterface */ private $accountRepos; - - /** - * Get the data necessary to show the configuration screen. - * - * @return array - */ - public function getNextData(): array - { - $delimiters = [ - ',' => trans('form.csv_comma'), - ';' => trans('form.csv_semicolon'), - 'tab' => trans('form.csv_tab'), - ]; - $config = $this->importJob->configuration; - $config['date-format'] = $config['date-format'] ?? 'Ymd'; - $specifics = []; - $this->repository->setConfiguration($this->importJob, $config); - - // collect specifics. - foreach (config('csv.import_specifics') as $name => $className) { - $specifics[$name] = [ - 'name' => trans($className::getName()), - 'description' => trans($className::getDescription()), - ]; - } - - $data = [ - 'accounts' => [], - 'delimiters' => $delimiters, - 'specifics' => $specifics, - ]; - - return $data; - } - - /** - * @param ImportJob $importJob - */ - public function setImportJob(ImportJob $importJob): void - { - $this->importJob = $importJob; - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($importJob->user); - $this->accountRepos = app(AccountRepositoryInterface::class); - $this->accountRepos->setUser($importJob->user); - - } + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; /** * Store data associated with current stage. @@ -137,6 +88,40 @@ class ConfigureUploadHandler implements FileConfigurationInterface return new MessageBag; } + /** + * Get the data necessary to show the configuration screen. + * + * @return array + */ + public function getNextData(): array + { + $delimiters = [ + ',' => trans('form.csv_comma'), + ';' => trans('form.csv_semicolon'), + 'tab' => trans('form.csv_tab'), + ]; + $config = $this->importJob->configuration; + $config['date-format'] = $config['date-format'] ?? 'Ymd'; + $specifics = []; + $this->repository->setConfiguration($this->importJob, $config); + + // collect specifics. + foreach (config('csv.import_specifics') as $name => $className) { + $specifics[$name] = [ + 'name' => trans($className::getName()), + 'description' => trans($className::getDescription()), + ]; + } + + $data = [ + 'accounts' => [], + 'delimiters' => $delimiters, + 'specifics' => $specifics, + ]; + + return $data; + } + /** * @param array $data * @@ -159,4 +144,17 @@ class ConfigureUploadHandler implements FileConfigurationInterface return $return; } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->accountRepos->setUser($importJob->user); + + } } diff --git a/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php b/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php index 27ba527bc2..40c9ac8888 100644 --- a/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php +++ b/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php @@ -80,6 +80,7 @@ class NewFileJobHandler implements FileConfigurationInterface /** * * Get the data necessary to show the configuration screen. + * * @codeCoverageIgnore * @return array */ diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php index 1687a0689d..d4f1a60e73 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php @@ -26,12 +26,7 @@ namespace FireflyIII\Support\Import\JobConfiguration\Spectre; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Services\Spectre\Object\Customer; use FireflyIII\Services\Spectre\Object\Login; -use FireflyIII\Services\Spectre\Object\Token; -use FireflyIII\Services\Spectre\Request\CreateTokenRequest; -use FireflyIII\Services\Spectre\Request\ListCustomersRequest; -use FireflyIII\Services\Spectre\Request\NewCustomerRequest; use FireflyIII\Support\Import\Information\GetSpectreCustomerTrait; use FireflyIII\Support\Import\Information\GetSpectreTokenTrait; use Illuminate\Support\MessageBag; diff --git a/app/Support/Import/Placeholder/ColumnValue.php b/app/Support/Import/Placeholder/ColumnValue.php index edefbcc168..b55cd00a67 100644 --- a/app/Support/Import/Placeholder/ColumnValue.php +++ b/app/Support/Import/Placeholder/ColumnValue.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Import\Placeholder; /** * Class ColumnValue + * * @codeCoverageIgnore */ class ColumnValue diff --git a/app/Support/Import/Routine/File/CSVProcessor.php b/app/Support/Import/Routine/File/CSVProcessor.php index 0d338f0fb4..e5b295af2a 100644 --- a/app/Support/Import/Routine/File/CSVProcessor.php +++ b/app/Support/Import/Routine/File/CSVProcessor.php @@ -62,7 +62,7 @@ class CSVProcessor implements FileProcessorInterface // validate mapped values: /** @var MappedValuesValidator $validator */ - $validator = app(MappedValuesValidator::class); + $validator = app(MappedValuesValidator::class); $validator->setImportJob($this->importJob); $mappedValues = $validator->validate($mappingConverger->getMappedValues()); diff --git a/app/Support/Import/Routine/File/FileProcessorInterface.php b/app/Support/Import/Routine/File/FileProcessorInterface.php index c361dd9a44..13f6dfffd8 100644 --- a/app/Support/Import/Routine/File/FileProcessorInterface.php +++ b/app/Support/Import/Routine/File/FileProcessorInterface.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Routine\File; + use FireflyIII\Models\ImportJob; diff --git a/app/Support/Import/Routine/File/LineReader.php b/app/Support/Import/Routine/File/LineReader.php index 36744c5d38..927847313b 100644 --- a/app/Support/Import/Routine/File/LineReader.php +++ b/app/Support/Import/Routine/File/LineReader.php @@ -164,6 +164,7 @@ class LineReader } catch (\League\Csv\Exception $e) { throw new FireflyException(sprintf('Cannot set delimiter: %s', $e->getMessage())); } + // @codeCoverageIgnoreEnd return $reader; diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 38825077c6..cc24ef44f1 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -208,7 +208,7 @@ class Preferences /** * @param \FireflyIII\User $user * @param $name - * @param mixed $value + * @param mixed $value * * @return Preference */ diff --git a/app/Support/Steam.php b/app/Support/Steam.php index e3ab8d1d72..6c6003e6d2 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -55,7 +55,7 @@ class Steam // /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); + $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); // use system default currency: if (0 === $currencyId) { @@ -77,9 +77,9 @@ class Steam ->where('transactions.transaction_currency_id', '!=', $currencyId) ->sum('transactions.foreign_amount'); - $balance = bcadd($nativeBalance, $foreignBalance); - $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; - $balance = bcadd($balance, $virtual); + $balance = bcadd($nativeBalance, $foreignBalance); + $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; + $balance = bcadd($balance, $virtual); $cache->store($balance); return $balance; diff --git a/app/Support/Twig/Extension/Transaction.php b/app/Support/Twig/Extension/Transaction.php index 759880ff88..5294840ac7 100644 --- a/app/Support/Twig/Extension/Transaction.php +++ b/app/Support/Twig/Extension/Transaction.php @@ -26,11 +26,11 @@ use FireflyIII\Models\AccountType; use FireflyIII\Models\Attachment; use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use Lang; use Log; use Twig_Extension; -use FireflyIII\Models\TransactionJournal; /** * Class Transaction. diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index a2945e833d..e744976414 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -24,12 +24,12 @@ namespace FireflyIII\Support\Twig; use Carbon\Carbon; use FireflyIII\Models\Account; +use FireflyIII\Support\Twig\Extension\Account as AccountExtension; use League\CommonMark\CommonMarkConverter; use Route; use Twig_Extension; use Twig_SimpleFilter; use Twig_SimpleFunction; -use FireflyIII\Support\Twig\Extension\Account as AccountExtension; /** * Class TwigSupport. diff --git a/app/TransactionRules/Actions/LinkToBill.php b/app/TransactionRules/Actions/LinkToBill.php index 6ffb84cb8e..d0bd43240c 100644 --- a/app/TransactionRules/Actions/LinkToBill.php +++ b/app/TransactionRules/Actions/LinkToBill.php @@ -58,8 +58,8 @@ class LinkToBill implements ActionInterface /** @var BillRepositoryInterface $repository */ $repository = app(BillRepositoryInterface::class); $repository->setUser($this->action->rule->user); - $billName = (string)$this->action->action_value; - $bill = $repository->findByName($billName); + $billName = (string)$this->action->action_value; + $bill = $repository->findByName($billName); if (null !== $bill && $journal->transactionType->type === TransactionType::WITHDRAWAL) { $journal->bill()->associate($bill); diff --git a/app/TransactionRules/Actions/SetDestinationAccount.php b/app/TransactionRules/Actions/SetDestinationAccount.php index 1ad5fb1a46..956ae71670 100644 --- a/app/TransactionRules/Actions/SetDestinationAccount.php +++ b/app/TransactionRules/Actions/SetDestinationAccount.php @@ -101,8 +101,8 @@ class SetDestinationAccount implements ActionInterface // update destination transaction with new destination account: // get destination transaction: - $transaction = $journal->transactions()->where('amount', '>', 0)->first(); - if(null === $transaction) { + $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + if (null === $transaction) { return true; } $transaction->account_id = $this->newDestinationAccount->id; diff --git a/app/User.php b/app/User.php index 3e3150fd13..2ffd076912 100644 --- a/app/User.php +++ b/app/User.php @@ -25,7 +25,23 @@ declare(strict_types=1); namespace FireflyIII; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Models\Account; +use FireflyIII\Models\Attachment; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; use FireflyIII\Models\CurrencyExchangeRate; +use FireflyIII\Models\ExportJob; +use FireflyIII\Models\ImportJob; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\Preference; +use FireflyIII\Models\Role; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Models\Tag; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; @@ -36,26 +52,11 @@ use Laravel\Passport\HasApiTokens; use Log; use Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\Tag; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\Role; -use FireflyIII\Models\Preference; -use FireflyIII\Models\Account; -use FireflyIII\Models\PiggyBank; -use FireflyIII\Models\ImportJob; -use FireflyIII\Models\ExportJob; -use FireflyIII\Models\Category; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Bill; -use FireflyIII\Models\AvailableBudget; -use FireflyIII\Models\Attachment; /** * Class User. - * @property int $id + * + * @property int $id * @property string $email */ class User extends Authenticatable From c18046c25dc07d6d31ffcedd2b226c03c82237df Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 6 Jun 2018 21:23:17 +0200 Subject: [PATCH 006/134] Update composer libs. --- composer.json | 253 +++++++++++++++++++++++++------------------------- composer.lock | 118 +++++++---------------- 2 files changed, 162 insertions(+), 209 deletions(-) diff --git a/composer.json b/composer.json index 9abb0009b2..66b1ca985a 100644 --- a/composer.json +++ b/composer.json @@ -1,128 +1,129 @@ { - "name": "grumpydictator/firefly-iii", - "description": "Firefly III: a personal finances manager.", - "keywords": [ - "finance", - "finances", - "manager", - "management", - "euro", - "dollar", - "laravel", - "money", - "currency", - "financials", - "financial", - "budgets", - "administration", - "tool", - "tooling", - "help", - "helper", - "assistant", - "planning", - "organizing", - "bills", - "personal finance", - "budgets", - "budgeting", - "budgeting tool", - "budgeting application", - "transactions", - "self hosted", - "self-hosted", - "transfers", - "management" - ], - "license": "GPL-3.0-or-later", - "homepage": "https://github.com/firefly-iii/firefly-iii", - "type": "project", - "authors": [{ - "name": "James Cole", - "email": "thegrumpydictator@gmail.com", - "homepage": "https://github.com/firefly-iii", - "role": "Developer" - }], - "require": { - "php": ">=7.1.0", - "ext-bcmath": "*", - "ext-curl": "*", - "ext-gd": "*", - "ext-intl": "*", - "ext-xml": "*", - "ext-zip": "*", - "bacon/bacon-qr-code": "1.*", - "bunq/sdk_php": "dev-master#8c1faefc111d9b970168a1837ca165d854954941", - "davejamesmiller/laravel-breadcrumbs": "5.*", - "doctrine/dbal": "2.*", - "fideloper/proxy": "4.*", - "laravel/framework": "5.6.*", - "laravel/passport": "^5.0", - "laravelcollective/html": "5.6.*", - "league/commonmark": "0.*", - "league/csv": "9.*", - "league/fractal": "^0.17.0", - "pragmarx/google2fa": "3.*", - "pragmarx/google2fa-laravel": "0.*", - "rcrowe/twigbridge": "0.9.*", - "rmccue/requests": "1.*", - "twig/twig": "1.*" - }, - "require-dev": { - "barryvdh/laravel-ide-helper": "2.*", - "filp/whoops": "2.*", - "fzaninotto/faker": "1.*", - "johnkary/phpunit-speedtrap": "^3.0", - "mockery/mockery": "^1.0", - "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "~7.0", - "roave/security-advisories": "dev-master" - }, - "autoload": { - "classmap": [ - "database/seeds", - "database/factories" - ], - "psr-4": { - "FireflyIII\\": "app/" - } - }, - "autoload-dev": { - "psr-4": { - "Tests\\": "tests/" - } - }, - "extra": { - "laravel": { - "dont-discover": [] - } - }, - "scripts": { - "pre-install-cmd": [ - "@php -r \"if (!(getenv('DYNO'))===false){file_exists('.env') || copy('.env.heroku', '.env');}\"" - ], - "post-root-package-install": [ - "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" - ], - "post-create-project-cmd": [ - "@php artisan key:generate" - ], - "post-autoload-dump": [ - "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" - ], - "post-update-cmd": [ - "php artisan firefly:upgrade-database", - "php artisan firefly:verify", - "php artisan firefly:instructions update", - "php artisan passport:install" - ], - "post-install-cmd": [ - "php artisan firefly:instructions install" - ] - }, - "config": { - "preferred-install": "dist", - "sort-packages": true, - "optimize-autoloader": true - } + "name": "grumpydictator/firefly-iii", + "description": "Firefly III: a personal finances manager.", + "keywords": [ + "finance", + "finances", + "manager", + "management", + "euro", + "dollar", + "laravel", + "money", + "currency", + "financials", + "financial", + "budgets", + "administration", + "tool", + "tooling", + "help", + "helper", + "assistant", + "planning", + "organizing", + "bills", + "personal finance", + "budgets", + "budgeting", + "budgeting tool", + "budgeting application", + "transactions", + "self hosted", + "self-hosted", + "transfers", + "management" + ], + "license": "GPL-3.0-or-later", + "homepage": "https://github.com/firefly-iii/firefly-iii", + "type": "project", + "authors": [ + { + "name": "James Cole", + "email": "thegrumpydictator@gmail.com", + "homepage": "https://github.com/firefly-iii", + "role": "Developer" + } + ], + "require": { + "php": ">=7.1.0", + "ext-bcmath": "*", + "ext-curl": "*", + "ext-gd": "*", + "ext-intl": "*", + "ext-xml": "*", + "ext-zip": "*", + "bacon/bacon-qr-code": "1.*", + "bunq/sdk_php": "dev-master#8c1faefc111d9b970168a1837ca165d854954941", + "davejamesmiller/laravel-breadcrumbs": "5.*", + "doctrine/dbal": "2.*", + "fideloper/proxy": "4.*", + "laravel/framework": "5.6.*", + "laravel/passport": "^5.0", + "laravelcollective/html": "5.6.*", + "league/commonmark": "0.*", + "league/csv": "9.*", + "league/fractal": "^0.17.0", + "pragmarx/google2fa": "3.*", + "pragmarx/google2fa-laravel": "0.*", + "rcrowe/twigbridge": "0.9.*", + "twig/twig": "1.*" + }, + "require-dev": { + "barryvdh/laravel-ide-helper": "2.*", + "filp/whoops": "2.*", + "fzaninotto/faker": "1.*", + "johnkary/phpunit-speedtrap": "^3.0", + "mockery/mockery": "^1.0", + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "~7.0", + "roave/security-advisories": "dev-master" + }, + "autoload": { + "classmap": [ + "database/seeds", + "database/factories" + ], + "psr-4": { + "FireflyIII\\": "app/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "scripts": { + "pre-install-cmd": [ + "@php -r \"if (!(getenv('DYNO'))===false){file_exists('.env') || copy('.env.heroku', '.env');}\"" + ], + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate" + ], + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" + ], + "post-update-cmd": [ + "php artisan firefly:upgrade-database", + "php artisan firefly:verify", + "php artisan firefly:instructions update", + "php artisan passport:install" + ], + "post-install-cmd": [ + "php artisan firefly:instructions install" + ] + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "optimize-autoloader": true + } } diff --git a/composer.lock b/composer.lock index e1f8241676..6cc0129b14 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "312e75271d4570f83bc7554892a3b6ab", + "content-hash": "dcaf20ad3436c4fc4cbebeee09c9de1f", "packages": [ { "name": "bacon/bacon-qr-code", @@ -717,16 +717,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v2.1.0", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "3f00985deec8df53d4cc1e5c33619bda1ee309a5" + "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/3f00985deec8df53d4cc1e5c33619bda1ee309a5", - "reference": "3f00985deec8df53d4cc1e5c33619bda1ee309a5", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/92a2c3768d50e21a1f26a53cb795ce72806266c5", + "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5", "shasum": "" }, "require": { @@ -762,7 +762,7 @@ "cron", "schedule" ], - "time": "2018-04-06T15:51:55+00:00" + "time": "2018-06-06T03:12:17+00:00" }, { "name": "egulias/email-validator", @@ -1150,16 +1150,16 @@ }, { "name": "laravel/framework", - "version": "v5.6.23", + "version": "v5.6.24", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f547f0a71a12763d1adb8493237d541c9e3a5d10" + "reference": "56290edeb0d8051826d40b4cbd8ed3c30348b2b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f547f0a71a12763d1adb8493237d541c9e3a5d10", - "reference": "f547f0a71a12763d1adb8493237d541c9e3a5d10", + "url": "https://api.github.com/repos/laravel/framework/zipball/56290edeb0d8051826d40b4cbd8ed3c30348b2b5", + "reference": "56290edeb0d8051826d40b4cbd8ed3c30348b2b5", "shasum": "" }, "require": { @@ -1285,7 +1285,7 @@ "framework", "laravel" ], - "time": "2018-05-22T14:55:57+00:00" + "time": "2018-06-04T14:51:03+00:00" }, { "name": "laravel/passport", @@ -2079,16 +2079,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.12", + "version": "v2.0.14", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" + "reference": "f6ce7dd93628088e1017fb5dd73b0b9fec7df9e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/f6ce7dd93628088e1017fb5dd73b0b9fec7df9e5", + "reference": "f6ce7dd93628088e1017fb5dd73b0b9fec7df9e5", "shasum": "" }, "require": { @@ -2120,10 +2120,11 @@ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ "csprng", + "polyfill", "pseudorandom", "random" ], - "time": "2018-04-04T21:24:14+00:00" + "time": "2018-06-06T17:40:22+00:00" }, { "name": "phpseclib/phpseclib", @@ -2695,55 +2696,6 @@ ], "time": "2018-02-08T15:59:23+00:00" }, - { - "name": "rmccue/requests", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/rmccue/Requests.git", - "reference": "87932f52ffad70504d93f04f15690cf16a089546" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546", - "reference": "87932f52ffad70504d93f04f15690cf16a089546", - "shasum": "" - }, - "require": { - "php": ">=5.2" - }, - "require-dev": { - "requests/test-server": "dev-master" - }, - "type": "library", - "autoload": { - "psr-0": { - "Requests": "library/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Ryan McCue", - "homepage": "http://ryanmccue.info" - } - ], - "description": "A HTTP library written in PHP, for human beings.", - "homepage": "http://github.com/rmccue/Requests", - "keywords": [ - "curl", - "fsockopen", - "http", - "idna", - "ipv6", - "iri", - "sockets" - ], - "time": "2016-10-13T00:11:37+00:00" - }, { "name": "swiftmailer/swiftmailer", "version": "v6.0.2", @@ -4232,16 +4184,16 @@ }, { "name": "filp/whoops", - "version": "2.1.14", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "c6081b8838686aa04f1e83ba7e91f78b7b2a23e6" + "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/c6081b8838686aa04f1e83ba7e91f78b7b2a23e6", - "reference": "c6081b8838686aa04f1e83ba7e91f78b7b2a23e6", + "url": "https://api.github.com/repos/filp/whoops/zipball/181c4502d8f34db7aed7bfe88d4f87875b8e947a", + "reference": "181c4502d8f34db7aed7bfe88d4f87875b8e947a", "shasum": "" }, "require": { @@ -4249,9 +4201,9 @@ "psr/log": "^1.0.1" }, "require-dev": { - "mockery/mockery": "0.9.*", + "mockery/mockery": "^0.9 || ^1.0", "phpunit/phpunit": "^4.8.35 || ^5.7", - "symfony/var-dumper": "^2.6 || ^3.0" + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -4260,7 +4212,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -4289,7 +4241,7 @@ "throwable", "whoops" ], - "time": "2017-11-23T18:22:44+00:00" + "time": "2018-03-03T17:56:25+00:00" }, { "name": "fzaninotto/faker", @@ -5202,16 +5154,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.2.2", + "version": "7.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3cf0836680bf5c365c627e8566d46c9e1f544db9" + "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cf0836680bf5c365c627e8566d46c9e1f544db9", - "reference": "3cf0836680bf5c365c627e8566d46c9e1f544db9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00bc0b93f0ff4f557e9ea766557fde96da9a03dd", + "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd", "shasum": "" }, "require": { @@ -5282,7 +5234,7 @@ "testing", "xunit" ], - "time": "2018-06-01T07:54:27+00:00" + "time": "2018-06-05T03:40:05+00:00" }, { "name": "roave/security-advisories", @@ -5290,12 +5242,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "4d93302eb93402083f5abe72002fe8dc35e12dae" + "reference": "ff09cbe142c9195e1ed85a409d7940d43d7306c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/4d93302eb93402083f5abe72002fe8dc35e12dae", - "reference": "4d93302eb93402083f5abe72002fe8dc35e12dae", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ff09cbe142c9195e1ed85a409d7940d43d7306c7", + "reference": "ff09cbe142c9195e1ed85a409d7940d43d7306c7", "shasum": "" }, "conflict": { @@ -5385,7 +5337,7 @@ "symfony/routing": ">=2,<2.0.19", "symfony/security": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", @@ -5448,7 +5400,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-05-30T02:58:56+00:00" + "time": "2018-06-06T08:36:30+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", From d22353b13d69b6bcf1088bde56726597b7c7854a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 8 Jun 2018 19:35:51 +0200 Subject: [PATCH 007/134] Update screenshots and Sandstorm files. --- .sandstorm/sandstorm-files.list | 713 ++++++++++++++++++------ .sandstorm/sandstorm-pkgdef.capnp | 6 +- .sandstorm/screenshots/screenshot-1.png | Bin 122635 -> 176915 bytes .sandstorm/screenshots/screenshot-2.png | Bin 124366 -> 174991 bytes .sandstorm/screenshots/screenshot-3.png | Bin 109933 -> 167155 bytes composer.lock | 19 +- 6 files changed, 561 insertions(+), 177 deletions(-) diff --git a/.sandstorm/sandstorm-files.list b/.sandstorm/sandstorm-files.list index 06a302593d..d311a31fcf 100644 --- a/.sandstorm/sandstorm-files.list +++ b/.sandstorm/sandstorm-files.list @@ -244,10 +244,12 @@ opt/app/app/Api/V1/Controllers/AboutController.php opt/app/app/Api/V1/Controllers/AccountController.php opt/app/app/Api/V1/Controllers/BillController.php opt/app/app/Api/V1/Controllers/Controller.php +opt/app/app/Api/V1/Controllers/CurrencyController.php opt/app/app/Api/V1/Controllers/TransactionController.php opt/app/app/Api/V1/Controllers/UserController.php opt/app/app/Api/V1/Requests/AccountRequest.php opt/app/app/Api/V1/Requests/BillRequest.php +opt/app/app/Api/V1/Requests/CurrencyRequest.php opt/app/app/Api/V1/Requests/Request.php opt/app/app/Api/V1/Requests/TransactionRequest.php opt/app/app/Api/V1/Requests/UserRequest.php @@ -321,6 +323,7 @@ opt/app/app/Generator/Report/Support.php opt/app/app/Generator/Report/Tag/MonthReportGenerator.php opt/app/app/Generator/Report/Tag/MultiYearReportGenerator.php opt/app/app/Generator/Report/Tag/YearReportGenerator.php +opt/app/app/Handlers/Events/APIEventHandler.php opt/app/app/Handlers/Events/AdminEventHandler.php opt/app/app/Handlers/Events/StoredJournalEventHandler.php opt/app/app/Handlers/Events/UpdatedJournalEventHandler.php @@ -348,6 +351,7 @@ opt/app/app/Helpers/Filter/NegativeAmountFilter.php opt/app/app/Helpers/Filter/OpposingAccountFilter.php opt/app/app/Helpers/Filter/PositiveAmountFilter.php opt/app/app/Helpers/Filter/SplitIndicatorFilter.php +opt/app/app/Helpers/Filter/TransactionViewFilter.php opt/app/app/Helpers/Filter/TransferFilter.php opt/app/app/Helpers/FiscalHelper.php opt/app/app/Helpers/FiscalHelperInterface.php @@ -393,10 +397,10 @@ opt/app/app/Http/Controllers/DebugController.php opt/app/app/Http/Controllers/ExportController.php opt/app/app/Http/Controllers/HelpController.php opt/app/app/Http/Controllers/HomeController.php -opt/app/app/Http/Controllers/Import/ConfigurationController.php opt/app/app/Http/Controllers/Import/IndexController.php +opt/app/app/Http/Controllers/Import/JobConfigurationController.php +opt/app/app/Http/Controllers/Import/JobStatusController.php opt/app/app/Http/Controllers/Import/PrerequisitesController.php -opt/app/app/Http/Controllers/Import/StatusController.php opt/app/app/Http/Controllers/JavascriptController.php opt/app/app/Http/Controllers/Json/AutoCompleteController.php opt/app/app/Http/Controllers/Json/BoxController.php @@ -480,17 +484,17 @@ opt/app/app/Http/Requests/UserFormRequest.php opt/app/app/Http/Requests/UserRegistrationRequest.php opt/app/app/Import/Configuration/BunqConfigurator.php opt/app/app/Import/Configuration/ConfiguratorInterface.php -opt/app/app/Import/Configuration/FileConfigurator.php -opt/app/app/Import/Configuration/SpectreConfigurator.php opt/app/app/Import/Converter/Amount.php opt/app/app/Import/Converter/AmountCredit.php opt/app/app/Import/Converter/AmountDebit.php opt/app/app/Import/Converter/ConverterInterface.php opt/app/app/Import/Converter/INGDebitCredit.php opt/app/app/Import/Converter/RabobankDebitCredit.php -opt/app/app/Import/FileProcessor/CsvProcessor.php -opt/app/app/Import/FileProcessor/FileProcessorInterface.php -opt/app/app/Import/Logging/CommandHandler.php +opt/app/app/Import/JobConfiguration/BunqJobConfiguration.php +opt/app/app/Import/JobConfiguration/FakeJobConfiguration.php +opt/app/app/Import/JobConfiguration/FileJobConfiguration.php +opt/app/app/Import/JobConfiguration/JobConfigurationInterface.php +opt/app/app/Import/JobConfiguration/SpectreJobConfiguration.php opt/app/app/Import/Mapper/AssetAccountIbans.php opt/app/app/Import/Mapper/AssetAccounts.php opt/app/app/Import/Mapper/Bills.php @@ -511,10 +515,12 @@ opt/app/app/Import/Object/ImportCategory.php opt/app/app/Import/Object/ImportCurrency.php opt/app/app/Import/Object/ImportJournal.php opt/app/app/Import/Prerequisites/BunqPrerequisites.php +opt/app/app/Import/Prerequisites/FakePrerequisites.php opt/app/app/Import/Prerequisites/FilePrerequisites.php opt/app/app/Import/Prerequisites/PrerequisitesInterface.php opt/app/app/Import/Prerequisites/SpectrePrerequisites.php opt/app/app/Import/Routine/BunqRoutine.php +opt/app/app/Import/Routine/FakeRoutine.php opt/app/app/Import/Routine/FileRoutine.php opt/app/app/Import/Routine/RoutineInterface.php opt/app/app/Import/Routine/SpectreRoutine.php @@ -524,14 +530,15 @@ opt/app/app/Import/Specifics/PresidentsChoice.php opt/app/app/Import/Specifics/RabobankDescription.php opt/app/app/Import/Specifics/SnsDescription.php opt/app/app/Import/Specifics/SpecificInterface.php -opt/app/app/Import/Storage/ImportStorage.php -opt/app/app/Import/Storage/ImportSupport.php +opt/app/app/Import/Storage/ImportArrayStorage.php opt/app/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php opt/app/app/Jobs/ExecuteRuleOnExistingTransactions.php opt/app/app/Jobs/Job.php opt/app/app/Jobs/MailError.php +opt/app/app/Mail/AccessTokenCreatedMail.php opt/app/app/Mail/AdminTestMail.php opt/app/app/Mail/ConfirmEmailChangeMail.php +opt/app/app/Mail/OAuthTokenCreatedMail.php opt/app/app/Mail/RegisteredUser.php opt/app/app/Mail/RequestedNewPassword.php opt/app/app/Mail/UndoEmailChangeMail.php @@ -627,46 +634,17 @@ opt/app/app/Repositories/User/UserRepositoryInterface.php opt/app/app/Rules/BelongsUser.php opt/app/app/Rules/UniqueIban.php opt/app/app/Rules/ValidTransactions.php -opt/app/app/Services/Bunq/Id/BunqId.php -opt/app/app/Services/Bunq/Id/DeviceServerId.php -opt/app/app/Services/Bunq/Id/DeviceSessionId.php -opt/app/app/Services/Bunq/Id/InstallationId.php -opt/app/app/Services/Bunq/Object/Alias.php -opt/app/app/Services/Bunq/Object/Amount.php -opt/app/app/Services/Bunq/Object/Avatar.php -opt/app/app/Services/Bunq/Object/BunqObject.php -opt/app/app/Services/Bunq/Object/DeviceServer.php -opt/app/app/Services/Bunq/Object/Image.php -opt/app/app/Services/Bunq/Object/LabelMonetaryAccount.php -opt/app/app/Services/Bunq/Object/LabelUser.php -opt/app/app/Services/Bunq/Object/MonetaryAccountBank.php -opt/app/app/Services/Bunq/Object/MonetaryAccountProfile.php -opt/app/app/Services/Bunq/Object/MonetaryAccountSetting.php -opt/app/app/Services/Bunq/Object/NotificationFilter.php -opt/app/app/Services/Bunq/Object/Payment.php -opt/app/app/Services/Bunq/Object/ServerPublicKey.php -opt/app/app/Services/Bunq/Object/UserCompany.php -opt/app/app/Services/Bunq/Object/UserLight.php -opt/app/app/Services/Bunq/Object/UserPerson.php -opt/app/app/Services/Bunq/Request/BunqRequest.php -opt/app/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php -opt/app/app/Services/Bunq/Request/DeviceServerRequest.php -opt/app/app/Services/Bunq/Request/DeviceSessionRequest.php -opt/app/app/Services/Bunq/Request/InstallationTokenRequest.php -opt/app/app/Services/Bunq/Request/ListDeviceServerRequest.php -opt/app/app/Services/Bunq/Request/ListMonetaryAccountRequest.php -opt/app/app/Services/Bunq/Request/ListPaymentRequest.php -opt/app/app/Services/Bunq/Request/ListUserRequest.php -opt/app/app/Services/Bunq/Token/BunqToken.php -opt/app/app/Services/Bunq/Token/InstallationToken.php -opt/app/app/Services/Bunq/Token/SessionToken.php +opt/app/app/Services/Bunq/ApiContext.php +opt/app/app/Services/Bunq/MonetaryAccount.php +opt/app/app/Services/Bunq/Payment.php opt/app/app/Services/Currency/ExchangeRateInterface.php -opt/app/app/Services/Currency/FixerIO.php opt/app/app/Services/Currency/FixerIOv2.php opt/app/app/Services/Github/Object/GithubObject.php opt/app/app/Services/Github/Object/Release.php opt/app/app/Services/Github/Request/GithubRequest.php opt/app/app/Services/Github/Request/UpdateRequest.php +opt/app/app/Services/IP/IPRetrievalInterface.php +opt/app/app/Services/IP/IpifyOrg.php opt/app/app/Services/Internal/Destroy/AccountDestroyService.php opt/app/app/Services/Internal/Destroy/BillDestroyService.php opt/app/app/Services/Internal/Destroy/CategoryDestroyService.php @@ -683,11 +661,11 @@ opt/app/app/Services/Internal/Update/CategoryUpdateService.php opt/app/app/Services/Internal/Update/CurrencyUpdateService.php opt/app/app/Services/Internal/Update/JournalUpdateService.php opt/app/app/Services/Internal/Update/TransactionUpdateService.php -opt/app/app/Services/Password/PwndVerifier.php opt/app/app/Services/Password/PwndVerifierV2.php opt/app/app/Services/Password/Verifier.php opt/app/app/Services/Spectre/Exception/DuplicatedCustomerException.php opt/app/app/Services/Spectre/Exception/SpectreException.php +opt/app/app/Services/Spectre/Exception/WrongRequestFormatException.php opt/app/app/Services/Spectre/Object/Account.php opt/app/app/Services/Spectre/Object/Attempt.php opt/app/app/Services/Spectre/Object/Customer.php @@ -711,13 +689,14 @@ opt/app/app/Support/Binder/BudgetList.php opt/app/app/Support/Binder/CategoryList.php opt/app/app/Support/Binder/CurrencyCode.php opt/app/app/Support/Binder/Date.php +opt/app/app/Support/Binder/ImportProvider.php opt/app/app/Support/Binder/JournalList.php +opt/app/app/Support/Binder/SimpleJournalList.php opt/app/app/Support/Binder/TagList.php opt/app/app/Support/Binder/UnfinishedJournal.php opt/app/app/Support/CacheProperties.php opt/app/app/Support/ChartColour.php opt/app/app/Support/Domain.php -opt/app/app/Support/Events/BillScanner.php opt/app/app/Support/ExpandedForm.php opt/app/app/Support/Facades/Amount.php opt/app/app/Support/Facades/ExpandedForm.php @@ -726,15 +705,44 @@ opt/app/app/Support/Facades/Navigation.php opt/app/app/Support/Facades/Preferences.php opt/app/app/Support/Facades/Steam.php opt/app/app/Support/FireflyConfig.php -opt/app/app/Support/Import/Configuration/Bunq/HaveAccounts.php -opt/app/app/Support/Import/Configuration/ConfigurationInterface.php -opt/app/app/Support/Import/Configuration/File/Initial.php -opt/app/app/Support/Import/Configuration/File/Map.php -opt/app/app/Support/Import/Configuration/File/Roles.php -opt/app/app/Support/Import/Configuration/File/UploadConfig.php -opt/app/app/Support/Import/Configuration/Spectre/HaveAccounts.php opt/app/app/Support/Import/Information/BunqInformation.php +opt/app/app/Support/Import/Information/GetSpectreCustomerTrait.php +opt/app/app/Support/Import/Information/GetSpectreTokenTrait.php opt/app/app/Support/Import/Information/InformationInterface.php +opt/app/app/Support/Import/JobConfiguration/Bunq/BunqJobConfigurationInterface.php +opt/app/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php +opt/app/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php +opt/app/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php +opt/app/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php +opt/app/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php +opt/app/app/Support/Import/JobConfiguration/File/FileConfigurationInterface.php +opt/app/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/AuthenticatedHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/DoAuthenticateHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/NewSpectreJobHandler.php +opt/app/app/Support/Import/JobConfiguration/Spectre/SpectreJobConfigurationInterface.php +opt/app/app/Support/Import/Placeholder/ColumnValue.php +opt/app/app/Support/Import/Placeholder/ImportTransaction.php +opt/app/app/Support/Import/Routine/Bunq/StageImportDataHandler.php +opt/app/app/Support/Import/Routine/Bunq/StageNewHandler.php +opt/app/app/Support/Import/Routine/Fake/StageAhoyHandler.php +opt/app/app/Support/Import/Routine/Fake/StageFinalHandler.php +opt/app/app/Support/Import/Routine/Fake/StageNewHandler.php +opt/app/app/Support/Import/Routine/File/AssetAccountMapper.php +opt/app/app/Support/Import/Routine/File/CSVProcessor.php +opt/app/app/Support/Import/Routine/File/CurrencyMapper.php +opt/app/app/Support/Import/Routine/File/FileProcessorInterface.php +opt/app/app/Support/Import/Routine/File/ImportableConverter.php +opt/app/app/Support/Import/Routine/File/ImportableCreator.php +opt/app/app/Support/Import/Routine/File/LineReader.php +opt/app/app/Support/Import/Routine/File/MappedValuesValidator.php +opt/app/app/Support/Import/Routine/File/MappingConverger.php +opt/app/app/Support/Import/Routine/File/OpposingAccountMapper.php +opt/app/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php +opt/app/app/Support/Import/Routine/Spectre/StageImportDataHandler.php +opt/app/app/Support/Import/Routine/Spectre/StageNewHandler.php opt/app/app/Support/Models/TransactionJournalTrait.php opt/app/app/Support/Navigation.php opt/app/app/Support/Preferences.php @@ -743,10 +751,12 @@ opt/app/app/Support/Search/Search.php opt/app/app/Support/Search/SearchInterface.php opt/app/app/Support/Steam.php opt/app/app/Support/Twig/AmountFormat.php +opt/app/app/Support/Twig/Extension/Account.php opt/app/app/Support/Twig/Extension/Transaction.php opt/app/app/Support/Twig/Extension/TransactionJournal.php opt/app/app/Support/Twig/General.php opt/app/app/Support/Twig/Journal.php +opt/app/app/Support/Twig/Loader/AccountLoader.php opt/app/app/Support/Twig/Loader/TransactionJournalLoader.php opt/app/app/Support/Twig/Loader/TransactionLoader.php opt/app/app/Support/Twig/PiggyBank.php @@ -760,6 +770,7 @@ opt/app/app/TransactionRules/Actions/AppendNotes.php opt/app/app/TransactionRules/Actions/ClearBudget.php opt/app/app/TransactionRules/Actions/ClearCategory.php opt/app/app/TransactionRules/Actions/ClearNotes.php +opt/app/app/TransactionRules/Actions/LinkToBill.php opt/app/app/TransactionRules/Actions/PrependDescription.php opt/app/app/TransactionRules/Actions/PrependNotes.php opt/app/app/TransactionRules/Actions/RemoveAllTags.php @@ -780,6 +791,7 @@ opt/app/app/TransactionRules/Triggers/AmountLess.php opt/app/app/TransactionRules/Triggers/AmountMore.php opt/app/app/TransactionRules/Triggers/BudgetIs.php opt/app/app/TransactionRules/Triggers/CategoryIs.php +opt/app/app/TransactionRules/Triggers/CurrencyIs.php opt/app/app/TransactionRules/Triggers/DescriptionContains.php opt/app/app/TransactionRules/Triggers/DescriptionEnds.php opt/app/app/TransactionRules/Triggers/DescriptionIs.php @@ -814,6 +826,7 @@ opt/app/app/Transformers/AttachmentTransformer.php opt/app/app/Transformers/BillTransformer.php opt/app/app/Transformers/BudgetTransformer.php opt/app/app/Transformers/CategoryTransformer.php +opt/app/app/Transformers/CurrencyTransformer.php opt/app/app/Transformers/JournalMetaTransformer.php opt/app/app/Transformers/PiggyBankEventTransformer.php opt/app/app/Transformers/PiggyBankTransformer.php @@ -852,7 +865,6 @@ opt/app/config/twigbridge.php opt/app/config/upgrade.php opt/app/config/view.php opt/app/database/factories/ModelFactory.php -opt/app/database/migrations opt/app/database/migrations/2016_06_16_000000_create_support_tables.php opt/app/database/migrations/2016_06_16_000001_create_users_table.php opt/app/database/migrations/2016_06_16_000002_create_main_tables.php @@ -873,6 +885,8 @@ opt/app/database/migrations/2018_01_01_000003_create_oauth_refresh_tokens_table. opt/app/database/migrations/2018_01_01_000004_create_oauth_clients_table.php opt/app/database/migrations/2018_01_01_000005_create_oauth_personal_access_clients_table.php opt/app/database/migrations/2018_03_19_141348_changes_for_v472.php +opt/app/database/migrations/2018_04_07_210913_changes_for_v473.php +opt/app/database/migrations/2018_04_29_174524_changes_for_v474.php opt/app/database/seeds/AccountTypeSeeder.php opt/app/database/seeds/ConfigSeeder.php opt/app/database/seeds/DatabaseSeeder.php @@ -884,6 +898,7 @@ opt/app/docker-compose.yml opt/app/index.php opt/app/package-lock.json opt/app/public/.htaccess +opt/app/public/.well-known/security.txt opt/app/public/android-chrome-192x192.png opt/app/public/android-chrome-512x512.png opt/app/public/apple-touch-icon.png @@ -1038,9 +1053,12 @@ opt/app/public/images/loading-small.gif opt/app/public/images/loading-wide.gif opt/app/public/images/logos/bunq.png opt/app/public/images/logos/csv.png +opt/app/public/images/logos/fake.png opt/app/public/images/logos/file.png opt/app/public/images/logos/plaid.png +opt/app/public/images/logos/quovo.png opt/app/public/images/logos/spectre.png +opt/app/public/images/logos/yodlee.png opt/app/public/images/page_green.png opt/app/public/images/page_white_acrobat.png opt/app/public/index.php @@ -1065,10 +1083,23 @@ opt/app/public/js/ff/export/index.js opt/app/public/js/ff/firefly.js opt/app/public/js/ff/guest.js opt/app/public/js/ff/help.js +opt/app/public/js/ff/import/file/configure-upload.js opt/app/public/js/ff/import/status.js +opt/app/public/js/ff/import/status_v2.js opt/app/public/js/ff/index.js opt/app/public/js/ff/install/index.js opt/app/public/js/ff/intro/intro.js +opt/app/public/js/ff/moment/de_DE.js +opt/app/public/js/ff/moment/en_US.js +opt/app/public/js/ff/moment/es_ES.js +opt/app/public/js/ff/moment/fr_FR.js +opt/app/public/js/ff/moment/id_ID.js +opt/app/public/js/ff/moment/it_IT.js +opt/app/public/js/ff/moment/nl_NL.js +opt/app/public/js/ff/moment/pl_PL.js +opt/app/public/js/ff/moment/pt_BR.js +opt/app/public/js/ff/moment/ru_RU.js +opt/app/public/js/ff/moment/tr_TR.js opt/app/public/js/ff/piggy-banks/create.js opt/app/public/js/ff/piggy-banks/edit.js opt/app/public/js/ff/piggy-banks/index.js @@ -1349,6 +1380,7 @@ opt/app/resources/views/admin/users/index.twig opt/app/resources/views/admin/users/show.twig opt/app/resources/views/attachments/delete.twig opt/app/resources/views/attachments/edit.twig +opt/app/resources/views/attachments/index.twig opt/app/resources/views/auth/login.twig opt/app/resources/views/auth/lost-two-factor.twig opt/app/resources/views/auth/passwords/email.twig @@ -1389,6 +1421,8 @@ opt/app/resources/views/demo/no-demo-text.twig opt/app/resources/views/demo/piggy-banks/index.twig opt/app/resources/views/demo/reports/index.twig opt/app/resources/views/demo/transactions/index.twig +opt/app/resources/views/emails/access-token-created-html.twig +opt/app/resources/views/emails/access-token-created-text.twig opt/app/resources/views/emails/admin-test-html.twig opt/app/resources/views/emails/admin-test-text.twig opt/app/resources/views/emails/confirm-account-html.twig @@ -1401,6 +1435,8 @@ opt/app/resources/views/emails/footer-html.twig opt/app/resources/views/emails/footer-text.twig opt/app/resources/views/emails/header-html.twig opt/app/resources/views/emails/header-text.twig +opt/app/resources/views/emails/oauth-client-created-html.twig +opt/app/resources/views/emails/oauth-client-created-text.twig opt/app/resources/views/emails/password-html.twig opt/app/resources/views/emails/password-text.twig opt/app/resources/views/emails/registered-html.twig @@ -1413,8 +1449,10 @@ opt/app/resources/views/errors/500.twig opt/app/resources/views/errors/503.twig opt/app/resources/views/errors/FireflyException.twig opt/app/resources/views/export/index.twig +opt/app/resources/views/form/amount-no-currency.twig opt/app/resources/views/form/amount-small.twig opt/app/resources/views/form/amount.twig +opt/app/resources/views/form/assetAccountCheckList.twig opt/app/resources/views/form/balance.twig opt/app/resources/views/form/checkbox.twig opt/app/resources/views/form/date.twig @@ -1423,7 +1461,6 @@ opt/app/resources/views/form/file.twig opt/app/resources/views/form/help.twig opt/app/resources/views/form/integer.twig opt/app/resources/views/form/location.twig -opt/app/resources/views/form/multiCheckbox.twig opt/app/resources/views/form/multiRadio.twig opt/app/resources/views/form/non-selectable-amount.twig opt/app/resources/views/form/number.twig @@ -1435,14 +1472,20 @@ opt/app/resources/views/form/tags.twig opt/app/resources/views/form/text.twig opt/app/resources/views/form/textarea.twig opt/app/resources/views/import/bank/form.twig -opt/app/resources/views/import/bunq/accounts.twig +opt/app/resources/views/import/bunq/choose-accounts.twig opt/app/resources/views/import/bunq/prerequisites.twig -opt/app/resources/views/import/file/initial.twig +opt/app/resources/views/import/fake/apply-rules.twig +opt/app/resources/views/import/fake/enter-album.twig +opt/app/resources/views/import/fake/enter-artist.twig +opt/app/resources/views/import/fake/enter-song.twig +opt/app/resources/views/import/fake/prerequisites.twig +opt/app/resources/views/import/file/configure-upload.twig opt/app/resources/views/import/file/map.twig +opt/app/resources/views/import/file/new.twig opt/app/resources/views/import/file/roles.twig -opt/app/resources/views/import/file/upload-config.twig opt/app/resources/views/import/index.twig opt/app/resources/views/import/spectre/accounts.twig +opt/app/resources/views/import/spectre/choose-login.twig opt/app/resources/views/import/spectre/prerequisites.twig opt/app/resources/views/import/spectre/redirect.twig opt/app/resources/views/import/status.twig @@ -1561,7 +1604,6 @@ opt/app/routes/breadcrumbs.php opt/app/routes/channels.php opt/app/routes/console.php opt/app/routes/web.php -opt/app/security.txt opt/app/server.php opt/app/storage opt/app/vendor/autoload.php @@ -1625,9 +1667,339 @@ opt/app/vendor/bacon/bacon-qr-code/tests/BaconQrCode/Encoder/MatrixUtilTest.php opt/app/vendor/bacon/bacon-qr-code/tests/BaconQrCode/Renderer/Text/HtmlTest.php opt/app/vendor/bacon/bacon-qr-code/tests/BaconQrCode/Renderer/Text/TextTest.php opt/app/vendor/bacon/bacon-qr-code/tests/bootstrap.php +opt/app/vendor/bin/bunq-install opt/app/vendor/bin/commonmark opt/app/vendor/bin/doctrine-dbal opt/app/vendor/bin/generate-defuse-key +opt/app/vendor/bin/var-dump-server +opt/app/vendor/bunq/sdk_php/.github/ISSUE_TEMPLATE.md +opt/app/vendor/bunq/sdk_php/.github/PULL_REQUEST_TEMPLATE.md +opt/app/vendor/bunq/sdk_php/.gitmodules +opt/app/vendor/bunq/sdk_php/.zappr.yaml +opt/app/vendor/bunq/sdk_php/LICENSE.md +opt/app/vendor/bunq/sdk_php/bin/bunq-install +opt/app/vendor/bunq/sdk_php/composer.json +opt/app/vendor/bunq/sdk_php/src/Context/ApiContext.php +opt/app/vendor/bunq/sdk_php/src/Context/BunqContext.php +opt/app/vendor/bunq/sdk_php/src/Context/InstallationContext.php +opt/app/vendor/bunq/sdk_php/src/Context/SessionContext.php +opt/app/vendor/bunq/sdk_php/src/Context/UserContext.php +opt/app/vendor/bunq/sdk_php/src/Exception/ApiException.php +opt/app/vendor/bunq/sdk_php/src/Exception/BadRequestException.php +opt/app/vendor/bunq/sdk_php/src/Exception/BunqException.php +opt/app/vendor/bunq/sdk_php/src/Exception/EXCEPTION.md +opt/app/vendor/bunq/sdk_php/src/Exception/ExceptionFactory.php +opt/app/vendor/bunq/sdk_php/src/Exception/ForbiddenException.php +opt/app/vendor/bunq/sdk_php/src/Exception/MethodNotAllowedException.php +opt/app/vendor/bunq/sdk_php/src/Exception/NotFoundException.php +opt/app/vendor/bunq/sdk_php/src/Exception/PleaseContactBunqException.php +opt/app/vendor/bunq/sdk_php/src/Exception/SecurityException.php +opt/app/vendor/bunq/sdk_php/src/Exception/TooManyRequestsException.php +opt/app/vendor/bunq/sdk_php/src/Exception/UnauthorizedException.php +opt/app/vendor/bunq/sdk_php/src/Exception/UnknownApiErrorException.php +opt/app/vendor/bunq/sdk_php/src/Http/ApiClient.php +opt/app/vendor/bunq/sdk_php/src/Http/BunqResponse.php +opt/app/vendor/bunq/sdk_php/src/Http/BunqResponseRaw.php +opt/app/vendor/bunq/sdk_php/src/Http/Certificate/api.bunq.com.pubkey.pem +opt/app/vendor/bunq/sdk_php/src/Http/Certificate/sandbox.public.api.bunq.com.pubkey.pem +opt/app/vendor/bunq/sdk_php/src/Http/Handler/HandlerBase.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/HandlerUtil.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerAuthentication.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerBase.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerEncryption.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/RequestHandlerSignature.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/ResponseHandlerBase.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/ResponseHandlerError.php +opt/app/vendor/bunq/sdk_php/src/Http/Handler/ResponseHandlerSignature.php +opt/app/vendor/bunq/sdk_php/src/Http/Pagination.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/AnchorObjectInterface.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/BunqModel.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/BunqResponseInstallation.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/BunqResponseSessionServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/DeviceServerInternal.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Id.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Installation.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/SandboxUserInternal.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/ServerPublicKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/SessionServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Token.php +opt/app/vendor/bunq/sdk_php/src/Model/Core/Uuid.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentConversationContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentMonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentPublic.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentPublicContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/AttachmentTabContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Avatar.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BillingContractSubscription.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeFundraiserProfile.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeFundraiserResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTabEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTabResultInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqMeTabResultResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseAttachmentPublic.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseAttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseAvatar.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseBillingContractSubscriptionList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseBunqMeTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseBunqMeTabList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCard.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardDebit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardGeneratedCvc2.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardGeneratedCvc2List.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardNameList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardPinChange.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardPinChangeList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCardResultList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegister.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegisterList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegisterQrCode.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCashRegisterQrCodeList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCertificatePinned.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCertificatePinnedList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseChatConversation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseChatConversationList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseChatMessageList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerLimitList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerStatementExport.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseCustomerStatementExportList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDevice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDeviceList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDeviceServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDeviceServerList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftPaymentList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteApiKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteApiKeyList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseDraftShareInviteBankList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseExportAnnualOverview.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseExportAnnualOverviewList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseIdealMerchantTransaction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseIdealMerchantTransactionList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInstallationServerPublicKeyList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInt.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoiceByUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoiceByUserList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseInvoiceList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMasterCardAction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMasterCardActionList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountBankList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountJoint.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountJointList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountLightList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseMonetaryAccountList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseNull.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentBatchList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentChatList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePaymentList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePermittedIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePermittedIpList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponsePromotionDisplay.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryBatchList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryChatList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestInquiryList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestResponseChatList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseRequestResponseList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSandboxUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSchedule.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleInstance.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleInstanceList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSchedulePayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseSchedulePaymentList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseScheduleUserList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankInquiryList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseShareInviteBankResponseList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseString.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabAttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabItemShop.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabItemShopList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultInquiryList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabResultResponseList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageMultiple.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageMultipleList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageSingle.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTabUsageSingleList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTokenQrRequestIdeal.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseTokenQrRequestSofort.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserCompany.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserCredentialPasswordIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserCredentialPasswordIpList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserList.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/BunqResponseUserPerson.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Card.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardDebit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardGeneratedCvc2.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardName.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardPinChange.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardReplace.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CardResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CashRegister.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CashRegisterQrCode.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CashRegisterQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CertificatePinned.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatConversation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatConversationReference.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatConversationSupportExternal.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessage.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageAnnouncement.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageAttachment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageStatus.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageText.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ChatMessageUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Customer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CustomerLimit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CustomerStatementExport.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/CustomerStatementExportContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Device.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DeviceServer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteApiKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteApiKeyQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/DraftShareInviteBankQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ExportAnnualOverview.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ExportAnnualOverviewContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/IdealMerchantTransaction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/InstallationServerPublicKey.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Invoice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/InvoiceByUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MasterCardAction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountBank.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountJoint.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/MonetaryAccountProfile.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Payment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PaymentBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PaymentChat.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PermittedIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/PromotionDisplay.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestInquiryBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestInquiryChat.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/RequestResponseChat.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/SandboxUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Schedule.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ScheduleInstance.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/SchedulePayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/SchedulePaymentBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ScheduleUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Session.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ShareInviteBankAmountUsed.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ShareInviteBankInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/ShareInviteBankResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Tab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabAttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabAttachmentTabContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabItem.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabItemShop.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabItemShopBatch.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabQrCodeContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabResultInquiry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabResultResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabUsageMultiple.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TabUsageSingle.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TokenQrRequestIdeal.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/TokenQrRequestSofort.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/User.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserCompany.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserCredentialPasswordIp.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserLight.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/UserPerson.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/Whitelist.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Endpoint/WhitelistResult.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Address.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Amount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AnchoredObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Attachment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AttachmentMonetaryAccountPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AttachmentPublic.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/AttachmentTab.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Avatar.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/BudgetRestriction.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/BunqId.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/BunqMeMerchantAvailable.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardCountryPermission.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardLimit.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardMagStripePermission.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CardPinAssignment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Certificate.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentAnchorEvent.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentAttachment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentGeolocation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentStatusConversation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentStatusConversationTitle.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentStatusMembership.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ChatMessageContentText.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/CoOwner.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftPaymentAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftPaymentEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftPaymentResponse.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/DraftShareInviteEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Error.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Geolocation.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Image.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/InvoiceItem.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/InvoiceItemGroup.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Issuer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/LabelCard.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/LabelMonetaryAccount.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/LabelUser.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/MonetaryAccountProfileDrain.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/MonetaryAccountProfileFill.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/MonetaryAccountSetting.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/NotificationAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/NotificationFilter.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/NotificationUrl.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/PermittedDevice.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Pointer.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/RequestInquiryReference.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/RequestReferenceSplitTheBillAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ScheduleAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ScheduleInstanceAnchorObject.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/SchedulePaymentEntry.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetail.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetailDraftPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetailPayment.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/ShareDetailReadOnly.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/TabTextWaitingScreen.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/TabVisibility.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/TaxResident.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/Ubo.php +opt/app/vendor/bunq/sdk_php/src/Model/Generated/Object/WhitelistResultViewAnchoredObject.php +opt/app/vendor/bunq/sdk_php/src/Security/KeyPair.php +opt/app/vendor/bunq/sdk_php/src/Security/PrivateKey.php +opt/app/vendor/bunq/sdk_php/src/Security/PublicKey.php +opt/app/vendor/bunq/sdk_php/src/Util/BunqEnum.php +opt/app/vendor/bunq/sdk_php/src/Util/BunqEnumApiEnvironmentType.php +opt/app/vendor/bunq/sdk_php/src/Util/FileUtil.php +opt/app/vendor/bunq/sdk_php/src/Util/InstallationUtil.php +opt/app/vendor/bunq/sdk_php/src/Util/ModelUtil.php opt/app/vendor/composer/ClassLoader.php opt/app/vendor/composer/LICENSE opt/app/vendor/composer/autoload_classmap.php @@ -2179,7 +2551,6 @@ opt/app/vendor/egulias/email-validator/EmailValidator/Warning/Warning.php opt/app/vendor/egulias/email-validator/LICENSE opt/app/vendor/egulias/email-validator/README.md opt/app/vendor/egulias/email-validator/composer.json -opt/app/vendor/egulias/email-validator/composer.lock opt/app/vendor/egulias/email-validator/phpunit.xml.dist opt/app/vendor/erusev/parsedown/LICENSE.txt opt/app/vendor/erusev/parsedown/Parsedown.php @@ -2476,6 +2847,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/Queue.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/QueueableCollection.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/QueueableEntity.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Queue/ShouldQueue.php +opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Redis/Connection.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Redis/Factory.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Redis/LimiterTimeoutException.php opt/app/vendor/laravel/framework/src/Illuminate/Contracts/Routing/BindingRegistrar.php @@ -2682,6 +3054,7 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ListenerMakeC opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/MailMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ModelMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/NotificationMakeCommand.php +opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ObserverMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/PackageDiscoverCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/PolicyMakeCommand.php opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/PresetCommand.php @@ -2733,6 +3106,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdow opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/markdown.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/model.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/notification.stub +opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/observer.plain.stub +opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/observer.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/pivot.model.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/policy.plain.stub opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/policy.stub @@ -4141,109 +4516,6 @@ opt/app/vendor/rcrowe/twigbridge/src/ServiceProvider.php opt/app/vendor/rcrowe/twigbridge/src/Twig/Globals.php opt/app/vendor/rcrowe/twigbridge/src/Twig/Loader.php opt/app/vendor/rcrowe/twigbridge/src/Twig/Template.php -opt/app/vendor/rmccue/requests/.coveralls.yml -opt/app/vendor/rmccue/requests/CHANGELOG.md -opt/app/vendor/rmccue/requests/LICENSE -opt/app/vendor/rmccue/requests/README.md -opt/app/vendor/rmccue/requests/bin/create_pear_package.php -opt/app/vendor/rmccue/requests/composer.json -opt/app/vendor/rmccue/requests/docs/README.md -opt/app/vendor/rmccue/requests/docs/authentication-custom.md -opt/app/vendor/rmccue/requests/docs/authentication.md -opt/app/vendor/rmccue/requests/docs/goals.md -opt/app/vendor/rmccue/requests/docs/hooks.md -opt/app/vendor/rmccue/requests/docs/proxy.md -opt/app/vendor/rmccue/requests/docs/usage-advanced.md -opt/app/vendor/rmccue/requests/docs/usage.md -opt/app/vendor/rmccue/requests/docs/why-requests.md -opt/app/vendor/rmccue/requests/examples/basic-auth.php -opt/app/vendor/rmccue/requests/examples/cookie.php -opt/app/vendor/rmccue/requests/examples/cookie_jar.php -opt/app/vendor/rmccue/requests/examples/get.php -opt/app/vendor/rmccue/requests/examples/multiple.php -opt/app/vendor/rmccue/requests/examples/post.php -opt/app/vendor/rmccue/requests/examples/proxy.php -opt/app/vendor/rmccue/requests/examples/session.php -opt/app/vendor/rmccue/requests/examples/timeout.php -opt/app/vendor/rmccue/requests/library/Requests.php -opt/app/vendor/rmccue/requests/library/Requests/Auth.php -opt/app/vendor/rmccue/requests/library/Requests/Auth/Basic.php -opt/app/vendor/rmccue/requests/library/Requests/Cookie.php -opt/app/vendor/rmccue/requests/library/Requests/Cookie/Jar.php -opt/app/vendor/rmccue/requests/library/Requests/Exception.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/304.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/305.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/306.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/400.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/401.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/402.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/403.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/404.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/405.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/406.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/407.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/408.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/409.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/410.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/411.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/412.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/413.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/414.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/415.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/416.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/417.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/418.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/428.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/429.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/431.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/500.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/501.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/502.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/503.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/504.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/505.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/511.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/HTTP/Unknown.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/Transport.php -opt/app/vendor/rmccue/requests/library/Requests/Exception/Transport/cURL.php -opt/app/vendor/rmccue/requests/library/Requests/Hooker.php -opt/app/vendor/rmccue/requests/library/Requests/Hooks.php -opt/app/vendor/rmccue/requests/library/Requests/IDNAEncoder.php -opt/app/vendor/rmccue/requests/library/Requests/IPv6.php -opt/app/vendor/rmccue/requests/library/Requests/IRI.php -opt/app/vendor/rmccue/requests/library/Requests/Proxy.php -opt/app/vendor/rmccue/requests/library/Requests/Proxy/HTTP.php -opt/app/vendor/rmccue/requests/library/Requests/Response.php -opt/app/vendor/rmccue/requests/library/Requests/Response/Headers.php -opt/app/vendor/rmccue/requests/library/Requests/SSL.php -opt/app/vendor/rmccue/requests/library/Requests/Session.php -opt/app/vendor/rmccue/requests/library/Requests/Transport.php -opt/app/vendor/rmccue/requests/library/Requests/Transport/cURL.php -opt/app/vendor/rmccue/requests/library/Requests/Transport/cacert.pem -opt/app/vendor/rmccue/requests/library/Requests/Transport/fsockopen.php -opt/app/vendor/rmccue/requests/library/Requests/Utility/CaseInsensitiveDictionary.php -opt/app/vendor/rmccue/requests/library/Requests/Utility/FilteredIterator.php -opt/app/vendor/rmccue/requests/package.xml.tpl -opt/app/vendor/rmccue/requests/tests/Auth/Basic.php -opt/app/vendor/rmccue/requests/tests/ChunkedEncoding.php -opt/app/vendor/rmccue/requests/tests/Cookies.php -opt/app/vendor/rmccue/requests/tests/Encoding.php -opt/app/vendor/rmccue/requests/tests/IDNAEncoder.php -opt/app/vendor/rmccue/requests/tests/IRI.php -opt/app/vendor/rmccue/requests/tests/Proxy/HTTP.php -opt/app/vendor/rmccue/requests/tests/Requests.php -opt/app/vendor/rmccue/requests/tests/Response/Headers.php -opt/app/vendor/rmccue/requests/tests/SSL.php -opt/app/vendor/rmccue/requests/tests/Session.php -opt/app/vendor/rmccue/requests/tests/Transport/Base.php -opt/app/vendor/rmccue/requests/tests/Transport/cURL.php -opt/app/vendor/rmccue/requests/tests/Transport/fsockopen.php -opt/app/vendor/rmccue/requests/tests/bootstrap.php -opt/app/vendor/rmccue/requests/tests/phpunit.xml.dist -opt/app/vendor/rmccue/requests/tests/utils/proxy/proxy.py -opt/app/vendor/rmccue/requests/tests/utils/proxy/start.sh -opt/app/vendor/rmccue/requests/tests/utils/proxy/stop.sh opt/app/vendor/swiftmailer/swiftmailer/.gitattributes opt/app/vendor/swiftmailer/swiftmailer/.github/ISSUE_TEMPLATE.md opt/app/vendor/swiftmailer/swiftmailer/.github/PULL_REQUEST_TEMPLATE.md @@ -4595,6 +4867,7 @@ opt/app/vendor/symfony/console/Exception/ExceptionInterface.php opt/app/vendor/symfony/console/Exception/InvalidArgumentException.php opt/app/vendor/symfony/console/Exception/InvalidOptionException.php opt/app/vendor/symfony/console/Exception/LogicException.php +opt/app/vendor/symfony/console/Exception/NamespaceNotFoundException.php opt/app/vendor/symfony/console/Exception/RuntimeException.php opt/app/vendor/symfony/console/Formatter/OutputFormatter.php opt/app/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -4615,6 +4888,7 @@ opt/app/vendor/symfony/console/Helper/QuestionHelper.php opt/app/vendor/symfony/console/Helper/SymfonyQuestionHelper.php opt/app/vendor/symfony/console/Helper/Table.php opt/app/vendor/symfony/console/Helper/TableCell.php +opt/app/vendor/symfony/console/Helper/TableRows.php opt/app/vendor/symfony/console/Helper/TableSeparator.php opt/app/vendor/symfony/console/Helper/TableStyle.php opt/app/vendor/symfony/console/Input/ArgvInput.php @@ -4632,6 +4906,7 @@ opt/app/vendor/symfony/console/Logger/ConsoleLogger.php opt/app/vendor/symfony/console/Output/BufferedOutput.php opt/app/vendor/symfony/console/Output/ConsoleOutput.php opt/app/vendor/symfony/console/Output/ConsoleOutputInterface.php +opt/app/vendor/symfony/console/Output/ConsoleSectionOutput.php opt/app/vendor/symfony/console/Output/NullOutput.php opt/app/vendor/symfony/console/Output/Output.php opt/app/vendor/symfony/console/Output/OutputInterface.php @@ -4647,6 +4922,7 @@ opt/app/vendor/symfony/console/Style/SymfonyStyle.php opt/app/vendor/symfony/console/Terminal.php opt/app/vendor/symfony/console/Tester/ApplicationTester.php opt/app/vendor/symfony/console/Tester/CommandTester.php +opt/app/vendor/symfony/console/Tester/TesterTrait.php opt/app/vendor/symfony/console/Tests/ApplicationTest.php opt/app/vendor/symfony/console/Tests/Command/CommandTest.php opt/app/vendor/symfony/console/Tests/Command/HelpCommandTest.php @@ -4686,6 +4962,7 @@ opt/app/vendor/symfony/console/Tests/Fixtures/FooSameCaseLowercaseCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/FooSameCaseUppercaseCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced1Command.php opt/app/vendor/symfony/console/Tests/Fixtures/FooSubnamespaced2Command.php +opt/app/vendor/symfony/console/Tests/Fixtures/FooWithoutAliasCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/FoobarCommand.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_0.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_1.php @@ -4700,6 +4977,7 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_2.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_3.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4.php +opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_4_with_iterators.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_5.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_6.php opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/command/command_7.php @@ -4720,6 +4998,7 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_1 opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_2.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_3.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4.txt +opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_4_with_iterators.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_5.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_6.txt opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7.txt @@ -4863,6 +5142,7 @@ opt/app/vendor/symfony/console/Tests/Input/InputTest.php opt/app/vendor/symfony/console/Tests/Input/StringInputTest.php opt/app/vendor/symfony/console/Tests/Logger/ConsoleLoggerTest.php opt/app/vendor/symfony/console/Tests/Output/ConsoleOutputTest.php +opt/app/vendor/symfony/console/Tests/Output/ConsoleSectionOutputTest.php opt/app/vendor/symfony/console/Tests/Output/NullOutputTest.php opt/app/vendor/symfony/console/Tests/Output/OutputTest.php opt/app/vendor/symfony/console/Tests/Output/StreamOutputTest.php @@ -5107,8 +5387,15 @@ opt/app/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php opt/app/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php opt/app/vendor/symfony/http-foundation/ExpressionRequestMatcher.php opt/app/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php +opt/app/vendor/symfony/http-foundation/File/Exception/CannotWriteFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/ExtensionFileException.php opt/app/vendor/symfony/http-foundation/File/Exception/FileException.php opt/app/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php +opt/app/vendor/symfony/http-foundation/File/Exception/FormSizeFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/IniSizeFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/NoFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/NoTmpDirFileException.php +opt/app/vendor/symfony/http-foundation/File/Exception/PartialFileException.php opt/app/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php opt/app/vendor/symfony/http-foundation/File/Exception/UploadException.php opt/app/vendor/symfony/http-foundation/File/File.php @@ -5123,6 +5410,7 @@ opt/app/vendor/symfony/http-foundation/File/Stream.php opt/app/vendor/symfony/http-foundation/File/UploadedFile.php opt/app/vendor/symfony/http-foundation/FileBag.php opt/app/vendor/symfony/http-foundation/HeaderBag.php +opt/app/vendor/symfony/http-foundation/HeaderUtils.php opt/app/vendor/symfony/http-foundation/IpUtils.php opt/app/vendor/symfony/http-foundation/JsonResponse.php opt/app/vendor/symfony/http-foundation/LICENSE @@ -5148,10 +5436,12 @@ opt/app/vendor/symfony/http-foundation/Session/SessionBagProxy.php opt/app/vendor/symfony/http-foundation/Session/SessionInterface.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php +opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php +opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php opt/app/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php opt/app/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -5178,7 +5468,21 @@ opt/app/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif opt/app/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php opt/app/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php opt/app/vendor/symfony/http-foundation/Tests/FileBagTest.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/common.inc +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_max_age.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_raw_urlencode.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_lax.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_samesite_strict.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/cookie_urlencode.php +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.expected +opt/app/vendor/symfony/http-foundation/Tests/Fixtures/response-functional/invalid_cookie_name.php opt/app/vendor/symfony/http-foundation/Tests/HeaderBagTest.php +opt/app/vendor/symfony/http-foundation/Tests/HeaderUtilsTest.php opt/app/vendor/symfony/http-foundation/Tests/IpUtilsTest.php opt/app/vendor/symfony/http-foundation/Tests/JsonResponseTest.php opt/app/vendor/symfony/http-foundation/Tests/ParameterBagTest.php @@ -5186,6 +5490,7 @@ opt/app/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php opt/app/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php opt/app/vendor/symfony/http-foundation/Tests/RequestStackTest.php opt/app/vendor/symfony/http-foundation/Tests/RequestTest.php +opt/app/vendor/symfony/http-foundation/Tests/ResponseFunctionalTest.php opt/app/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php opt/app/vendor/symfony/http-foundation/Tests/ResponseTest.php opt/app/vendor/symfony/http-foundation/Tests/ResponseTestCase.php @@ -5195,6 +5500,7 @@ opt/app/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttribu opt/app/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/SessionTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractRedisSessionHandlerTestCase.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/common.inc opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected @@ -5210,10 +5516,15 @@ opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/wi opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.expected opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/Fixtures/with_cookie_and_session.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MigratingSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisClusterSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PredisSessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisArraySessionHandlerTest.php +opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/RedisSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php opt/app/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php @@ -5245,6 +5556,7 @@ opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeV opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php +opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php opt/app/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php @@ -5369,6 +5681,7 @@ opt/app/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php opt/app/vendor/symfony/http-kernel/Tests/ClientTest.php opt/app/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php +opt/app/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php opt/app/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php @@ -5488,6 +5801,11 @@ opt/app/vendor/symfony/http-kernel/Tests/UriSignerTest.php opt/app/vendor/symfony/http-kernel/UriSigner.php opt/app/vendor/symfony/http-kernel/composer.json opt/app/vendor/symfony/http-kernel/phpunit.xml.dist +opt/app/vendor/symfony/polyfill-ctype/Ctype.php +opt/app/vendor/symfony/polyfill-ctype/LICENSE +opt/app/vendor/symfony/polyfill-ctype/README.md +opt/app/vendor/symfony/polyfill-ctype/bootstrap.php +opt/app/vendor/symfony/polyfill-ctype/composer.json opt/app/vendor/symfony/polyfill-mbstring/LICENSE opt/app/vendor/symfony/polyfill-mbstring/Mbstring.php opt/app/vendor/symfony/polyfill-mbstring/README.md @@ -5519,6 +5837,7 @@ opt/app/vendor/symfony/process/Exception/ExceptionInterface.php opt/app/vendor/symfony/process/Exception/InvalidArgumentException.php opt/app/vendor/symfony/process/Exception/LogicException.php opt/app/vendor/symfony/process/Exception/ProcessFailedException.php +opt/app/vendor/symfony/process/Exception/ProcessSignaledException.php opt/app/vendor/symfony/process/Exception/ProcessTimedOutException.php opt/app/vendor/symfony/process/Exception/RuntimeException.php opt/app/vendor/symfony/process/ExecutableFinder.php @@ -5596,8 +5915,6 @@ opt/app/vendor/symfony/routing/Loader/PhpFileLoader.php opt/app/vendor/symfony/routing/Loader/XmlFileLoader.php opt/app/vendor/symfony/routing/Loader/YamlFileLoader.php opt/app/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd -opt/app/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php -opt/app/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php opt/app/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php opt/app/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php opt/app/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -5626,6 +5943,24 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/AbstractClassController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/ActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/DefaultValueController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/ExplicitLocalizedActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/InvokableController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/InvokableLocalizedController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedMethodActionControllers.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixLocalizedActionController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixMissingLocaleActionController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixMissingRouteLocaleActionController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/LocalizedPrefixWithRouteWithoutLocale.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/MethodActionControllers.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/MissingRouteNameController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/NothingButNameController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/PrefixedActionLocalizedRouteController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/PrefixedActionPathController.php +opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotationFixtures/RouteWithPrefixController.php opt/app/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php opt/app/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php opt/app/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php @@ -5652,12 +5987,18 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml opt/app/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher0.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher10.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher11.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher12.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher13.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher8.php +opt/app/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher9.php opt/app/vendor/symfony/routing/Tests/Fixtures/empty.yml opt/app/vendor/symfony/routing/Tests/Fixtures/file_resource.yml opt/app/vendor/symfony/routing/Tests/Fixtures/foo.xml @@ -5673,11 +6014,31 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/glob/import_single.yml opt/app/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl.php opt/app/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_bar.php opt/app/vendor/symfony/routing/Tests/Fixtures/glob/php_dsl_baz.php +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_name_prefix/routing.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_name_prefix/routing.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_no_trailing_slash/routing.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/import_with_no_trailing_slash/routing.yml opt/app/vendor/symfony/routing/Tests/Fixtures/incomplete.yml opt/app/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale-but-not-localized.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale-but-not-localized.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/imported-with-locale.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-controller-default.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale-imports-non-localized-route.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale-imports-non-localized-route.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale.xml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importer-with-locale.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/importing-localized-route.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/localized-route.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/missing-locale-in-importer.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/not-localized.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/officially_formatted_locales.yml +opt/app/vendor/symfony/routing/Tests/Fixtures/localized/route-without-path-or-locales.yml opt/app/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml @@ -5695,7 +6056,11 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml opt/app/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml opt/app/vendor/symfony/routing/Tests/Fixtures/null_values.xml opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_i18n.php opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub_i18n.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_dsl_sub_root.php +opt/app/vendor/symfony/routing/Tests/Fixtures/php_object_dsl.php opt/app/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml opt/app/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml opt/app/vendor/symfony/routing/Tests/Fixtures/validpattern.php @@ -5714,6 +6079,7 @@ opt/app/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php +opt/app/vendor/symfony/routing/Tests/Loader/FileLocatorStub.php opt/app/vendor/symfony/routing/Tests/Loader/GlobFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php @@ -5721,7 +6087,6 @@ opt/app/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php opt/app/vendor/symfony/routing/Tests/Matcher/DumpedRedirectableUrlMatcherTest.php opt/app/vendor/symfony/routing/Tests/Matcher/DumpedUrlMatcherTest.php -opt/app/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php opt/app/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php opt/app/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php opt/app/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -5805,6 +6170,7 @@ opt/app/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-strict.xsd opt/app/vendor/symfony/translation/Tests/Catalogue/AbstractOperationTest.php opt/app/vendor/symfony/translation/Tests/Catalogue/MergeOperationTest.php opt/app/vendor/symfony/translation/Tests/Catalogue/TargetOperationTest.php +opt/app/vendor/symfony/translation/Tests/Command/XliffLintCommandTest.php opt/app/vendor/symfony/translation/Tests/DataCollector/TranslationDataCollectorTest.php opt/app/vendor/symfony/translation/Tests/DataCollectorTranslatorTest.php opt/app/vendor/symfony/translation/Tests/DependencyInjection/TranslationDumperPassTest.php @@ -5921,6 +6287,7 @@ opt/app/vendor/symfony/var-dumper/Caster/DoctrineCaster.php opt/app/vendor/symfony/var-dumper/Caster/EnumStub.php opt/app/vendor/symfony/var-dumper/Caster/ExceptionCaster.php opt/app/vendor/symfony/var-dumper/Caster/FrameStub.php +opt/app/vendor/symfony/var-dumper/Caster/GmpCaster.php opt/app/vendor/symfony/var-dumper/Caster/LinkStub.php opt/app/vendor/symfony/var-dumper/Caster/PdoCaster.php opt/app/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -5940,18 +6307,32 @@ opt/app/vendor/symfony/var-dumper/Cloner/Data.php opt/app/vendor/symfony/var-dumper/Cloner/DumperInterface.php opt/app/vendor/symfony/var-dumper/Cloner/Stub.php opt/app/vendor/symfony/var-dumper/Cloner/VarCloner.php +opt/app/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php +opt/app/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php +opt/app/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php +opt/app/vendor/symfony/var-dumper/Command/ServerDumpCommand.php opt/app/vendor/symfony/var-dumper/Dumper/AbstractDumper.php opt/app/vendor/symfony/var-dumper/Dumper/CliDumper.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php +opt/app/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php opt/app/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php opt/app/vendor/symfony/var-dumper/Dumper/HtmlDumper.php +opt/app/vendor/symfony/var-dumper/Dumper/ServerDumper.php opt/app/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php opt/app/vendor/symfony/var-dumper/LICENSE opt/app/vendor/symfony/var-dumper/README.md +opt/app/vendor/symfony/var-dumper/Resources/bin/var-dump-server +opt/app/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css opt/app/vendor/symfony/var-dumper/Resources/functions/dump.php +opt/app/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js +opt/app/vendor/symfony/var-dumper/Server/DumpServer.php opt/app/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php opt/app/vendor/symfony/var-dumper/Tests/Caster/CasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/DateCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/ExceptionCasterTest.php +opt/app/vendor/symfony/var-dumper/Tests/Caster/GmpCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/PdoCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/RedisCasterTest.php opt/app/vendor/symfony/var-dumper/Tests/Caster/ReflectionCasterTest.php @@ -5962,11 +6343,13 @@ opt/app/vendor/symfony/var-dumper/Tests/Cloner/DataTest.php opt/app/vendor/symfony/var-dumper/Tests/Cloner/VarClonerTest.php opt/app/vendor/symfony/var-dumper/Tests/Dumper/CliDumperTest.php opt/app/vendor/symfony/var-dumper/Tests/Dumper/HtmlDumperTest.php +opt/app/vendor/symfony/var-dumper/Tests/Dumper/ServerDumperTest.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/FooInterface.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/GeneratorDemo.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/NotLoadableClass.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/Twig.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/dumb-var.php +opt/app/vendor/symfony/var-dumper/Tests/Fixtures/dump_server.php opt/app/vendor/symfony/var-dumper/Tests/Fixtures/xml_reader.xml opt/app/vendor/symfony/var-dumper/Tests/Test/VarDumperTestTraitTest.php opt/app/vendor/symfony/var-dumper/VarDumper.php diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index 6f5c532bd3..1a8b1f1c97 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -65,9 +65,9 @@ const pkgdef :Spk.PackageDefinition = ( # Sizes are given in device-independent pixels, so if you took these # screenshots on a Retina-style high DPI screen, divide each dimension by two. - (width = 1291, height = 800, png = embed "screenshots/screenshot-1.png"), - (width = 1291, height = 800, png = embed "screenshots/screenshot-2.png"), - (width = 1291, height = 800, png = embed "screenshots/screenshot-3.png"), + (width = 1290, height = 800, png = embed "screenshots/screenshot-1.png"), + (width = 1290, height = 800, png = embed "screenshots/screenshot-2.png"), + (width = 1290, height = 800, png = embed "screenshots/screenshot-3.png"), ], changeLog = (defaultText = embed "changelog.md"), diff --git a/.sandstorm/screenshots/screenshot-1.png b/.sandstorm/screenshots/screenshot-1.png index c8a49481c0738a10e4b7e41035f6d711a2437c23..5f786ad2ff20abe3a301c67efe45eba2743126ce 100644 GIT binary patch literal 176915 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%0A@9T`PV2Le*Q9G2q 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

vd!1jBtV40=%S-(|reE@6*&jxq6nVbv@V{ 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 z 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 diff --git a/.sandstorm/screenshots/screenshot-2.png b/.sandstorm/screenshots/screenshot-2.png index 15dbbd4122b1524dc1fff3c30d2df23496bf73af..fe0bfb0218b3d02f75c7cc27af79930030720d4f 100644 GIT binary patch literal 174991 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%0AK`}6PRgoB^W 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%0A4RjdGPd-pdy-)7aV8q>bT>qXTXWVN(8`&G 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%0D?|X9-|Lh!|k7#n) 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�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 &CR~R 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 (m<%`(Fqg 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

+
  • + + {{ 'recurrences'|_ }} +
  • diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig new file mode 100644 index 0000000000..3329ae2208 --- /dev/null +++ b/resources/views/recurring/create.twig @@ -0,0 +1,150 @@ +{% extends "./layout/default" %} +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName) }} +{% endblock %} +{% block content %} + +
    + +
    +
    +
    +
    +
    +

    {{ 'mandatory_for_recurring'|_ }}

    +
    +
    + {{ ExpandedForm.text('name') }} + {{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }} + {{ ExpandedForm.date('repeat_until',null) }} + {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} + {{ ExpandedForm.number('skip', 0) }} + + {# three buttons to distinguish type of transaction#} +
    + + + +
    +
    +
    + +
    +
    +

    {{ 'mandatory_for_transaction'|_ }}

    +
    +
    +

    {{ 'mandatory_fields_for_tranaction'|_ }}

    + {{ ExpandedForm.text('transaction_description') }} + {# transaction information (mandatory) #} + {{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('amount', []) }} + + {# source account if withdrawal, or if transfer: #} + {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + + {# source account name for deposits: #} + {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} + + {# destination if deposit or transfer: #} + {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + + {# destination account name for withdrawals #} + {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} +
    +
    + + +
    +
    +

    {{ 'expected_repetitions'|_ }}

    +
    +
    + Here. +
    +
    +
    + +
    +
    +
    +

    {{ 'optional_for_recurring'|_ }}

    +
    +
    + {{ ExpandedForm.textarea('recurring_description') }} + + {{ ExpandedForm.checkbox('active',1) }} + + {{ ExpandedForm.checkbox('apply_rules',1) }} +
    +
    +
    +
    +

    {{ 'optional_for_transaction'|_ }}

    +
    +
    + {# transaction information (optional) #} + {{ ExpandedForm.currencyList('foreign_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('foreign_amount', []) }} + + {# BUDGET ONLY WHEN CREATING A WITHDRAWAL #} + {% if budgets|length > 1 %} + {{ ExpandedForm.select('budget_id', budgets, null) }} + {% else %} + {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }} + {% endif %} + + {# CATEGORY ALWAYS #} + {{ ExpandedForm.text('category') }} + + {# TAGS #} + {{ ExpandedForm.text('tags') }} + + {# RELATE THIS TRANSFER TO A PIGGY BANK #} + {{ ExpandedForm.select('piggy_bank_id', [], '0') }} +
    +
    + +
    +
    +

    {{ 'options'|_ }}

    +
    +
    + {{ ExpandedForm.optionsList('create','recurrence') }} +
    + +
    + +
    +
    + +{% endblock %} +{% block scripts %} + + + + + + +{% endblock %} + +{% block styles %} + + + +{% endblock %} diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig new file mode 100644 index 0000000000..63f12e8605 --- /dev/null +++ b/resources/views/recurring/index.twig @@ -0,0 +1,120 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + + {% if recurring|length > 0 %} +
    +
    +
    +
    +

    + {{ 'recurrences'|_ }} +

    + +
    + +
    + + + +
    + {{ recurring.render|raw }} +
    + + + + + + + + + + + {% for rt in recurring %} + + + + + + + {% endfor %} + +
    {{ trans('list.title') }}{{ trans('list.transaction_s') }}{{ trans('list.repetitions') }}
    + {{ rt.transaction_type|_ }}: + + {{ rt.title }} + {% if rt.description|length > 0 %} +
    {{ rt.description }}
    + {% endif %} +
    +
      + {% for rtt in rt.transactions %} +
    1. + {# normal amount + comma#} + {{ formatAmountBySymbol(rtt['amount'],rtt['currency_symbol'],rtt['currency_dp']) }}{% if rtt['foreign_amount'] == null %},{% endif %} + + {# foreign amount + comma #} + {% if null != rtt['foreign_amount'] %} + ({{ formatAmountBySymbol(rtt['foreign_amount'],rtt['foreign_currency_symbol'],rtt['foreign_currency_dp']) }}), + {% endif %} + {{ rtt['source_account_name'] }} + → + {{ rtt['destination_account_name'] }} +
    2. + {% endfor %} +
    +
    +
      + {% for rep in rt.repetitions %} +
    • {{ rep.description }}
    • + {% endfor %} +
    +
    +
    + {{ recurring.render|raw }} +
    +
    + +
    +
    +
    + {% endif %} + + + + + {% if recurring|length == 0 and page == 1 %} + {% include 'partials.empty' with {what: 'default', type: 'recurring',route: route('recurring.create')} %} + {% endif %} +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig new file mode 100644 index 0000000000..492a15fffe --- /dev/null +++ b/resources/views/recurring/show.twig @@ -0,0 +1,190 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }} +{% endblock %} + +{% block content %} +
    + +
    +
    +
    +

    + {{ array.title }} +

    +
    +
    +

    {{ array.description }}

    +
      + {% for rep in array.repetitions %} +
    • {{ rep.description }}
    • + {% endfor %} +
    +
    + +
    +
    + +
    +
    +
    +

    + {{ 'expected_transactions'|_ }} +

    +
    +
    + +
      + {% for rep in array.repetitions %} +
    • {{ rep.description }} +
        + {% for occ in rep.occurrences %} +
      • {{ occ.formatLocalized(trans('config.month_and_date_day')) }}
      • + {% endfor %} +
      +
    • + {% endfor %} +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +

    + {{ 'transaction_data'|_ }} +

    +
    +
    + + + + + + + + + + {% for transaction in array.transactions %} + + + + + + + + {% endfor %} + +
    {{ trans('list.source') }}{{ trans('list.destination') }}{{ trans('list.amount') }}{{ trans('list.category') }}{{ trans('list.budget') }}
    + {{ transaction.source_account_name }} + + {{ transaction.destination_account_name }} + + {{ formatAmountBySymbol(transaction.amount,transaction.currency_symbol,transaction.currency_dp) }} + {% if null != transaction.foreign_amount %} + ({{ formatAmountBySymbol(transaction.foreign_amount,transaction.foreign_currency_symbol,transaction.foreign_currency_dp) }}) + {% endif %} + + {% for meta in transaction.meta %} + {% if meta.name == 'category_name' %} + + {{ meta.category_name }} + + {% endif %} + {% endfor %} + + {% for meta in transaction.meta %} + {% if meta.name == 'budget_id' %} + + {{ meta.budget_name }} + + {% endif %} + {% endfor %} +
    +
    +
    +
    + + + {% if array.meta|length > 0 %} +
    +
    +
    +

    + {{ 'meta_data'|_ }} +

    +
    +
    + + + + + + + {% for meta in array.meta %} + + + + + {% endfor %} + + +
    {{ trans('list.field') }}{{ trans('list.value') }}
    {{ trans('firefly.recurring_meta_field_'~meta.name) }} + {% if meta.name == 'tags' %} + {% for tag in meta.tags %} + {{ tag }} + {% endfor %} + {% endif %} + {% if meta.name == 'notes' %} + {{ meta.value|markdown }} + {% endif %} + {% if meta.name == 'bill_id' %} + {{ meta.bill_name }} + {% endif %} + {% if meta.name == 'piggy_bank_id' %} + {{ meta.piggy_bank_name }} + {% endif %} +
    +
    +
    +
    + {% endif %} +
    +
    + +
    +
    +
    +

    + {{ 'transactions'|_ }} +

    +
    +
    + Bla bla +
    +
    +
    +
    +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index cba44bd866..992ed89f63 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -33,6 +33,7 @@ use FireflyIII\Models\Category; use FireflyIII\Models\ImportJob; use FireflyIII\Models\LinkType; use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\Recurrence; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\Models\Tag; @@ -761,6 +762,30 @@ try { } ); + // Recurring transactions controller: + Breadcrumbs::register( + 'recurring.index', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('home'); + $breadcrumbs->push(trans('firefly.recurrences'), route('recurring.index')); + } + ); + Breadcrumbs::register( + 'recurring.show', + function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) { + $breadcrumbs->parent('recurring.index'); + $breadcrumbs->push($recurrence->title, route('recurring.show', [$recurrence->id])); + } + ); + + Breadcrumbs::register( + 'recurring.create', + function (BreadCrumbsGenerator $breadcrumbs) { + $breadcrumbs->parent('recurring.index'); + $breadcrumbs->push(trans('firefly.create_new_recurrence'), route('recurring.create')); + } + ); + // Rules Breadcrumbs::register( 'rules.index', diff --git a/routes/web.php b/routes/web.php index 7c98292c2a..2fbd1c00d4 100755 --- a/routes/web.php +++ b/routes/web.php @@ -466,28 +466,6 @@ Route::group( // download config: Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'job.download']); - - // import method prerequisites: - # - # - #Route::get('reset/{bank}', ['uses' => 'Import\IndexController@reset', 'as' => 'reset']); - - // create the job: - #Route::get('create/{bank}', ['uses' => 'Import\IndexController@create', 'as' => 'create-job']); - - // configure the job: - - #Route::post('configure/{importJob}', ['uses' => 'Import\ConfigurationController@post', 'as' => 'configure.post']); - - // get status of any job: - #Route::get('status/{importJob}', ['uses' => 'Import\StatusController@index', 'as' => 'status']); - #Route::get('json/{importJob}', ['uses' => 'Import\StatusController@json', 'as' => 'status.json']); - - // start a job - #Route::any('start/{importJob}', ['uses' => 'Import\IndexController@start', 'as' => 'start']); - - // download config - #Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'download']); } ); @@ -632,6 +610,23 @@ Route::group( } ); +/** + * Recurring Transactions Controller + */ +Route::group( + ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Recurring', 'prefix' => 'recurring', 'as' => 'recurring.'], function () { + + Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']); + Route::get('suggest', ['uses' => 'IndexController@suggest', 'as' => 'suggest']); + Route::get('show/{recurrence}', ['uses' => 'IndexController@show', 'as' => 'show']); + Route::get('create', ['uses' => 'CreateController@create', 'as' => 'create']); + Route::get('edit/{recurrence}', ['uses' => 'EditController@edit', 'as' => 'edit']); + Route::get('delete/{recurrence}', ['uses' => 'DeleteController@delete', 'as' => 'delete']); + + Route::post('store', ['uses' => 'CreateController@store', 'as' => 'store']); +} +); + /** * Report Controller */ From 5a058491b0696152d92f52bbdb82488f01e7132b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 10 Jun 2018 16:59:41 +0200 Subject: [PATCH 014/134] Some code optimalisations. --- app/Http/Controllers/AccountController.php | 4 ---- app/Models/Bill.php | 2 ++ app/Models/Budget.php | 2 ++ app/Models/Category.php | 3 +++ app/Models/Note.php | 2 ++ app/Models/PiggyBank.php | 2 ++ app/Models/Transaction.php | 9 +++++---- app/Models/TransactionCurrency.php | 1 + .../PiggyBank/PiggyBankRepository.php | 15 +++++++++++++++ .../PiggyBank/PiggyBankRepositoryInterface.php | 8 +++++++- app/Support/CacheProperties.php | 3 +-- 11 files changed, 40 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index db68a5c841..ad44631ba9 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -34,7 +34,6 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; @@ -52,8 +51,6 @@ class AccountController extends Controller { /** @var CurrencyRepositoryInterface */ private $currencyRepos; - /** @var JournalRepositoryInterface */ - private $journalRepos; /** @var AccountRepositoryInterface */ private $repository; @@ -72,7 +69,6 @@ class AccountController extends Controller $this->repository = app(AccountRepositoryInterface::class); $this->currencyRepos = app(CurrencyRepositoryInterface::class); - $this->journalRepos = app(JournalRepositoryInterface::class); return $next($request); } diff --git a/app/Models/Bill.php b/app/Models/Bill.php index bf4c01ff12..3da9e56add 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -37,6 +37,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property int $transaction_currency_id * @property string $amount_min * @property string $amount_max + * @property int $id + * @property string $name */ class Bill extends Model { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 60f80da5f3..c4bd59d5c8 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -31,6 +31,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Budget. + * @property int $id + * @property string $name */ class Budget extends Model { diff --git a/app/Models/Category.php b/app/Models/Category.php index 068c12934b..7bc567f2b7 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -31,6 +31,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Category. + * + * @property string $name + * @property int $id */ class Category extends Model { diff --git a/app/Models/Note.php b/app/Models/Note.php index a6588c8564..f2cc1ddb20 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -26,6 +26,8 @@ use Illuminate\Database\Eloquent\Model; /** * Class Note. + * + * @property string $text */ class Note extends Model { diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index e5b8c30a37..92819a5d28 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -36,6 +36,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon $targetdate * @property Carbon $startdate * @property string $targetamount + * @property int $id + * @property string $name * */ class PiggyBank extends Model diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index df124b1041..2a9ffa0a69 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -25,6 +25,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -252,18 +253,18 @@ class Transaction extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionCurrency() + public function transactionCurrency(): BelongsTo { return $this->belongsTo(TransactionCurrency::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionJournal() + public function transactionJournal(): BelongsTo { return $this->belongsTo(TransactionJournal::class); } diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 829d5e9876..e54c7513a6 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -30,6 +30,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * Class TransactionCurrency. * * @property string $code + * @property string $symbol * @property int $decimal_places * */ diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 401d53d4b1..99ef2b7f28 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -203,6 +203,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return null; } + /** + * @param int $piggyBankId + * + * @return PiggyBank|null + */ + public function findNull(int $piggyBankId): ?PiggyBank + { + $piggyBank = $this->user->piggyBanks()->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); + if (null !== $piggyBank) { + return $piggyBank; + } + + return null; + } + /** * Get current amount saved in piggy bank. * diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index 9b22a8d3b8..f1ee19124a 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -102,11 +102,17 @@ interface PiggyBankRepositoryInterface /** * @param int $piggyBankid - * + * @deprecated * @return PiggyBank */ public function find(int $piggyBankid): PiggyBank; + /** + * @param int $piggyBankId + * @return PiggyBank|null + */ + public function findNull(int $piggyBankId): ?PiggyBank; + /** * Find by name or return NULL. * diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php index 0e0be45819..d6d34aa8ed 100644 --- a/app/Support/CacheProperties.php +++ b/app/Support/CacheProperties.php @@ -24,7 +24,6 @@ namespace FireflyIII\Support; use Cache; use Illuminate\Support\Collection; -use Preferences as Prefs; /** * Class CacheProperties. @@ -44,7 +43,7 @@ class CacheProperties $this->properties = new Collection; if (auth()->check()) { $this->addProperty(auth()->user()->id); - $this->addProperty(Prefs::lastActivity()); + $this->addProperty(app('preferences')->lastActivity()); } } From 2de19547cae695aa207a4e6c9087980324571d71 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 11 Jun 2018 11:41:38 +0200 Subject: [PATCH 015/134] Fix missing variable --- app/Services/Currency/FixerIOv2.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/Currency/FixerIOv2.php b/app/Services/Currency/FixerIOv2.php index f44e917eb4..b5cf3cde09 100644 --- a/app/Services/Currency/FixerIOv2.php +++ b/app/Services/Currency/FixerIOv2.php @@ -49,13 +49,13 @@ class FixerIOv2 implements ExchangeRateInterface public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate { // create new exchange rate with default values. - // create new currency exchange rate object: + $rate = 0; $exchangeRate = new CurrencyExchangeRate; $exchangeRate->user()->associate($this->user); $exchangeRate->fromCurrency()->associate($fromCurrency); $exchangeRate->toCurrency()->associate($toCurrency); $exchangeRate->date = $date; - $exchangeRate->rate = 0; + $exchangeRate->rate = $rate; // get API key $apiKey = env('FIXER_API_KEY', ''); From 4b4dc2e298f2f892157366daa17699d4d6d735c9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 12 Jun 2018 18:48:15 +0200 Subject: [PATCH 016/134] Various code related to the recurring transactions. --- .../Recurring/CreateController.php | 9 +- .../Controllers/Recurring/IndexController.php | 14 + .../2018_06_08_200526_changes_for_v475.php | 1 + public/js/ff/recurring/create.js | 34 ++- resources/lang/en_US/demo.php | 2 + resources/lang/en_US/firefly.php | 4 + resources/lang/en_US/form.php | 3 + resources/views/recurring/create.twig | 260 ++++++++++-------- routes/web.php | 1 + 9 files changed, 213 insertions(+), 115 deletions(-) diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php index f7f5d06712..52e12bf7c7 100644 --- a/app/Http/Controllers/Recurring/CreateController.php +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -74,6 +74,13 @@ class CreateController extends Controller $tomorrow = new Carbon; $tomorrow->addDay(); + // types of repetitions: + $typesOfRepetitions = [ + 'forever' => trans('firefly.repeat_forever'), + 'until_date' => trans('firefly.repeat_until_date'), + 'times' => trans('firefly.repeat_times'), + ]; + // flash some data: $preFilled = [ 'first_date' => $tomorrow->format('Y-m-d'), @@ -83,7 +90,7 @@ class CreateController extends Controller ]; $request->session()->flash('preFilled', $preFilled); - return view('recurring.create', compact('tomorrow', 'preFilled', 'defaultCurrency','budgets')); + return view('recurring.create', compact('tomorrow', 'preFilled','typesOfRepetitions', 'defaultCurrency', 'budgets')); } } \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index 8cde8b238f..74f948415c 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -63,6 +63,20 @@ class IndexController extends Controller ); } + /** + * @param Request $request + * + * @return string + */ + public function calendar(Request $request) + { + $date = new Carbon; + $daysOfWeek = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; + //$firstDayOfMonth = mktime(0, 0, 0, $month, 1, $year); + + return view('recurring.calendar'); + } + /** * @param Request $request * diff --git a/database/migrations/2018_06_08_200526_changes_for_v475.php b/database/migrations/2018_06_08_200526_changes_for_v475.php index 053a6e676b..db5cfbebe7 100644 --- a/database/migrations/2018_06_08_200526_changes_for_v475.php +++ b/database/migrations/2018_06_08_200526_changes_for_v475.php @@ -44,6 +44,7 @@ class ChangesForV475 extends Migration $table->date('first_date'); $table->date('repeat_until')->nullable(); $table->date('latest_date')->nullable(); + $table->smallInteger('repetitions', false, true); $table->boolean('apply_rules')->default(true); $table->boolean('active')->default(true); diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index 78ff8264b6..ba79cd4e4e 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -32,12 +32,44 @@ $(document).ready(function () { initializeButtons(); initializeAutoComplete(); respondToFirstDateChange(); + respondToRepetitionEnd(); $('.switch-button').on('click', switchTransactionType); + $('#ffInput_repetition_end').on('change', respondToRepetitionEnd); $('#ffInput_first_date').on('change', respondToFirstDateChange); - + $('#calendar-link').on('click', showRepCalendar); }); +function showRepCalendar() { + + // fill model with calendar: + + + $('#defaultModal').modal({}); + return false; +} + +function respondToRepetitionEnd() { + var obj = $('#ffInput_repetition_end'); + var value = obj.val(); + switch (value) { + case 'forever': + $('#repeat_until_holder').hide(); + $('#repetitions_holder').hide(); + break; + case 'until_date': + $('#repeat_until_holder').show(); + $('#repetitions_holder').hide(); + break; + case 'times': + $('#repeat_until_holder').hide(); + $('#repetitions_holder').show(); + break; + } + + +} + function respondToFirstDateChange() { var obj = $('#ffInput_first_date'); var select = $('#ffInput_repetition_type'); diff --git a/resources/lang/en_US/demo.php b/resources/lang/en_US/demo.php index 640ab10298..e56f6f24c6 100644 --- a/resources/lang/en_US/demo.php +++ b/resources/lang/en_US/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index af9109e153..e839af9f50 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1235,6 +1235,10 @@ return [ 'optional_for_transaction' => 'Optional transaction information', 'change_date_other_options' => 'Change the "first date" to see more options.', 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 172fc28ccb..9445534e2a 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -230,5 +230,8 @@ return [ 'recurring_description' => 'Recurring transaction description', 'repetition_type' => 'Type of repetition', 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', ]; diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 3329ae2208..70b1a034e2 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -6,129 +6,163 @@
    + {# row with recurrence information #}
    -
    -
    -
    -
    -

    {{ 'mandatory_for_recurring'|_ }}

    + +
    + {# mandatory recurrence stuff #} +
    +
    +

    {{ 'mandatory_for_recurring'|_ }}

    +
    +
    + {{ ExpandedForm.text('name') }} + {{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }} + {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} + {{ ExpandedForm.number('skip', 0) }} + {{ ExpandedForm.select('repetition_end', typesOfRepetitions) }} + {{ ExpandedForm.date('repeat_until',null) }} + {{ ExpandedForm.number('repetitions',null) }} + + {# calendar in popup #} +
    + + +
    -
    - {{ ExpandedForm.text('name') }} - {{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }} - {{ ExpandedForm.date('repeat_until',null) }} - {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} - {{ ExpandedForm.number('skip', 0) }} +
    +
    +
    - {# three buttons to distinguish type of transaction#} -
    - +
    + {# optional recurrence stuff #} +
    +
    +

    {{ 'optional_for_recurring'|_ }}

    +
    +
    + {{ ExpandedForm.textarea('recurring_description') }} -
    - + {{ ExpandedForm.checkbox('active',1) }} + + {{ ExpandedForm.checkbox('apply_rules',1) }} +
    +
    +
    +
    + +
    +
    + {# mandatory transaction information #} +
    +
    +

    {{ 'mandatory_for_transaction'|_ }}

    +
    +
    +

    {{ 'mandatory_fields_for_tranaction'|_ }}

    + {# three buttons to distinguish type of transaction#} +
    + + +
    + {# end of three buttons#} + + {{ ExpandedForm.text('transaction_description') }} + {# transaction information (mandatory) #} + {{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('amount', []) }} + + {# source account if withdrawal, or if transfer: #} + {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + + {# source account name for deposits: #} + {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} + + {# destination if deposit or transfer: #} + {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + + {# destination account name for withdrawals #} + {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }}
    - -
    -
    -

    {{ 'mandatory_for_transaction'|_ }}

    -
    -
    -

    {{ 'mandatory_fields_for_tranaction'|_ }}

    - {{ ExpandedForm.text('transaction_description') }} - {# transaction information (mandatory) #} - {{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }} - {{ ExpandedForm.amountNoCurrency('amount', []) }} - - {# source account if withdrawal, or if transfer: #} - {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} - - {# source account name for deposits: #} - {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} - - {# destination if deposit or transfer: #} - {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} - - {# destination account name for withdrawals #} - {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} -
    -
    - - -
    -
    -

    {{ 'expected_repetitions'|_ }}

    -
    -
    - Here. -
    -
    -
    - -
    -
    -
    -

    {{ 'optional_for_recurring'|_ }}

    -
    -
    - {{ ExpandedForm.textarea('recurring_description') }} - - {{ ExpandedForm.checkbox('active',1) }} - - {{ ExpandedForm.checkbox('apply_rules',1) }} -
    -
    -
    -
    -

    {{ 'optional_for_transaction'|_ }}

    -
    -
    - {# transaction information (optional) #} - {{ ExpandedForm.currencyList('foreign_currency_id', defaultCurrency.id) }} - {{ ExpandedForm.amountNoCurrency('foreign_amount', []) }} - - {# BUDGET ONLY WHEN CREATING A WITHDRAWAL #} - {% if budgets|length > 1 %} - {{ ExpandedForm.select('budget_id', budgets, null) }} - {% else %} - {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }} - {% endif %} - - {# CATEGORY ALWAYS #} - {{ ExpandedForm.text('category') }} - - {# TAGS #} - {{ ExpandedForm.text('tags') }} - - {# RELATE THIS TRANSFER TO A PIGGY BANK #} - {{ ExpandedForm.select('piggy_bank_id', [], '0') }} -
    -
    - -
    -
    -

    {{ 'options'|_ }}

    -
    -
    - {{ ExpandedForm.optionsList('create','recurrence') }} -
    - -
    -
    + +
    + {# optional transaction information #} +
    +
    +

    {{ 'optional_for_transaction'|_ }}

    +
    +
    + {# transaction information (optional) #} + {{ ExpandedForm.currencyList('foreign_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('foreign_amount', []) }} + + {# BUDGET ONLY WHEN CREATING A WITHDRAWAL #} + {% if budgets|length > 1 %} + {{ ExpandedForm.select('budget_id', budgets, null) }} + {% else %} + {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }} + {% endif %} + + {# CATEGORY ALWAYS #} + {{ ExpandedForm.text('category') }} + + {# TAGS #} + {{ ExpandedForm.text('tags') }} + + {# RELATE THIS TRANSFER TO A PIGGY BANK #} + {{ ExpandedForm.select('piggy_bank_id', [], '0') }} +
    +
    +
    +
    + + {# row with submit stuff. #} +
    +
    +
    +
    +

    {{ 'options'|_ }}

    +
    +
    + {{ ExpandedForm.optionsList('create','recurrence') }} +
    + +
    +
    +
    + {# +
    +
    +
    +

    {{ 'expected_repetitions'|_ }}

    +
    +
    + Here. +
    +
    +
    +
    +
    + #} {% endblock %} {% block scripts %} diff --git a/routes/web.php b/routes/web.php index 2fbd1c00d4..fb991d829c 100755 --- a/routes/web.php +++ b/routes/web.php @@ -618,6 +618,7 @@ Route::group( Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']); Route::get('suggest', ['uses' => 'IndexController@suggest', 'as' => 'suggest']); + Route::get('calendar', ['uses' => 'IndexController@calendar', 'as' => 'calendar']); Route::get('show/{recurrence}', ['uses' => 'IndexController@show', 'as' => 'show']); Route::get('create', ['uses' => 'CreateController@create', 'as' => 'create']); Route::get('edit/{recurrence}', ['uses' => 'EditController@edit', 'as' => 'edit']); From 8f4db78ff20d3b440d045b40fc1f5413594edf82 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 12 Jun 2018 19:03:01 +0200 Subject: [PATCH 017/134] Capital sensitive [skip ci] --- app/Models/RecurrenceTransaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php index 6446797ffb..482f16151a 100644 --- a/app/Models/RecurrenceTransaction.php +++ b/app/Models/RecurrenceTransaction.php @@ -82,7 +82,7 @@ class RecurrenceTransaction extends Model */ public function recurrenceTransactionMeta(): HasMany { - return $this->hasMany(recurrenceTransactionMeta::class,'rt_id'); + return $this->hasMany(RecurrenceTransactionMeta::class,'rt_id'); } /** From b941f590e0ac1ce55ea4cb35bf639e82b14e30c5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 12 Jun 2018 19:44:17 +0200 Subject: [PATCH 018/134] Demo text for recurring. --- resources/views/demo/recurring/index.twig | 1 + resources/views/demo/recurring/recurring-create.twig | 1 + 2 files changed, 2 insertions(+) create mode 100644 resources/views/demo/recurring/index.twig create mode 100644 resources/views/demo/recurring/recurring-create.twig diff --git a/resources/views/demo/recurring/index.twig b/resources/views/demo/recurring/index.twig new file mode 100644 index 0000000000..366736f634 --- /dev/null +++ b/resources/views/demo/recurring/index.twig @@ -0,0 +1 @@ +{{ trans('demo.recurring-index') }} diff --git a/resources/views/demo/recurring/recurring-create.twig b/resources/views/demo/recurring/recurring-create.twig new file mode 100644 index 0000000000..f97b5249ed --- /dev/null +++ b/resources/views/demo/recurring/recurring-create.twig @@ -0,0 +1 @@ +{{ trans('demo.recurring-create') }} From fc011ba1d998b9960261ea8e7b5b324f79910f5c Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 12 Jun 2018 21:38:05 +0200 Subject: [PATCH 019/134] Add calendar view. --- .../Controllers/Recurring/IndexController.php | 4 - public/js/ff/recurring/create.js | 25 +- public/lib/fc/fullcalendar.css | 1293 ++ public/lib/fc/fullcalendar.js | 15010 ++++++++++++++++ public/lib/fc/fullcalendar.min.css | 5 + public/lib/fc/fullcalendar.min.js | 12 + public/lib/fc/fullcalendar.print.css | 176 + public/lib/fc/fullcalendar.print.min.css | 9 + resources/views/recurring/calendar.twig | 16 + resources/views/recurring/create.twig | 51 +- routes/web.php | 2 +- 11 files changed, 16581 insertions(+), 22 deletions(-) create mode 100644 public/lib/fc/fullcalendar.css create mode 100644 public/lib/fc/fullcalendar.js create mode 100644 public/lib/fc/fullcalendar.min.css create mode 100644 public/lib/fc/fullcalendar.min.js create mode 100644 public/lib/fc/fullcalendar.print.css create mode 100644 public/lib/fc/fullcalendar.print.min.css create mode 100644 resources/views/recurring/calendar.twig diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index 74f948415c..c11541832b 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -70,10 +70,6 @@ class IndexController extends Controller */ public function calendar(Request $request) { - $date = new Carbon; - $daysOfWeek = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; - //$firstDayOfMonth = mktime(0, 0, 0, $month, 1, $year); - return view('recurring.calendar'); } diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index ba79cd4e4e..5f2317fe68 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -40,12 +40,27 @@ $(document).ready(function () { $('#calendar-link').on('click', showRepCalendar); }); +/** + * + */ function showRepCalendar() { - - // fill model with calendar: - - - $('#defaultModal').modal({}); + $('#recurring_calendar').fullCalendar( + { + defaultDate: '2018-06-13', + editable: false, + height: 400, + width: 200, + contentHeight: 300, + aspectRatio: 1.25, + eventLimit: true, // allow "more" link when too many events + events: [ + { + title: '', + start: '2018-06-14' + } + ] + }); + $('#calendarModal').modal('show'); return false; } diff --git a/public/lib/fc/fullcalendar.css b/public/lib/fc/fullcalendar.css new file mode 100644 index 0000000000..dcbc999758 --- /dev/null +++ b/public/lib/fc/fullcalendar.css @@ -0,0 +1,1293 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +.fc { + direction: ltr; + text-align: left; } + +.fc-rtl { + text-align: right; } + +body .fc { + /* extra precedence to overcome jqui */ + font-size: 1em; } + +/* Colors +--------------------------------------------------------------------------------------------------*/ +.fc-highlight { + /* when user is selecting cells */ + background: #bce8f1; + opacity: .3; } + +.fc-bgevent { + /* default look for background events */ + background: #8fdf82; + opacity: .3; } + +.fc-nonbusiness { + /* default look for non-business-hours areas */ + /* will inherit .fc-bgevent's styles */ + background: #d7d7d7; } + +/* Buttons (styled ') + .click(function (ev) { + // don't process clicks for disabled buttons + if (!buttonEl.hasClass(theme.getClass('stateDisabled'))) { + buttonClick(ev); + // after the click action, if the button becomes the "active" tab, or disabled, + // it should never have a hover class, so remove it now. + if (buttonEl.hasClass(theme.getClass('stateActive')) || + buttonEl.hasClass(theme.getClass('stateDisabled'))) { + buttonEl.removeClass(theme.getClass('stateHover')); + } + } + }) + .mousedown(function () { + // the *down* effect (mouse pressed in). + // only on buttons that are not the "active" tab, or disabled + buttonEl + .not('.' + theme.getClass('stateActive')) + .not('.' + theme.getClass('stateDisabled')) + .addClass(theme.getClass('stateDown')); + }) + .mouseup(function () { + // undo the *down* effect + buttonEl.removeClass(theme.getClass('stateDown')); + }) + .hover(function () { + // the *hover* effect. + // only on buttons that are not the "active" tab, or disabled + buttonEl + .not('.' + theme.getClass('stateActive')) + .not('.' + theme.getClass('stateDisabled')) + .addClass(theme.getClass('stateHover')); + }, function () { + // undo the *hover* effect + buttonEl + .removeClass(theme.getClass('stateHover')) + .removeClass(theme.getClass('stateDown')); // if mouseleave happens before mouseup + }); + groupChildren = groupChildren.add(buttonEl); + } + } + }); + if (isOnlyButtons) { + groupChildren + .first().addClass(theme.getClass('cornerLeft')).end() + .last().addClass(theme.getClass('cornerRight')).end(); + } + if (groupChildren.length > 1) { + groupEl = $('
    '); + if (isOnlyButtons) { + groupEl.addClass(theme.getClass('buttonGroup')); + } + groupEl.append(groupChildren); + sectionEl.append(groupEl); + } + else { + sectionEl.append(groupChildren); // 1 or 0 children + } + }); + } + return sectionEl; + }; + Toolbar.prototype.updateTitle = function (text) { + if (this.el) { + this.el.find('h2').text(text); + } + }; + Toolbar.prototype.activateButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .addClass(this.calendar.theme.getClass('stateActive')); + } + }; + Toolbar.prototype.deactivateButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .removeClass(this.calendar.theme.getClass('stateActive')); + } + }; + Toolbar.prototype.disableButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .prop('disabled', true) + .addClass(this.calendar.theme.getClass('stateDisabled')); + } + }; + Toolbar.prototype.enableButton = function (buttonName) { + if (this.el) { + this.el.find('.fc-' + buttonName + '-button') + .prop('disabled', false) + .removeClass(this.calendar.theme.getClass('stateDisabled')); + } + }; + Toolbar.prototype.getViewsWithButtons = function () { + return this.viewsWithButtons; + }; + return Toolbar; +}()); +exports.default = Toolbar; + + +/***/ }), +/* 240 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var options_1 = __webpack_require__(32); +var locale_1 = __webpack_require__(31); +var Model_1 = __webpack_require__(48); +var OptionsManager = /** @class */ (function (_super) { + tslib_1.__extends(OptionsManager, _super); + function OptionsManager(_calendar, overrides) { + var _this = _super.call(this) || this; + _this._calendar = _calendar; + _this.overrides = $.extend({}, overrides); // make a copy + _this.dynamicOverrides = {}; + _this.compute(); + return _this; + } + OptionsManager.prototype.add = function (newOptionHash) { + var optionCnt = 0; + var optionName; + this.recordOverrides(newOptionHash); // will trigger this model's watchers + for (optionName in newOptionHash) { + optionCnt++; + } + // special-case handling of single option change. + // if only one option change, `optionName` will be its name. + if (optionCnt === 1) { + if (optionName === 'height' || optionName === 'contentHeight' || optionName === 'aspectRatio') { + this._calendar.updateViewSize(true); // isResize=true + return; + } + else if (optionName === 'defaultDate') { + return; // can't change date this way. use gotoDate instead + } + else if (optionName === 'businessHours') { + return; // this model already reacts to this + } + else if (/^(event|select)(Overlap|Constraint|Allow)$/.test(optionName)) { + return; // doesn't affect rendering. only interactions. + } + else if (optionName === 'timezone') { + this._calendar.view.flash('initialEvents'); + return; + } + } + // catch-all. rerender the header and footer and rebuild/rerender the current view + this._calendar.renderHeader(); + this._calendar.renderFooter(); + // even non-current views will be affected by this option change. do before rerender + // TODO: detangle + this._calendar.viewsByType = {}; + this._calendar.reinitView(); + }; + // Computes the flattened options hash for the calendar and assigns to `this.options`. + // Assumes this.overrides and this.dynamicOverrides have already been initialized. + OptionsManager.prototype.compute = function () { + var locale; + var localeDefaults; + var isRTL; + var dirDefaults; + var rawOptions; + locale = util_1.firstDefined(// explicit locale option given? + this.dynamicOverrides.locale, this.overrides.locale); + localeDefaults = locale_1.localeOptionHash[locale]; + if (!localeDefaults) { + locale = options_1.globalDefaults.locale; + localeDefaults = locale_1.localeOptionHash[locale] || {}; + } + isRTL = util_1.firstDefined(// based on options computed so far, is direction RTL? + this.dynamicOverrides.isRTL, this.overrides.isRTL, localeDefaults.isRTL, options_1.globalDefaults.isRTL); + dirDefaults = isRTL ? options_1.rtlDefaults : {}; + this.dirDefaults = dirDefaults; + this.localeDefaults = localeDefaults; + rawOptions = options_1.mergeOptions([ + options_1.globalDefaults, + dirDefaults, + localeDefaults, + this.overrides, + this.dynamicOverrides + ]); + locale_1.populateInstanceComputableOptions(rawOptions); // fill in gaps with computed options + this.reset(rawOptions); + }; + // stores the new options internally, but does not rerender anything. + OptionsManager.prototype.recordOverrides = function (newOptionHash) { + var optionName; + for (optionName in newOptionHash) { + this.dynamicOverrides[optionName] = newOptionHash[optionName]; + } + this._calendar.viewSpecManager.clearCache(); // the dynamic override invalidates the options in this cache, so just clear it + this.compute(); // this.options needs to be recomputed after the dynamic override + }; + return OptionsManager; +}(Model_1.default)); +exports.default = OptionsManager; + + +/***/ }), +/* 241 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var moment = __webpack_require__(0); +var $ = __webpack_require__(3); +var ViewRegistry_1 = __webpack_require__(22); +var util_1 = __webpack_require__(4); +var options_1 = __webpack_require__(32); +var locale_1 = __webpack_require__(31); +var ViewSpecManager = /** @class */ (function () { + function ViewSpecManager(optionsManager, _calendar) { + this.optionsManager = optionsManager; + this._calendar = _calendar; + this.clearCache(); + } + ViewSpecManager.prototype.clearCache = function () { + this.viewSpecCache = {}; + }; + // Gets information about how to create a view. Will use a cache. + ViewSpecManager.prototype.getViewSpec = function (viewType) { + var cache = this.viewSpecCache; + return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType)); + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + ViewSpecManager.prototype.getUnitViewSpec = function (unit) { + var viewTypes; + var i; + var spec; + if ($.inArray(unit, util_1.unitsDesc) !== -1) { + // put views that have buttons first. there will be duplicates, but oh well + viewTypes = this._calendar.header.getViewsWithButtons(); // TODO: include footer as well? + $.each(ViewRegistry_1.viewHash, function (viewType) { + viewTypes.push(viewType); + }); + for (i = 0; i < viewTypes.length; i++) { + spec = this.getViewSpec(viewTypes[i]); + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + } + }; + // Builds an object with information on how to create a given view + ViewSpecManager.prototype.buildViewSpec = function (requestedViewType) { + var viewOverrides = this.optionsManager.overrides.views || {}; + var specChain = []; // for the view. lowest to highest priority + var defaultsChain = []; // for the view. lowest to highest priority + var overridesChain = []; // for the view. lowest to highest priority + var viewType = requestedViewType; + var spec; // for the view + var overrides; // for the view + var durationInput; + var duration; + var unit; + // iterate from the specific view definition to a more general one until we hit an actual View class + while (viewType) { + spec = ViewRegistry_1.viewHash[viewType]; + overrides = viewOverrides[viewType]; + viewType = null; // clear. might repopulate for another iteration + if (typeof spec === 'function') { + spec = { 'class': spec }; + } + if (spec) { + specChain.unshift(spec); + defaultsChain.unshift(spec.defaults || {}); + durationInput = durationInput || spec.duration; + viewType = viewType || spec.type; + } + if (overrides) { + overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level + durationInput = durationInput || overrides.duration; + viewType = viewType || overrides.type; + } + } + spec = util_1.mergeProps(specChain); + spec.type = requestedViewType; + if (!spec['class']) { + return false; + } + // fall back to top-level `duration` option + durationInput = durationInput || + this.optionsManager.dynamicOverrides.duration || + this.optionsManager.overrides.duration; + if (durationInput) { + duration = moment.duration(durationInput); + if (duration.valueOf()) { + unit = util_1.computeDurationGreatestUnit(duration, durationInput); + spec.duration = duration; + spec.durationUnit = unit; + // view is a single-unit duration, like "week" or "day" + // incorporate options for this. lowest priority + if (duration.as(unit) === 1) { + spec.singleUnit = unit; + overridesChain.unshift(viewOverrides[unit] || {}); + } + } + } + spec.defaults = options_1.mergeOptions(defaultsChain); + spec.overrides = options_1.mergeOptions(overridesChain); + this.buildViewSpecOptions(spec); + this.buildViewSpecButtonText(spec, requestedViewType); + return spec; + }; + // Builds and assigns a view spec's options object from its already-assigned defaults and overrides + ViewSpecManager.prototype.buildViewSpecOptions = function (spec) { + var optionsManager = this.optionsManager; + spec.options = options_1.mergeOptions([ + options_1.globalDefaults, + spec.defaults, + optionsManager.dirDefaults, + optionsManager.localeDefaults, + optionsManager.overrides, + spec.overrides, + optionsManager.dynamicOverrides // dynamically set via setter. highest precedence + ]); + locale_1.populateInstanceComputableOptions(spec.options); + }; + // Computes and assigns a view spec's buttonText-related options + ViewSpecManager.prototype.buildViewSpecButtonText = function (spec, requestedViewType) { + var optionsManager = this.optionsManager; + // given an options object with a possible `buttonText` hash, lookup the buttonText for the + // requested view, falling back to a generic unit entry like "week" or "day" + function queryButtonText(options) { + var buttonText = options.buttonText || {}; + return buttonText[requestedViewType] || + // view can decide to look up a certain key + (spec.buttonTextKey ? buttonText[spec.buttonTextKey] : null) || + // a key like "month" + (spec.singleUnit ? buttonText[spec.singleUnit] : null); + } + // highest to lowest priority + spec.buttonTextOverride = + queryButtonText(optionsManager.dynamicOverrides) || + queryButtonText(optionsManager.overrides) || // constructor-specified buttonText lookup hash takes precedence + spec.overrides.buttonText; // `buttonText` for view-specific options is a string + // highest to lowest priority. mirrors buildViewSpecOptions + spec.buttonTextDefault = + queryButtonText(optionsManager.localeDefaults) || + queryButtonText(optionsManager.dirDefaults) || + spec.defaults.buttonText || // a single string. from ViewSubclass.defaults + queryButtonText(options_1.globalDefaults) || + (spec.duration ? this._calendar.humanizeDuration(spec.duration) : null) || // like "3 days" + requestedViewType; // fall back to given view name + }; + return ViewSpecManager; +}()); +exports.default = ViewSpecManager; + + +/***/ }), +/* 242 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var EventPeriod_1 = __webpack_require__(243); +var ArrayEventSource_1 = __webpack_require__(52); +var EventSource_1 = __webpack_require__(6); +var EventSourceParser_1 = __webpack_require__(38); +var SingleEventDef_1 = __webpack_require__(13); +var EventInstanceGroup_1 = __webpack_require__(18); +var EmitterMixin_1 = __webpack_require__(11); +var ListenerMixin_1 = __webpack_require__(7); +var EventManager = /** @class */ (function () { + function EventManager(calendar) { + this.calendar = calendar; + this.stickySource = new ArrayEventSource_1.default(calendar); + this.otherSources = []; + } + EventManager.prototype.requestEvents = function (start, end, timezone, force) { + if (force || + !this.currentPeriod || + !this.currentPeriod.isWithinRange(start, end) || + timezone !== this.currentPeriod.timezone) { + this.setPeriod(// will change this.currentPeriod + new EventPeriod_1.default(start, end, timezone)); + } + return this.currentPeriod.whenReleased(); + }; + // Source Adding/Removing + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.addSource = function (eventSource) { + this.otherSources.push(eventSource); + if (this.currentPeriod) { + this.currentPeriod.requestSource(eventSource); // might release + } + }; + EventManager.prototype.removeSource = function (doomedSource) { + util_1.removeExact(this.otherSources, doomedSource); + if (this.currentPeriod) { + this.currentPeriod.purgeSource(doomedSource); // might release + } + }; + EventManager.prototype.removeAllSources = function () { + this.otherSources = []; + if (this.currentPeriod) { + this.currentPeriod.purgeAllSources(); // might release + } + }; + // Source Refetching + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.refetchSource = function (eventSource) { + var currentPeriod = this.currentPeriod; + if (currentPeriod) { + currentPeriod.freeze(); + currentPeriod.purgeSource(eventSource); + currentPeriod.requestSource(eventSource); + currentPeriod.thaw(); + } + }; + EventManager.prototype.refetchAllSources = function () { + var currentPeriod = this.currentPeriod; + if (currentPeriod) { + currentPeriod.freeze(); + currentPeriod.purgeAllSources(); + currentPeriod.requestSources(this.getSources()); + currentPeriod.thaw(); + } + }; + // Source Querying + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.getSources = function () { + return [this.stickySource].concat(this.otherSources); + }; + // like querySources, but accepts multple match criteria (like multiple IDs) + EventManager.prototype.multiQuerySources = function (matchInputs) { + // coerce into an array + if (!matchInputs) { + matchInputs = []; + } + else if (!$.isArray(matchInputs)) { + matchInputs = [matchInputs]; + } + var matchingSources = []; + var i; + // resolve raw inputs to real event source objects + for (i = 0; i < matchInputs.length; i++) { + matchingSources.push.apply(// append + matchingSources, this.querySources(matchInputs[i])); + } + return matchingSources; + }; + // matchInput can either by a real event source object, an ID, or the function/URL for the source. + // returns an array of matching source objects. + EventManager.prototype.querySources = function (matchInput) { + var sources = this.otherSources; + var i; + var source; + // given a proper event source object + for (i = 0; i < sources.length; i++) { + source = sources[i]; + if (source === matchInput) { + return [source]; + } + } + // an ID match + source = this.getSourceById(EventSource_1.default.normalizeId(matchInput)); + if (source) { + return [source]; + } + // parse as an event source + matchInput = EventSourceParser_1.default.parse(matchInput, this.calendar); + if (matchInput) { + return $.grep(sources, function (source) { + return isSourcesEquivalent(matchInput, source); + }); + } + }; + /* + ID assumed to already be normalized + */ + EventManager.prototype.getSourceById = function (id) { + return $.grep(this.otherSources, function (source) { + return source.id && source.id === id; + })[0]; + }; + // Event-Period + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.setPeriod = function (eventPeriod) { + if (this.currentPeriod) { + this.unbindPeriod(this.currentPeriod); + this.currentPeriod = null; + } + this.currentPeriod = eventPeriod; + this.bindPeriod(eventPeriod); + eventPeriod.requestSources(this.getSources()); + }; + EventManager.prototype.bindPeriod = function (eventPeriod) { + this.listenTo(eventPeriod, 'release', function (eventsPayload) { + this.trigger('release', eventsPayload); + }); + }; + EventManager.prototype.unbindPeriod = function (eventPeriod) { + this.stopListeningTo(eventPeriod); + }; + // Event Getting/Adding/Removing + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.getEventDefByUid = function (uid) { + if (this.currentPeriod) { + return this.currentPeriod.getEventDefByUid(uid); + } + }; + EventManager.prototype.addEventDef = function (eventDef, isSticky) { + if (isSticky) { + this.stickySource.addEventDef(eventDef); + } + if (this.currentPeriod) { + this.currentPeriod.addEventDef(eventDef); // might release + } + }; + EventManager.prototype.removeEventDefsById = function (eventId) { + this.getSources().forEach(function (eventSource) { + eventSource.removeEventDefsById(eventId); + }); + if (this.currentPeriod) { + this.currentPeriod.removeEventDefsById(eventId); // might release + } + }; + EventManager.prototype.removeAllEventDefs = function () { + this.getSources().forEach(function (eventSource) { + eventSource.removeAllEventDefs(); + }); + if (this.currentPeriod) { + this.currentPeriod.removeAllEventDefs(); + } + }; + // Event Mutating + // ----------------------------------------------------------------------------------------------------------------- + /* + Returns an undo function. + */ + EventManager.prototype.mutateEventsWithId = function (eventDefId, eventDefMutation) { + var currentPeriod = this.currentPeriod; + var eventDefs; + var undoFuncs = []; + if (currentPeriod) { + currentPeriod.freeze(); + eventDefs = currentPeriod.getEventDefsById(eventDefId); + eventDefs.forEach(function (eventDef) { + // add/remove esp because id might change + currentPeriod.removeEventDef(eventDef); + undoFuncs.push(eventDefMutation.mutateSingle(eventDef)); + currentPeriod.addEventDef(eventDef); + }); + currentPeriod.thaw(); + return function () { + currentPeriod.freeze(); + for (var i = 0; i < eventDefs.length; i++) { + currentPeriod.removeEventDef(eventDefs[i]); + undoFuncs[i](); + currentPeriod.addEventDef(eventDefs[i]); + } + currentPeriod.thaw(); + }; + } + return function () { }; + }; + /* + copies and then mutates + */ + EventManager.prototype.buildMutatedEventInstanceGroup = function (eventDefId, eventDefMutation) { + var eventDefs = this.getEventDefsById(eventDefId); + var i; + var defCopy; + var allInstances = []; + for (i = 0; i < eventDefs.length; i++) { + defCopy = eventDefs[i].clone(); + if (defCopy instanceof SingleEventDef_1.default) { + eventDefMutation.mutateSingle(defCopy); + allInstances.push.apply(allInstances, // append + defCopy.buildInstances()); + } + } + return new EventInstanceGroup_1.default(allInstances); + }; + // Freezing + // ----------------------------------------------------------------------------------------------------------------- + EventManager.prototype.freeze = function () { + if (this.currentPeriod) { + this.currentPeriod.freeze(); + } + }; + EventManager.prototype.thaw = function () { + if (this.currentPeriod) { + this.currentPeriod.thaw(); + } + }; + // methods that simply forward to EventPeriod + EventManager.prototype.getEventDefsById = function (eventDefId) { + return this.currentPeriod.getEventDefsById(eventDefId); + }; + EventManager.prototype.getEventInstances = function () { + return this.currentPeriod.getEventInstances(); + }; + EventManager.prototype.getEventInstancesWithId = function (eventDefId) { + return this.currentPeriod.getEventInstancesWithId(eventDefId); + }; + EventManager.prototype.getEventInstancesWithoutId = function (eventDefId) { + return this.currentPeriod.getEventInstancesWithoutId(eventDefId); + }; + return EventManager; +}()); +exports.default = EventManager; +EmitterMixin_1.default.mixInto(EventManager); +ListenerMixin_1.default.mixInto(EventManager); +function isSourcesEquivalent(source0, source1) { + return source0.getPrimitive() === source1.getPrimitive(); +} + + +/***/ }), +/* 243 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var Promise_1 = __webpack_require__(20); +var EmitterMixin_1 = __webpack_require__(11); +var UnzonedRange_1 = __webpack_require__(5); +var EventInstanceGroup_1 = __webpack_require__(18); +var EventPeriod = /** @class */ (function () { + function EventPeriod(start, end, timezone) { + this.pendingCnt = 0; + this.freezeDepth = 0; + this.stuntedReleaseCnt = 0; + this.releaseCnt = 0; + this.start = start; + this.end = end; + this.timezone = timezone; + this.unzonedRange = new UnzonedRange_1.default(start.clone().stripZone(), end.clone().stripZone()); + this.requestsByUid = {}; + this.eventDefsByUid = {}; + this.eventDefsById = {}; + this.eventInstanceGroupsById = {}; + } + EventPeriod.prototype.isWithinRange = function (start, end) { + // TODO: use a range util function? + return !start.isBefore(this.start) && !end.isAfter(this.end); + }; + // Requesting and Purging + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.requestSources = function (sources) { + this.freeze(); + for (var i = 0; i < sources.length; i++) { + this.requestSource(sources[i]); + } + this.thaw(); + }; + EventPeriod.prototype.requestSource = function (source) { + var _this = this; + var request = { source: source, status: 'pending', eventDefs: null }; + this.requestsByUid[source.uid] = request; + this.pendingCnt += 1; + source.fetch(this.start, this.end, this.timezone).then(function (eventDefs) { + if (request.status !== 'cancelled') { + request.status = 'completed'; + request.eventDefs = eventDefs; + _this.addEventDefs(eventDefs); + _this.pendingCnt--; + _this.tryRelease(); + } + }, function () { + if (request.status !== 'cancelled') { + request.status = 'failed'; + _this.pendingCnt--; + _this.tryRelease(); + } + }); + }; + EventPeriod.prototype.purgeSource = function (source) { + var request = this.requestsByUid[source.uid]; + if (request) { + delete this.requestsByUid[source.uid]; + if (request.status === 'pending') { + request.status = 'cancelled'; + this.pendingCnt--; + this.tryRelease(); + } + else if (request.status === 'completed') { + request.eventDefs.forEach(this.removeEventDef.bind(this)); + } + } + }; + EventPeriod.prototype.purgeAllSources = function () { + var requestsByUid = this.requestsByUid; + var uid; + var request; + var completedCnt = 0; + for (uid in requestsByUid) { + request = requestsByUid[uid]; + if (request.status === 'pending') { + request.status = 'cancelled'; + } + else if (request.status === 'completed') { + completedCnt++; + } + } + this.requestsByUid = {}; + this.pendingCnt = 0; + if (completedCnt) { + this.removeAllEventDefs(); // might release + } + }; + // Event Definitions + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.getEventDefByUid = function (eventDefUid) { + return this.eventDefsByUid[eventDefUid]; + }; + EventPeriod.prototype.getEventDefsById = function (eventDefId) { + var a = this.eventDefsById[eventDefId]; + if (a) { + return a.slice(); // clone + } + return []; + }; + EventPeriod.prototype.addEventDefs = function (eventDefs) { + for (var i = 0; i < eventDefs.length; i++) { + this.addEventDef(eventDefs[i]); + } + }; + EventPeriod.prototype.addEventDef = function (eventDef) { + var eventDefsById = this.eventDefsById; + var eventDefId = eventDef.id; + var eventDefs = eventDefsById[eventDefId] || (eventDefsById[eventDefId] = []); + var eventInstances = eventDef.buildInstances(this.unzonedRange); + var i; + eventDefs.push(eventDef); + this.eventDefsByUid[eventDef.uid] = eventDef; + for (i = 0; i < eventInstances.length; i++) { + this.addEventInstance(eventInstances[i], eventDefId); + } + }; + EventPeriod.prototype.removeEventDefsById = function (eventDefId) { + var _this = this; + this.getEventDefsById(eventDefId).forEach(function (eventDef) { + _this.removeEventDef(eventDef); + }); + }; + EventPeriod.prototype.removeAllEventDefs = function () { + var isEmpty = $.isEmptyObject(this.eventDefsByUid); + this.eventDefsByUid = {}; + this.eventDefsById = {}; + this.eventInstanceGroupsById = {}; + if (!isEmpty) { + this.tryRelease(); + } + }; + EventPeriod.prototype.removeEventDef = function (eventDef) { + var eventDefsById = this.eventDefsById; + var eventDefs = eventDefsById[eventDef.id]; + delete this.eventDefsByUid[eventDef.uid]; + if (eventDefs) { + util_1.removeExact(eventDefs, eventDef); + if (!eventDefs.length) { + delete eventDefsById[eventDef.id]; + } + this.removeEventInstancesForDef(eventDef); + } + }; + // Event Instances + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.getEventInstances = function () { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var eventInstances = []; + var id; + for (id in eventInstanceGroupsById) { + eventInstances.push.apply(eventInstances, // append + eventInstanceGroupsById[id].eventInstances); + } + return eventInstances; + }; + EventPeriod.prototype.getEventInstancesWithId = function (eventDefId) { + var eventInstanceGroup = this.eventInstanceGroupsById[eventDefId]; + if (eventInstanceGroup) { + return eventInstanceGroup.eventInstances.slice(); // clone + } + return []; + }; + EventPeriod.prototype.getEventInstancesWithoutId = function (eventDefId) { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var matchingInstances = []; + var id; + for (id in eventInstanceGroupsById) { + if (id !== eventDefId) { + matchingInstances.push.apply(matchingInstances, // append + eventInstanceGroupsById[id].eventInstances); + } + } + return matchingInstances; + }; + EventPeriod.prototype.addEventInstance = function (eventInstance, eventDefId) { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var eventInstanceGroup = eventInstanceGroupsById[eventDefId] || + (eventInstanceGroupsById[eventDefId] = new EventInstanceGroup_1.default()); + eventInstanceGroup.eventInstances.push(eventInstance); + this.tryRelease(); + }; + EventPeriod.prototype.removeEventInstancesForDef = function (eventDef) { + var eventInstanceGroupsById = this.eventInstanceGroupsById; + var eventInstanceGroup = eventInstanceGroupsById[eventDef.id]; + var removeCnt; + if (eventInstanceGroup) { + removeCnt = util_1.removeMatching(eventInstanceGroup.eventInstances, function (currentEventInstance) { + return currentEventInstance.def === eventDef; + }); + if (!eventInstanceGroup.eventInstances.length) { + delete eventInstanceGroupsById[eventDef.id]; + } + if (removeCnt) { + this.tryRelease(); + } + } + }; + // Releasing and Freezing + // ----------------------------------------------------------------------------------------------------------------- + EventPeriod.prototype.tryRelease = function () { + if (!this.pendingCnt) { + if (!this.freezeDepth) { + this.release(); + } + else { + this.stuntedReleaseCnt++; + } + } + }; + EventPeriod.prototype.release = function () { + this.releaseCnt++; + this.trigger('release', this.eventInstanceGroupsById); + }; + EventPeriod.prototype.whenReleased = function () { + var _this = this; + if (this.releaseCnt) { + return Promise_1.default.resolve(this.eventInstanceGroupsById); + } + else { + return Promise_1.default.construct(function (onResolve) { + _this.one('release', onResolve); + }); + } + }; + EventPeriod.prototype.freeze = function () { + if (!(this.freezeDepth++)) { + this.stuntedReleaseCnt = 0; + } + }; + EventPeriod.prototype.thaw = function () { + if (!(--this.freezeDepth) && this.stuntedReleaseCnt && !this.pendingCnt) { + this.release(); + } + }; + return EventPeriod; +}()); +exports.default = EventPeriod; +EmitterMixin_1.default.mixInto(EventPeriod); + + +/***/ }), +/* 244 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var ListenerMixin_1 = __webpack_require__(7); +/* Creates a clone of an element and lets it track the mouse as it moves +----------------------------------------------------------------------------------------------------------------------*/ +var MouseFollower = /** @class */ (function () { + function MouseFollower(sourceEl, options) { + this.isFollowing = false; + this.isHidden = false; + this.isAnimating = false; // doing the revert animation? + this.options = options = options || {}; + this.sourceEl = sourceEl; + this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent + } + // Causes the element to start following the mouse + MouseFollower.prototype.start = function (ev) { + if (!this.isFollowing) { + this.isFollowing = true; + this.y0 = util_1.getEvY(ev); + this.x0 = util_1.getEvX(ev); + this.topDelta = 0; + this.leftDelta = 0; + if (!this.isHidden) { + this.updatePosition(); + } + if (util_1.getEvIsTouch(ev)) { + this.listenTo($(document), 'touchmove', this.handleMove); + } + else { + this.listenTo($(document), 'mousemove', this.handleMove); + } + } + }; + // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. + // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. + MouseFollower.prototype.stop = function (shouldRevert, callback) { + var _this = this; + var revertDuration = this.options.revertDuration; + var complete = function () { + _this.isAnimating = false; + _this.removeElement(); + _this.top0 = _this.left0 = null; // reset state for future updatePosition calls + if (callback) { + callback(); + } + }; + if (this.isFollowing && !this.isAnimating) { + this.isFollowing = false; + this.stopListeningTo($(document)); + if (shouldRevert && revertDuration && !this.isHidden) { + this.isAnimating = true; + this.el.animate({ + top: this.top0, + left: this.left0 + }, { + duration: revertDuration, + complete: complete + }); + } + else { + complete(); + } + } + }; + // Gets the tracking element. Create it if necessary + MouseFollower.prototype.getEl = function () { + var el = this.el; + if (!el) { + el = this.el = this.sourceEl.clone() + .addClass(this.options.additionalClass || '') + .css({ + position: 'absolute', + visibility: '', + display: this.isHidden ? 'none' : '', + margin: 0, + right: 'auto', + bottom: 'auto', + width: this.sourceEl.width(), + height: this.sourceEl.height(), + opacity: this.options.opacity || '', + zIndex: this.options.zIndex + }); + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + el.addClass('fc-unselectable'); + el.appendTo(this.parentEl); + } + return el; + }; + // Removes the tracking element if it has already been created + MouseFollower.prototype.removeElement = function () { + if (this.el) { + this.el.remove(); + this.el = null; + } + }; + // Update the CSS position of the tracking element + MouseFollower.prototype.updatePosition = function () { + var sourceOffset; + var origin; + this.getEl(); // ensure this.el + // make sure origin info was computed + if (this.top0 == null) { + sourceOffset = this.sourceEl.offset(); + origin = this.el.offsetParent().offset(); + this.top0 = sourceOffset.top - origin.top; + this.left0 = sourceOffset.left - origin.left; + } + this.el.css({ + top: this.top0 + this.topDelta, + left: this.left0 + this.leftDelta + }); + }; + // Gets called when the user moves the mouse + MouseFollower.prototype.handleMove = function (ev) { + this.topDelta = util_1.getEvY(ev) - this.y0; + this.leftDelta = util_1.getEvX(ev) - this.x0; + if (!this.isHidden) { + this.updatePosition(); + } + }; + // Temporarily makes the tracking element invisible. Can be called before following starts + MouseFollower.prototype.hide = function () { + if (!this.isHidden) { + this.isHidden = true; + if (this.el) { + this.el.hide(); + } + } + }; + // Show the tracking element after it has been temporarily hidden + MouseFollower.prototype.show = function () { + if (this.isHidden) { + this.isHidden = false; + this.updatePosition(); + this.getEl().show(); + } + }; + return MouseFollower; +}()); +exports.default = MouseFollower; +ListenerMixin_1.default.mixInto(MouseFollower); + + +/***/ }), +/* 245 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var HitDragListener_1 = __webpack_require__(23); +var Interaction_1 = __webpack_require__(15); +var DateClicking = /** @class */ (function (_super) { + tslib_1.__extends(DateClicking, _super); + /* + component must implement: + - bindDateHandlerToEl + - getSafeHitFootprint + - getHitEl + */ + function DateClicking(component) { + var _this = _super.call(this, component) || this; + _this.dragListener = _this.buildDragListener(); + return _this; + } + DateClicking.prototype.end = function () { + this.dragListener.endInteraction(); + }; + DateClicking.prototype.bindToEl = function (el) { + var component = this.component; + var dragListener = this.dragListener; + component.bindDateHandlerToEl(el, 'mousedown', function (ev) { + if (!component.shouldIgnoreMouse()) { + dragListener.startInteraction(ev); + } + }); + component.bindDateHandlerToEl(el, 'touchstart', function (ev) { + if (!component.shouldIgnoreTouch()) { + dragListener.startInteraction(ev); + } + }); + }; + // Creates a listener that tracks the user's drag across day elements, for day clicking. + DateClicking.prototype.buildDragListener = function () { + var _this = this; + var component = this.component; + var dayClickHit; // null if invalid dayClick + var dragListener = new HitDragListener_1.default(component, { + scroll: this.opt('dragScroll'), + interactionStart: function () { + dayClickHit = dragListener.origHit; + }, + hitOver: function (hit, isOrig, origHit) { + // if user dragged to another cell at any point, it can no longer be a dayClick + if (!isOrig) { + dayClickHit = null; + } + }, + hitOut: function () { + dayClickHit = null; + }, + interactionEnd: function (ev, isCancelled) { + var componentFootprint; + if (!isCancelled && dayClickHit) { + componentFootprint = component.getSafeHitFootprint(dayClickHit); + if (componentFootprint) { + _this.view.triggerDayClick(componentFootprint, component.getHitEl(dayClickHit), ev); + } + } + } + }); + // because dragListener won't be called with any time delay, "dragging" will begin immediately, + // which will kill any touchmoving/scrolling. Prevent this. + dragListener.shouldCancelTouchScroll = false; + dragListener.scrollAlwaysKills = true; + return dragListener; + }; + return DateClicking; +}(Interaction_1.default)); +exports.default = DateClicking; + + +/***/ }), +/* 246 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var EventRenderer_1 = __webpack_require__(42); +/* +Only handles foreground segs. +Does not own rendering. Use for low-level util methods by TimeGrid. +*/ +var TimeGridEventRenderer = /** @class */ (function (_super) { + tslib_1.__extends(TimeGridEventRenderer, _super); + function TimeGridEventRenderer(timeGrid, fillRenderer) { + var _this = _super.call(this, timeGrid, fillRenderer) || this; + _this.timeGrid = timeGrid; + return _this; + } + TimeGridEventRenderer.prototype.renderFgSegs = function (segs) { + this.renderFgSegsIntoContainers(segs, this.timeGrid.fgContainerEls); + }; + // Given an array of foreground segments, render a DOM element for each, computes position, + // and attaches to the column inner-container elements. + TimeGridEventRenderer.prototype.renderFgSegsIntoContainers = function (segs, containerEls) { + var segsByCol; + var col; + segsByCol = this.timeGrid.groupSegsByCol(segs); + for (col = 0; col < this.timeGrid.colCnt; col++) { + this.updateFgSegCoords(segsByCol[col]); + } + this.timeGrid.attachSegsByCol(segsByCol, containerEls); + }; + TimeGridEventRenderer.prototype.unrenderFgSegs = function () { + if (this.fgSegs) { + this.fgSegs.forEach(function (seg) { + seg.el.remove(); + }); + } + }; + // Computes a default event time formatting string if `timeFormat` is not explicitly defined + TimeGridEventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM) + }; + // Computes a default `displayEventEnd` value if one is not expliclty defined + TimeGridEventRenderer.prototype.computeDisplayEventEnd = function () { + return true; + }; + // Renders the HTML for a single event segment's default rendering + TimeGridEventRenderer.prototype.fgSegHtml = function (seg, disableResizing) { + var view = this.view; + var calendar = view.calendar; + var componentFootprint = seg.footprint.componentFootprint; + var isAllDay = componentFootprint.isAllDay; + var eventDef = seg.footprint.eventDef; + var isDraggable = view.isEventDefDraggable(eventDef); + var isResizableFromStart = !disableResizing && seg.isStart && view.isEventDefResizableFromStart(eventDef); + var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventDefResizableFromEnd(eventDef); + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd); + var skinCss = util_1.cssToStr(this.getSkinCss(eventDef)); + var timeText; + var fullTimeText; // more verbose time text. for the print stylesheet + var startTimeText; // just the start time text + classes.unshift('fc-time-grid-event', 'fc-v-event'); + // if the event appears to span more than one day... + if (view.isMultiDayRange(componentFootprint.unzonedRange)) { + // Don't display time text on segments that run entirely through a day. + // That would appear as midnight-midnight and would look dumb. + // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) + if (seg.isStart || seg.isEnd) { + var zonedStart = calendar.msToMoment(seg.startMs); + var zonedEnd = calendar.msToMoment(seg.endMs); + timeText = this._getTimeText(zonedStart, zonedEnd, isAllDay); + fullTimeText = this._getTimeText(zonedStart, zonedEnd, isAllDay, 'LT'); + startTimeText = this._getTimeText(zonedStart, zonedEnd, isAllDay, null, false); // displayEnd=false + } + } + else { + // Display the normal time text for the *event's* times + timeText = this.getTimeText(seg.footprint); + fullTimeText = this.getTimeText(seg.footprint, 'LT'); + startTimeText = this.getTimeText(seg.footprint, null, false); // displayEnd=false + } + return '' + + '
    ' + + (timeText ? + '
    ' + + '' + util_1.htmlEscape(timeText) + '' + + '
    ' : + '') + + (eventDef.title ? + '
    ' + + util_1.htmlEscape(eventDef.title) + + '
    ' : + '') + + '
    ' + + '
    ' + + /* TODO: write CSS for this + (isResizableFromStart ? + '
    ' : + '' + ) + + */ + (isResizableFromEnd ? + '
    ' : + '') + + ''; + }; + // Given segments that are assumed to all live in the *same column*, + // compute their verical/horizontal coordinates and assign to their elements. + TimeGridEventRenderer.prototype.updateFgSegCoords = function (segs) { + this.timeGrid.computeSegVerticals(segs); // horizontals relies on this + this.computeFgSegHorizontals(segs); // compute horizontal coordinates, z-index's, and reorder the array + this.timeGrid.assignSegVerticals(segs); + this.assignFgSegHorizontals(segs); + }; + // Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. + // NOTE: Also reorders the given array by date! + TimeGridEventRenderer.prototype.computeFgSegHorizontals = function (segs) { + var levels; + var level0; + var i; + this.sortEventSegs(segs); // order by certain criteria + levels = buildSlotSegLevels(segs); + computeForwardSlotSegs(levels); + if ((level0 = levels[0])) { + for (i = 0; i < level0.length; i++) { + computeSlotSegPressures(level0[i]); + } + for (i = 0; i < level0.length; i++) { + this.computeFgSegForwardBack(level0[i], 0, 0); + } + } + }; + // Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range + // from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and + // seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left. + // + // The segment might be part of a "series", which means consecutive segments with the same pressure + // who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of + // segments behind this one in the current series, and `seriesBackwardCoord` is the starting + // coordinate of the first segment in the series. + TimeGridEventRenderer.prototype.computeFgSegForwardBack = function (seg, seriesBackwardPressure, seriesBackwardCoord) { + var forwardSegs = seg.forwardSegs; + var i; + if (seg.forwardCoord === undefined) { + if (!forwardSegs.length) { + // if there are no forward segments, this segment should butt up against the edge + seg.forwardCoord = 1; + } + else { + // sort highest pressure first + this.sortForwardSegs(forwardSegs); + // this segment's forwardCoord will be calculated from the backwardCoord of the + // highest-pressure forward segment. + this.computeFgSegForwardBack(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord); + seg.forwardCoord = forwardSegs[0].backwardCoord; + } + // calculate the backwardCoord from the forwardCoord. consider the series + seg.backwardCoord = seg.forwardCoord - + (seg.forwardCoord - seriesBackwardCoord) / // available width for series + (seriesBackwardPressure + 1); // # of segments in the series + // use this segment's coordinates to computed the coordinates of the less-pressurized + // forward segments + for (i = 0; i < forwardSegs.length; i++) { + this.computeFgSegForwardBack(forwardSegs[i], 0, seg.forwardCoord); + } + } + }; + TimeGridEventRenderer.prototype.sortForwardSegs = function (forwardSegs) { + forwardSegs.sort(util_1.proxy(this, 'compareForwardSegs')); + }; + // A cmp function for determining which forward segment to rely on more when computing coordinates. + TimeGridEventRenderer.prototype.compareForwardSegs = function (seg1, seg2) { + // put higher-pressure first + return seg2.forwardPressure - seg1.forwardPressure || + // put segments that are closer to initial edge first (and favor ones with no coords yet) + (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || + // do normal sorting... + this.compareEventSegs(seg1, seg2); + }; + // Given foreground event segments that have already had their position coordinates computed, + // assigns position-related CSS values to their elements. + TimeGridEventRenderer.prototype.assignFgSegHorizontals = function (segs) { + var i; + var seg; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.el.css(this.generateFgSegHorizontalCss(seg)); + // if the height is short, add a className for alternate styling + if (seg.bottom - seg.top < 30) { + seg.el.addClass('fc-short'); + } + } + }; + // Generates an object with CSS properties/values that should be applied to an event segment element. + // Contains important positioning-related properties that should be applied to any event element, customized or not. + TimeGridEventRenderer.prototype.generateFgSegHorizontalCss = function (seg) { + var shouldOverlap = this.opt('slotEventOverlap'); + var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point + var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point + var props = this.timeGrid.generateSegVerticalCss(seg); // get top/bottom first + var isRTL = this.timeGrid.isRTL; + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); + } + if (isRTL) { + left = 1 - forwardCoord; + right = backwardCoord; + } + else { + left = backwardCoord; + right = 1 - forwardCoord; + } + props.zIndex = seg.level + 1; // convert from 0-base to 1-based + props.left = left * 100 + '%'; + props.right = right * 100 + '%'; + if (shouldOverlap && seg.forwardPressure) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + }; + return TimeGridEventRenderer; +}(EventRenderer_1.default)); +exports.default = TimeGridEventRenderer; +// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is +// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. +function buildSlotSegLevels(segs) { + var levels = []; + var i; + var seg; + var j; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + // go through all the levels and stop on the first level where there are no collisions + for (j = 0; j < levels.length; j++) { + if (!computeSlotSegCollisions(seg, levels[j]).length) { + break; + } + } + seg.level = j; + (levels[j] || (levels[j] = [])).push(seg); + } + return levels; +} +// For every segment, figure out the other segments that are in subsequent +// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs +function computeForwardSlotSegs(levels) { + var i; + var level; + var j; + var seg; + var k; + for (i = 0; i < levels.length; i++) { + level = levels[i]; + for (j = 0; j < level.length; j++) { + seg = level[j]; + seg.forwardSegs = []; + for (k = i + 1; k < levels.length; k++) { + computeSlotSegCollisions(seg, levels[k], seg.forwardSegs); + } + } + } +} +// Figure out which path forward (via seg.forwardSegs) results in the longest path until +// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure +function computeSlotSegPressures(seg) { + var forwardSegs = seg.forwardSegs; + var forwardPressure = 0; + var i; + var forwardSeg; + if (seg.forwardPressure === undefined) { + for (i = 0; i < forwardSegs.length; i++) { + forwardSeg = forwardSegs[i]; + // figure out the child's maximum forward path + computeSlotSegPressures(forwardSeg); + // either use the existing maximum, or use the child's forward pressure + // plus one (for the forwardSeg itself) + forwardPressure = Math.max(forwardPressure, 1 + forwardSeg.forwardPressure); + } + seg.forwardPressure = forwardPressure; + } +} +// Find all the segments in `otherSegs` that vertically collide with `seg`. +// Append into an optionally-supplied `results` array and return. +function computeSlotSegCollisions(seg, otherSegs, results) { + if (results === void 0) { results = []; } + for (var i = 0; i < otherSegs.length; i++) { + if (isSlotSegCollision(seg, otherSegs[i])) { + results.push(otherSegs[i]); + } + } + return results; +} +// Do these segments occupy the same vertical space? +function isSlotSegCollision(seg1, seg2) { + return seg1.bottom > seg2.top && seg1.top < seg2.bottom; +} + + +/***/ }), +/* 247 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var HelperRenderer_1 = __webpack_require__(58); +var TimeGridHelperRenderer = /** @class */ (function (_super) { + tslib_1.__extends(TimeGridHelperRenderer, _super); + function TimeGridHelperRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeGridHelperRenderer.prototype.renderSegs = function (segs, sourceSeg) { + var helperNodes = []; + var i; + var seg; + var sourceEl; + // TODO: not good to call eventRenderer this way + this.eventRenderer.renderFgSegsIntoContainers(segs, this.component.helperContainerEls); + // Try to make the segment that is in the same row as sourceSeg look the same + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + if (sourceSeg && sourceSeg.col === seg.col) { + sourceEl = sourceSeg.el; + seg.el.css({ + left: sourceEl.css('left'), + right: sourceEl.css('right'), + 'margin-left': sourceEl.css('margin-left'), + 'margin-right': sourceEl.css('margin-right') + }); + } + helperNodes.push(seg.el[0]); + } + return $(helperNodes); // must return the elements rendered + }; + return TimeGridHelperRenderer; +}(HelperRenderer_1.default)); +exports.default = TimeGridHelperRenderer; + + +/***/ }), +/* 248 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var FillRenderer_1 = __webpack_require__(57); +var TimeGridFillRenderer = /** @class */ (function (_super) { + tslib_1.__extends(TimeGridFillRenderer, _super); + function TimeGridFillRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeGridFillRenderer.prototype.attachSegEls = function (type, segs) { + var timeGrid = this.component; + var containerEls; + // TODO: more efficient lookup + if (type === 'bgEvent') { + containerEls = timeGrid.bgContainerEls; + } + else if (type === 'businessHours') { + containerEls = timeGrid.businessContainerEls; + } + else if (type === 'highlight') { + containerEls = timeGrid.highlightContainerEls; + } + timeGrid.updateSegVerticals(segs); + timeGrid.attachSegsByCol(timeGrid.groupSegsByCol(segs), containerEls); + return segs.map(function (seg) { + return seg.el[0]; + }); + }; + return TimeGridFillRenderer; +}(FillRenderer_1.default)); +exports.default = TimeGridFillRenderer; + + +/***/ }), +/* 249 */ +/***/ (function(module, exports, __webpack_require__) { + +/* A rectangular panel that is absolutely positioned over other content +------------------------------------------------------------------------------------------------------------------------ +Options: + - className (string) + - content (HTML string or jQuery element set) + - parentEl + - top + - left + - right (the x coord of where the right edge should be. not a "CSS" right) + - autoHide (boolean) + - show (callback) + - hide (callback) +*/ +Object.defineProperty(exports, "__esModule", { value: true }); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var ListenerMixin_1 = __webpack_require__(7); +var Popover = /** @class */ (function () { + function Popover(options) { + this.isHidden = true; + this.margin = 10; // the space required between the popover and the edges of the scroll container + this.options = options || {}; + } + // Shows the popover on the specified position. Renders it if not already + Popover.prototype.show = function () { + if (this.isHidden) { + if (!this.el) { + this.render(); + } + this.el.show(); + this.position(); + this.isHidden = false; + this.trigger('show'); + } + }; + // Hides the popover, through CSS, but does not remove it from the DOM + Popover.prototype.hide = function () { + if (!this.isHidden) { + this.el.hide(); + this.isHidden = true; + this.trigger('hide'); + } + }; + // Creates `this.el` and renders content inside of it + Popover.prototype.render = function () { + var _this = this; + var options = this.options; + this.el = $('
    ') + .addClass(options.className || '') + .css({ + // position initially to the top left to avoid creating scrollbars + top: 0, + left: 0 + }) + .append(options.content) + .appendTo(options.parentEl); + // when a click happens on anything inside with a 'fc-close' className, hide the popover + this.el.on('click', '.fc-close', function () { + _this.hide(); + }); + if (options.autoHide) { + this.listenTo($(document), 'mousedown', this.documentMousedown); + } + }; + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + Popover.prototype.documentMousedown = function (ev) { + // only hide the popover if the click happened outside the popover + if (this.el && !$(ev.target).closest(this.el).length) { + this.hide(); + } + }; + // Hides and unregisters any handlers + Popover.prototype.removeElement = function () { + this.hide(); + if (this.el) { + this.el.remove(); + this.el = null; + } + this.stopListeningTo($(document), 'mousedown'); + }; + // Positions the popover optimally, using the top/left/right options + Popover.prototype.position = function () { + var options = this.options; + var origin = this.el.offsetParent().offset(); + var width = this.el.outerWidth(); + var height = this.el.outerHeight(); + var windowEl = $(window); + var viewportEl = util_1.getScrollParent(this.el); + var viewportTop; + var viewportLeft; + var viewportOffset; + var top; // the "position" (not "offset") values for the popover + var left; // + // compute top and left + top = options.top || 0; + if (options.left !== undefined) { + left = options.left; + } + else if (options.right !== undefined) { + left = options.right - width; // derive the left value from the right value + } + else { + left = 0; + } + if (viewportEl.is(window) || viewportEl.is(document)) { + viewportEl = windowEl; + viewportTop = 0; // the window is always at the top left + viewportLeft = 0; // (and .offset() won't work if called here) + } + else { + viewportOffset = viewportEl.offset(); + viewportTop = viewportOffset.top; + viewportLeft = viewportOffset.left; + } + // if the window is scrolled, it causes the visible area to be further down + viewportTop += windowEl.scrollTop(); + viewportLeft += windowEl.scrollLeft(); + // constrain to the view port. if constrained by two edges, give precedence to top/left + if (options.viewportConstrain !== false) { + top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); + top = Math.max(top, viewportTop + this.margin); + left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); + left = Math.max(left, viewportLeft + this.margin); + } + this.el.css({ + top: top - origin.top, + left: left - origin.left + }); + }; + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + // TODO: better code reuse for this. Repeat code + Popover.prototype.trigger = function (name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + }; + return Popover; +}()); +exports.default = Popover; +ListenerMixin_1.default.mixInto(Popover); + + +/***/ }), +/* 250 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var util_1 = __webpack_require__(4); +var EventRenderer_1 = __webpack_require__(42); +/* Event-rendering methods for the DayGrid class +----------------------------------------------------------------------------------------------------------------------*/ +var DayGridEventRenderer = /** @class */ (function (_super) { + tslib_1.__extends(DayGridEventRenderer, _super); + function DayGridEventRenderer(dayGrid, fillRenderer) { + var _this = _super.call(this, dayGrid, fillRenderer) || this; + _this.dayGrid = dayGrid; + return _this; + } + DayGridEventRenderer.prototype.renderBgRanges = function (eventRanges) { + // don't render timed background events + eventRanges = $.grep(eventRanges, function (eventRange) { + return eventRange.eventDef.isAllDay(); + }); + _super.prototype.renderBgRanges.call(this, eventRanges); + }; + // Renders the given foreground event segments onto the grid + DayGridEventRenderer.prototype.renderFgSegs = function (segs) { + var rowStructs = this.rowStructs = this.renderSegRows(segs); + // append to each row's content skeleton + this.dayGrid.rowEls.each(function (i, rowNode) { + $(rowNode).find('.fc-content-skeleton > table').append(rowStructs[i].tbodyEl); + }); + }; + // Unrenders all currently rendered foreground event segments + DayGridEventRenderer.prototype.unrenderFgSegs = function () { + var rowStructs = this.rowStructs || []; + var rowStruct; + while ((rowStruct = rowStructs.pop())) { + rowStruct.tbodyEl.remove(); + } + this.rowStructs = null; + }; + // Uses the given events array to generate elements that should be appended to each row's content skeleton. + // Returns an array of rowStruct objects (see the bottom of `renderSegRow`). + // PRECONDITION: each segment shoud already have a rendered and assigned `.el` + DayGridEventRenderer.prototype.renderSegRows = function (segs) { + var rowStructs = []; + var segRows; + var row; + segRows = this.groupSegRows(segs); // group into nested arrays + // iterate each row of segment groupings + for (row = 0; row < segRows.length; row++) { + rowStructs.push(this.renderSegRow(row, segRows[row])); + } + return rowStructs; + }; + // Given a row # and an array of segments all in the same row, render a element, a skeleton that contains + // the segments. Returns object with a bunch of internal data about how the render was calculated. + // NOTE: modifies rowSegs + DayGridEventRenderer.prototype.renderSegRow = function (row, rowSegs) { + var colCnt = this.dayGrid.colCnt; + var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels + var levelCnt = Math.max(1, segLevels.length); // ensure at least one level + var tbody = $(''); + var segMatrix = []; // lookup for which segments are rendered into which level+col cells + var cellMatrix = []; // lookup for all elements of the level+col matrix + var loneCellMatrix = []; // lookup for elements that only take up a single column + var i; + var levelSegs; + var col; + var tr; + var j; + var seg; + var td; + // populates empty cells from the current column (`col`) to `endCol` + function emptyCellsUntil(endCol) { + while (col < endCol) { + // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell + td = (loneCellMatrix[i - 1] || [])[col]; + if (td) { + td.attr('rowspan', parseInt(td.attr('rowspan') || 1, 10) + 1); + } + else { + td = $(''); + tr.append(td); + } + cellMatrix[i][col] = td; + loneCellMatrix[i][col] = td; + col++; + } + } + for (i = 0; i < levelCnt; i++) { + levelSegs = segLevels[i]; + col = 0; + tr = $(''); + segMatrix.push([]); + cellMatrix.push([]); + loneCellMatrix.push([]); + // levelCnt might be 1 even though there are no actual levels. protect against this. + // this single empty row is useful for styling. + if (levelSegs) { + for (j = 0; j < levelSegs.length; j++) { + seg = levelSegs[j]; + emptyCellsUntil(seg.leftCol); + // create a container that occupies or more columns. append the event element. + td = $('').append(seg.el); + if (seg.leftCol !== seg.rightCol) { + td.attr('colspan', seg.rightCol - seg.leftCol + 1); + } + else { + loneCellMatrix[i][col] = td; + } + while (col <= seg.rightCol) { + cellMatrix[i][col] = td; + segMatrix[i][col] = seg; + col++; + } + tr.append(td); + } + } + emptyCellsUntil(colCnt); // finish off the row + this.dayGrid.bookendCells(tr); + tbody.append(tr); + } + return { + row: row, + tbodyEl: tbody, + cellMatrix: cellMatrix, + segMatrix: segMatrix, + segLevels: segLevels, + segs: rowSegs + }; + }; + // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. + // NOTE: modifies segs + DayGridEventRenderer.prototype.buildSegLevels = function (segs) { + var levels = []; + var i; + var seg; + var j; + // Give preference to elements with certain criteria, so they have + // a chance to be closer to the top. + this.sortEventSegs(segs); + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + // loop through levels, starting with the topmost, until the segment doesn't collide with other segments + for (j = 0; j < levels.length; j++) { + if (!isDaySegCollision(seg, levels[j])) { + break; + } + } + // `j` now holds the desired subrow index + seg.level = j; + // create new level array if needed and append segment + (levels[j] || (levels[j] = [])).push(seg); + } + // order segments left-to-right. very important if calendar is RTL + for (j = 0; j < levels.length; j++) { + levels[j].sort(compareDaySegCols); + } + return levels; + }; + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row + DayGridEventRenderer.prototype.groupSegRows = function (segs) { + var segRows = []; + var i; + for (i = 0; i < this.dayGrid.rowCnt; i++) { + segRows.push([]); + } + for (i = 0; i < segs.length; i++) { + segRows[segs[i].row].push(segs[i]); + } + return segRows; + }; + // Computes a default event time formatting string if `timeFormat` is not explicitly defined + DayGridEventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('extraSmallTimeFormat'); // like "6p" or "6:30p" + }; + // Computes a default `displayEventEnd` value if one is not expliclty defined + DayGridEventRenderer.prototype.computeDisplayEventEnd = function () { + return this.dayGrid.colCnt === 1; // we'll likely have space if there's only one day + }; + // Builds the HTML to be used for the default element for an individual segment + DayGridEventRenderer.prototype.fgSegHtml = function (seg, disableResizing) { + var view = this.view; + var eventDef = seg.footprint.eventDef; + var isAllDay = seg.footprint.componentFootprint.isAllDay; + var isDraggable = view.isEventDefDraggable(eventDef); + var isResizableFromStart = !disableResizing && isAllDay && + seg.isStart && view.isEventDefResizableFromStart(eventDef); + var isResizableFromEnd = !disableResizing && isAllDay && + seg.isEnd && view.isEventDefResizableFromEnd(eventDef); + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd); + var skinCss = util_1.cssToStr(this.getSkinCss(eventDef)); + var timeHtml = ''; + var timeText; + var titleHtml; + classes.unshift('fc-day-grid-event', 'fc-h-event'); + // Only display a timed events time if it is the starting segment + if (seg.isStart) { + timeText = this.getTimeText(seg.footprint); + if (timeText) { + timeHtml = '' + util_1.htmlEscape(timeText) + ''; + } + } + titleHtml = + '' + + (util_1.htmlEscape(eventDef.title || '') || ' ') + // we always want one line of height + ''; + return '' + + '
    ' + + (this.dayGrid.isRTL ? + titleHtml + ' ' + timeHtml : // put a natural space in between + timeHtml + ' ' + titleHtml // + ) + + '
    ' + + (isResizableFromStart ? + '
    ' : + '') + + (isResizableFromEnd ? + '
    ' : + '') + + ''; + }; + return DayGridEventRenderer; +}(EventRenderer_1.default)); +exports.default = DayGridEventRenderer; +// Computes whether two segments' columns collide. They are assumed to be in the same row. +function isDaySegCollision(seg, otherSegs) { + var i; + var otherSeg; + for (i = 0; i < otherSegs.length; i++) { + otherSeg = otherSegs[i]; + if (otherSeg.leftCol <= seg.rightCol && + otherSeg.rightCol >= seg.leftCol) { + return true; + } + } + return false; +} +// A cmp function for determining the leftmost event +function compareDaySegCols(a, b) { + return a.leftCol - b.leftCol; +} + + +/***/ }), +/* 251 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var HelperRenderer_1 = __webpack_require__(58); +var DayGridHelperRenderer = /** @class */ (function (_super) { + tslib_1.__extends(DayGridHelperRenderer, _super); + function DayGridHelperRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. + DayGridHelperRenderer.prototype.renderSegs = function (segs, sourceSeg) { + var helperNodes = []; + var rowStructs; + // TODO: not good to call eventRenderer this way + rowStructs = this.eventRenderer.renderSegRows(segs); + // inject each new event skeleton into each associated row + this.component.rowEls.each(function (row, rowNode) { + var rowEl = $(rowNode); // the .fc-row + var skeletonEl = $('
    '); // will be absolutely positioned + var skeletonTopEl; + var skeletonTop; + // If there is an original segment, match the top position. Otherwise, put it at the row's top level + if (sourceSeg && sourceSeg.row === row) { + skeletonTop = sourceSeg.el.position().top; + } + else { + skeletonTopEl = rowEl.find('.fc-content-skeleton tbody'); + if (!skeletonTopEl.length) { + skeletonTopEl = rowEl.find('.fc-content-skeleton table'); + } + skeletonTop = skeletonTopEl.position().top; + } + skeletonEl.css('top', skeletonTop) + .find('table') + .append(rowStructs[row].tbodyEl); + rowEl.append(skeletonEl); + helperNodes.push(skeletonEl[0]); + }); + return $(helperNodes); // must return the elements rendered + }; + return DayGridHelperRenderer; +}(HelperRenderer_1.default)); +exports.default = DayGridHelperRenderer; + + +/***/ }), +/* 252 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var FillRenderer_1 = __webpack_require__(57); +var DayGridFillRenderer = /** @class */ (function (_super) { + tslib_1.__extends(DayGridFillRenderer, _super); + function DayGridFillRenderer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.fillSegTag = 'td'; // override the default tag name + return _this; + } + DayGridFillRenderer.prototype.attachSegEls = function (type, segs) { + var nodes = []; + var i; + var seg; + var skeletonEl; + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + skeletonEl = this.renderFillRow(type, seg); + this.component.rowEls.eq(seg.row).append(skeletonEl); + nodes.push(skeletonEl[0]); + } + return nodes; + }; + // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered. + DayGridFillRenderer.prototype.renderFillRow = function (type, seg) { + var colCnt = this.component.colCnt; + var startCol = seg.leftCol; + var endCol = seg.rightCol + 1; + var className; + var skeletonEl; + var trEl; + if (type === 'businessHours') { + className = 'bgevent'; + } + else { + className = type.toLowerCase(); + } + skeletonEl = $('
    ' + + '
    ' + + '
    '); + trEl = skeletonEl.find('tr'); + if (startCol > 0) { + trEl.append(''); + } + trEl.append(seg.el.attr('colspan', endCol - startCol)); + if (endCol < colCnt) { + trEl.append(''); + } + this.component.bookendCells(trEl); + return skeletonEl; + }; + return DayGridFillRenderer; +}(FillRenderer_1.default)); +exports.default = DayGridFillRenderer; + + +/***/ }), +/* 253 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var BasicViewDateProfileGenerator_1 = __webpack_require__(228); +var UnzonedRange_1 = __webpack_require__(5); +var MonthViewDateProfileGenerator = /** @class */ (function (_super) { + tslib_1.__extends(MonthViewDateProfileGenerator, _super); + function MonthViewDateProfileGenerator() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Computes the date range that will be rendered. + MonthViewDateProfileGenerator.prototype.buildRenderRange = function (currentUnzonedRange, currentRangeUnit, isRangeAllDay) { + var renderUnzonedRange = _super.prototype.buildRenderRange.call(this, currentUnzonedRange, currentRangeUnit, isRangeAllDay); + var start = this.msToUtcMoment(renderUnzonedRange.startMs, isRangeAllDay); + var end = this.msToUtcMoment(renderUnzonedRange.endMs, isRangeAllDay); + var rowCnt; + // ensure 6 weeks + if (this.opt('fixedWeekCount')) { + rowCnt = Math.ceil(// could be partial weeks due to hiddenDays + end.diff(start, 'weeks', true) // dontRound=true + ); + end.add(6 - rowCnt, 'weeks'); + } + return new UnzonedRange_1.default(start, end); + }; + return MonthViewDateProfileGenerator; +}(BasicViewDateProfileGenerator_1.default)); +exports.default = MonthViewDateProfileGenerator; + + +/***/ }), +/* 254 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var util_1 = __webpack_require__(4); +var EventRenderer_1 = __webpack_require__(42); +var ListEventRenderer = /** @class */ (function (_super) { + tslib_1.__extends(ListEventRenderer, _super); + function ListEventRenderer() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListEventRenderer.prototype.renderFgSegs = function (segs) { + if (!segs.length) { + this.component.renderEmptyMessage(); + } + else { + this.component.renderSegList(segs); + } + }; + // generates the HTML for a single event row + ListEventRenderer.prototype.fgSegHtml = function (seg) { + var view = this.view; + var calendar = view.calendar; + var theme = calendar.theme; + var eventFootprint = seg.footprint; + var eventDef = eventFootprint.eventDef; + var componentFootprint = eventFootprint.componentFootprint; + var url = eventDef.url; + var classes = ['fc-list-item'].concat(this.getClasses(eventDef)); + var bgColor = this.getBgColor(eventDef); + var timeHtml; + if (componentFootprint.isAllDay) { + timeHtml = view.getAllDayHtml(); + } + else if (view.isMultiDayRange(componentFootprint.unzonedRange)) { + if (seg.isStart || seg.isEnd) { + timeHtml = util_1.htmlEscape(this._getTimeText(calendar.msToMoment(seg.startMs), calendar.msToMoment(seg.endMs), componentFootprint.isAllDay)); + } + else { + timeHtml = view.getAllDayHtml(); + } + } + else { + // Display the normal time text for the *event's* times + timeHtml = util_1.htmlEscape(this.getTimeText(eventFootprint)); + } + if (url) { + classes.push('fc-has-url'); + } + return '' + + (this.displayEventTime ? + '' + + (timeHtml || '') + + '' : + '') + + '' + + '' + + '' + + '' + + '' + + util_1.htmlEscape(eventDef.title || '') + + '' + + '' + + ''; + }; + // like "4:00am" + ListEventRenderer.prototype.computeEventTimeFormat = function () { + return this.opt('mediumTimeFormat'); + }; + return ListEventRenderer; +}(EventRenderer_1.default)); +exports.default = ListEventRenderer; + + +/***/ }), +/* 255 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var $ = __webpack_require__(3); +var EventPointing_1 = __webpack_require__(59); +var ListEventPointing = /** @class */ (function (_super) { + tslib_1.__extends(ListEventPointing, _super); + function ListEventPointing() { + return _super !== null && _super.apply(this, arguments) || this; + } + // for events with a url, the whole should be clickable, + // but it's impossible to wrap with an tag. simulate this. + ListEventPointing.prototype.handleClick = function (seg, ev) { + var url; + _super.prototype.handleClick.call(this, seg, ev); // might prevent the default action + // not clicking on or within an with an href + if (!$(ev.target).closest('a[href]').length) { + url = seg.footprint.eventDef.url; + if (url && !ev.isDefaultPrevented()) { + window.location.href = url; // simulate link click + } + } + }; + return ListEventPointing; +}(EventPointing_1.default)); +exports.default = ListEventPointing; + + +/***/ }), +/* 256 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var EventSourceParser_1 = __webpack_require__(38); +var ArrayEventSource_1 = __webpack_require__(52); +var FuncEventSource_1 = __webpack_require__(215); +var JsonFeedEventSource_1 = __webpack_require__(216); +EventSourceParser_1.default.registerClass(ArrayEventSource_1.default); +EventSourceParser_1.default.registerClass(FuncEventSource_1.default); +EventSourceParser_1.default.registerClass(JsonFeedEventSource_1.default); + + +/***/ }), +/* 257 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ThemeRegistry_1 = __webpack_require__(51); +var StandardTheme_1 = __webpack_require__(213); +var JqueryUiTheme_1 = __webpack_require__(214); +var Bootstrap3Theme_1 = __webpack_require__(258); +var Bootstrap4Theme_1 = __webpack_require__(259); +ThemeRegistry_1.defineThemeSystem('standard', StandardTheme_1.default); +ThemeRegistry_1.defineThemeSystem('jquery-ui', JqueryUiTheme_1.default); +ThemeRegistry_1.defineThemeSystem('bootstrap3', Bootstrap3Theme_1.default); +ThemeRegistry_1.defineThemeSystem('bootstrap4', Bootstrap4Theme_1.default); + + +/***/ }), +/* 258 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Theme_1 = __webpack_require__(19); +var Bootstrap3Theme = /** @class */ (function (_super) { + tslib_1.__extends(Bootstrap3Theme, _super); + function Bootstrap3Theme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Bootstrap3Theme; +}(Theme_1.default)); +exports.default = Bootstrap3Theme; +Bootstrap3Theme.prototype.classes = { + widget: 'fc-bootstrap3', + tableGrid: 'table-bordered', + tableList: 'table', + tableListHeading: 'active', + buttonGroup: 'btn-group', + button: 'btn btn-default', + stateActive: 'active', + stateDisabled: 'disabled', + today: 'alert alert-info', + popover: 'panel panel-default', + popoverHeader: 'panel-heading', + popoverContent: 'panel-body', + // day grid + // for left/right border color when border is inset from edges (all-day in agenda view) + // avoid `panel` class b/c don't want margins/radius. only border color. + headerRow: 'panel-default', + dayRow: 'panel-default', + // list view + listView: 'panel panel-default' +}; +Bootstrap3Theme.prototype.baseIconClass = 'glyphicon'; +Bootstrap3Theme.prototype.iconClasses = { + close: 'glyphicon-remove', + prev: 'glyphicon-chevron-left', + next: 'glyphicon-chevron-right', + prevYear: 'glyphicon-backward', + nextYear: 'glyphicon-forward' +}; +Bootstrap3Theme.prototype.iconOverrideOption = 'bootstrapGlyphicons'; +Bootstrap3Theme.prototype.iconOverrideCustomButtonOption = 'bootstrapGlyphicon'; +Bootstrap3Theme.prototype.iconOverridePrefix = 'glyphicon-'; + + +/***/ }), +/* 259 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = __webpack_require__(2); +var Theme_1 = __webpack_require__(19); +var Bootstrap4Theme = /** @class */ (function (_super) { + tslib_1.__extends(Bootstrap4Theme, _super); + function Bootstrap4Theme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Bootstrap4Theme; +}(Theme_1.default)); +exports.default = Bootstrap4Theme; +Bootstrap4Theme.prototype.classes = { + widget: 'fc-bootstrap4', + tableGrid: 'table-bordered', + tableList: 'table', + tableListHeading: 'table-active', + buttonGroup: 'btn-group', + button: 'btn btn-primary', + stateActive: 'active', + stateDisabled: 'disabled', + today: 'alert alert-info', + popover: 'card card-primary', + popoverHeader: 'card-header', + popoverContent: 'card-body', + // day grid + // for left/right border color when border is inset from edges (all-day in agenda view) + // avoid `table` class b/c don't want margins/padding/structure. only border color. + headerRow: 'table-bordered', + dayRow: 'table-bordered', + // list view + listView: 'card card-primary' +}; +Bootstrap4Theme.prototype.baseIconClass = 'fa'; +Bootstrap4Theme.prototype.iconClasses = { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right' +}; +Bootstrap4Theme.prototype.iconOverrideOption = 'bootstrapFontAwesome'; +Bootstrap4Theme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'; +Bootstrap4Theme.prototype.iconOverridePrefix = 'fa-'; + + +/***/ }), +/* 260 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ViewRegistry_1 = __webpack_require__(22); +var BasicView_1 = __webpack_require__(62); +var MonthView_1 = __webpack_require__(229); +ViewRegistry_1.defineView('basic', { + 'class': BasicView_1.default +}); +ViewRegistry_1.defineView('basicDay', { + type: 'basic', + duration: { days: 1 } +}); +ViewRegistry_1.defineView('basicWeek', { + type: 'basic', + duration: { weeks: 1 } +}); +ViewRegistry_1.defineView('month', { + 'class': MonthView_1.default, + duration: { months: 1 }, + defaults: { + fixedWeekCount: true + } +}); + + +/***/ }), +/* 261 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ViewRegistry_1 = __webpack_require__(22); +var AgendaView_1 = __webpack_require__(226); +ViewRegistry_1.defineView('agenda', { + 'class': AgendaView_1.default, + defaults: { + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true // a bad name. confused with overlap/constraint system + } +}); +ViewRegistry_1.defineView('agendaDay', { + type: 'agenda', + duration: { days: 1 } +}); +ViewRegistry_1.defineView('agendaWeek', { + type: 'agenda', + duration: { weeks: 1 } +}); + + +/***/ }), +/* 262 */ +/***/ (function(module, exports, __webpack_require__) { + +Object.defineProperty(exports, "__esModule", { value: true }); +var ViewRegistry_1 = __webpack_require__(22); +var ListView_1 = __webpack_require__(230); +ViewRegistry_1.defineView('list', { + 'class': ListView_1.default, + buttonTextKey: 'list', + defaults: { + buttonText: 'list', + listDayFormat: 'LL', + noEventsMessage: 'No events to display' + } +}); +ViewRegistry_1.defineView('listDay', { + type: 'list', + duration: { days: 1 }, + defaults: { + listDayFormat: 'dddd' // day-of-week is all we need. full date is probably in header + } +}); +ViewRegistry_1.defineView('listWeek', { + type: 'list', + duration: { weeks: 1 }, + defaults: { + listDayFormat: 'dddd', + listDayAltFormat: 'LL' + } +}); +ViewRegistry_1.defineView('listMonth', { + type: 'list', + duration: { month: 1 }, + defaults: { + listDayAltFormat: 'dddd' // day-of-week is nice-to-have + } +}); +ViewRegistry_1.defineView('listYear', { + type: 'list', + duration: { year: 1 }, + defaults: { + listDayAltFormat: 'dddd' // day-of-week is nice-to-have + } +}); + + +/***/ }), +/* 263 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); + + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/public/lib/fc/fullcalendar.min.css b/public/lib/fc/fullcalendar.min.css new file mode 100644 index 0000000000..cf86d29318 --- /dev/null +++ b/public/lib/fc/fullcalendar.min.css @@ -0,0 +1,5 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */.fc button,.fc table,body .fc{font-size:1em}.fc-bg,.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-icon,.fc-unselectable{-webkit-touch-callout:none;-khtml-user-select:none}.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}.fc th,.fc-basic-view td.fc-week-number,.fc-icon,.fc-toolbar{text-align:center}.fc-highlight{background:#bce8f1;opacity:.3}.fc-bgevent{background:#8fdf82;opacity:.3}.fc-nonbusiness{background:#d7d7d7}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;box-shadow:none}.fc-event.fc-draggable,.fc-event[href],.fc-popover .fc-header .fc-close,a[data-goto]{cursor:pointer}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-bg table,.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc table{width:100%;box-sizing:border-box;table-layout:fixed;border-collapse:collapse;border-spacing:0}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}a[data-goto]:hover{text-decoration:underline}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc .fc-row .fc-content-skeleton table,.fc .fc-row .fc-content-skeleton td,.fc .fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{-webkit-overflow-scrolling:touch}.fc-icon,.fc-row.fc-rigid,.fc-time-grid-event{overflow:hidden}.fc-scroller>.fc-day-grid,.fc-scroller>.fc-time-grid{position:relative;width:100%}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad}.fc-event,.fc-event-dot{background-color:#3a87ad}.fc-event,.fc-event:hover{color:#fff;text-decoration:none}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:4;display:none}.fc-event.fc-allow-mouse-resize .fc-resizer,.fc-event.fc-selected .fc-resizer{display:block}.fc-event.fc-selected .fc-resizer:before{content:"";position:absolute;z-index:9999;top:50%;left:50%;width:40px;height:40px;margin-left:-20px;margin-top:-20px}.fc-event.fc-selected{z-index:9999!important;box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event.fc-selected.fc-dragging{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-h-event.fc-selected:before{content:"";position:absolute;z-index:3;top:-10px;bottom:-10px;left:0;right:0}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-ltr .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-end-resizer{cursor:w-resize;left:-1px}.fc-ltr .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-start-resizer{cursor:e-resize;right:-1px}.fc-h-event.fc-allow-mouse-resize .fc-resizer{width:7px;top:-1px;bottom:-1px}.fc-h-event.fc-selected .fc-resizer{border-radius:4px;border-width:1px;width:6px;height:6px;border-style:solid;border-color:inherit;background:#fff;top:50%;margin-top:-4px}.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,.fc-rtl .fc-h-event.fc-selected .fc-end-resizer{margin-left:-4px}.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,.fc-rtl .fc-h-event.fc-selected .fc-start-resizer{margin-right:-4px}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}tr:first-child>td>.fc-day-grid-event{margin-top:2px}.fc-day-grid-event.fc-selected:after{content:"";position:absolute;z-index:1;top:-1px;right:-1px;bottom:-1px;left:-1px;background:#000;opacity:.25}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer{margin-left:-2px}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer{margin-right:-2px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc.fc-bootstrap3 a,.ui-widget .fc-event{text-decoration:none}.fc-limited{display:none}.fc-icon,.fc-toolbar .fc-center{display:inline-block}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-bootstrap3 .fc-popover .panel-body,.fc-bootstrap4 .fc-popover .card-body{padding:0}.fc-now-indicator{position:absolute;border:0 solid red}.fc-bootstrap3 .fc-today.alert,.fc-bootstrap4 .fc-today.alert{border-radius:0}.fc-unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff;border-width:1px;border-style:solid}.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed td.fc-today{background:#fcf8e3}.fc-unthemed .fc-disabled-day{background:#d7d7d7;opacity:.3}.fc-icon{height:1em;line-height:1em;font-size:1em;font-family:"Courier New",Courier,monospace;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative}.fc-icon-left-single-arrow:after{content:"\2039";font-weight:700;font-size:200%;top:-7%}.fc-icon-right-single-arrow:after{content:"\203A";font-weight:700;font-size:200%;top:-7%}.fc-icon-left-double-arrow:after{content:"\AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\D7";font-size:200%;top:6%}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666;font-size:.9em;margin-top:2px}.fc-unthemed .fc-list-item:hover td{background-color:#f5f5f5}.ui-widget .fc-disabled-day{background-image:none}.fc-bootstrap3 .fc-time-grid .fc-slats table,.fc-bootstrap4 .fc-time-grid .fc-slats table,.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-bootstrap3 hr.fc-divider,.fc-bootstrap4 hr.fc-divider{border-color:inherit}.ui-widget .fc-event{color:#fff;font-weight:400}.ui-widget td.fc-axis{font-weight:400}.fc.fc-bootstrap3 a[data-goto]:hover{text-decoration:underline}.fc.fc-bootstrap4 a{text-decoration:none}.fc.fc-bootstrap4 a[data-goto]:hover{text-decoration:underline}.fc-bootstrap4 a.fc-event:not([href]):not([tabindex]){color:#fff}.fc-bootstrap4 .fc-popover.card{position:absolute}.fc-toolbar.fc-header-toolbar{margin-bottom:1em}.fc-toolbar.fc-footer-toolbar{margin-top:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-day-top.fc-other-month{opacity:.3}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:2px}.fc-basic-view th.fc-day-number,.fc-basic-view th.fc-week-number{padding:0 2px}.fc-ltr .fc-basic-view .fc-day-top .fc-day-number{float:right}.fc-rtl .fc-basic-view .fc-day-top .fc-day-number{float:left}.fc-ltr .fc-basic-view .fc-day-top .fc-week-number{float:left;border-radius:0 0 3px}.fc-rtl .fc-basic-view .fc-day-top .fc-week-number{float:right;border-radius:0 0 0 3px}.fc-basic-view .fc-day-top .fc-week-number{min-width:1.5em;text-align:center;background-color:#f2f2f2;color:grey}.fc-basic-view td.fc-week-number>*{display:inline-block;min-width:1.25em}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-content-col{position:relative}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:3;top:0;left:0;right:0}.fc-time-grid .fc-business-container{position:relative;z-index:1}.fc-time-grid .fc-bgevent-container{position:relative;z-index:2}.fc-time-grid .fc-highlight-container{z-index:3;position:relative}.fc-time-grid .fc-event-container{position:relative;z-index:4}.fc-time-grid .fc-now-indicator-line{z-index:5}.fc-time-grid .fc-helper-container{position:relative;z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event.fc-selected{overflow:visible}.fc-time-grid-event.fc-selected .fc-bg{display:none}.fc-time-grid-event .fc-content{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\A0-\A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after{content:"="}.fc-time-grid-event.fc-selected .fc-resizer{border-radius:5px;border-width:1px;width:8px;height:8px;border-style:solid;border-color:inherit;background:#fff;left:50%;margin-left:-5px;bottom:-5px}.fc-time-grid .fc-now-indicator-line{border-top-width:1px;left:0;right:0}.fc-time-grid .fc-now-indicator-arrow{margin-top:-5px}.fc-ltr .fc-time-grid .fc-now-indicator-arrow{left:0;border-width:5px 0 5px 6px;border-top-color:transparent;border-bottom-color:transparent}.fc-rtl .fc-time-grid .fc-now-indicator-arrow{right:0;border-width:5px 6px 5px 0;border-top-color:transparent;border-bottom-color:transparent}.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee} \ No newline at end of file diff --git a/public/lib/fc/fullcalendar.min.js b/public/lib/fc/fullcalendar.min.js new file mode 100644 index 0000000000..88045457d9 --- /dev/null +++ b/public/lib/fc/fullcalendar.min.js @@ -0,0 +1,12 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("moment"),require("jquery")):"function"==typeof define&&define.amd?define(["moment","jquery"],e):"object"==typeof exports?exports.FullCalendar=e(require("moment"),require("jquery")):t.FullCalendar=e(t.moment,t.jQuery)}("undefined"!=typeof self?self:this,function(t,e){return function(t){function e(i){if(n[i])return n[i].exports;var r=n[i]={i:i,l:!1,exports:{}};return t[i].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.d=function(t,n,i){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=236)}([function(e,n){e.exports=t},,function(t,e){var n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])};e.__extends=function(t,e){function i(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)}},function(t,n){t.exports=e},function(t,e,n){function i(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function r(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function o(){ht("body").addClass("fc-not-allowed")}function s(){ht("body").removeClass("fc-not-allowed")}function a(t,e,n){var i=Math.floor(e/t.length),r=Math.floor(e-i*(t.length-1)),o=[],s=[],a=[],u=0;l(t),t.each(function(e,n){var l=e===t.length-1?r:i,d=ht(n).outerHeight(!0);d *").each(function(t,n){var i=ht(n).outerWidth();i>e&&(e=i)}),e++,t.width(e),e}function d(t,e){var n,i=t.add(e);return i.css({position:"relative",left:-1}),n=t.outerHeight()-e.outerHeight(),i.css({position:"",left:""}),n}function c(t){var e=t.css("position"),n=t.parents().filter(function(){var t=ht(this);return/(auto|scroll)/.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==e&&n.length?n:ht(t[0].ownerDocument||document)}function p(t,e){var n=t.offset(),i=n.left-(e?e.left:0),r=n.top-(e?e.top:0);return{left:i,right:i+t.outerWidth(),top:r,bottom:r+t.outerHeight()}}function h(t,e){var n=t.offset(),i=g(t),r=n.left+b(t,"border-left-width")+i.left-(e?e.left:0),o=n.top+b(t,"border-top-width")+i.top-(e?e.top:0);return{left:r,right:r+t[0].clientWidth,top:o,bottom:o+t[0].clientHeight}}function f(t,e){var n=t.offset(),i=n.left+b(t,"border-left-width")+b(t,"padding-left")-(e?e.left:0),r=n.top+b(t,"border-top-width")+b(t,"padding-top")-(e?e.top:0);return{left:i,right:i+t.width(),top:r,bottom:r+t.height()}}function g(t){var e,n=t[0].offsetWidth-t[0].clientWidth,i=t[0].offsetHeight-t[0].clientHeight;return n=v(n),i=v(i),e={left:0,right:0,top:0,bottom:i},y()&&"rtl"===t.css("direction")?e.left=n:e.right=n,e}function v(t){return t=Math.max(0,t),t=Math.round(t)}function y(){return null===ft&&(ft=m()),ft}function m(){var t=ht("
    ").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),e=t.children(),n=e.offset().left>t.offset().left;return t.remove(),n}function b(t,e){return parseFloat(t.css(e))||0}function w(t){return 1===t.which&&!t.ctrlKey}function D(t){var e=t.originalEvent.touches;return e&&e.length?e[0].pageX:t.pageX}function E(t){var e=t.originalEvent.touches;return e&&e.length?e[0].pageY:t.pageY}function S(t){return/^touch/.test(t.type)}function C(t){t.addClass("fc-unselectable").on("selectstart",T)}function R(t){t.removeClass("fc-unselectable").off("selectstart",T)}function T(t){t.preventDefault()}function M(t,e){var n={left:Math.max(t.left,e.left),right:Math.min(t.right,e.right),top:Math.max(t.top,e.top),bottom:Math.min(t.bottom,e.bottom)};return n.left=1&&ut(o)));i++);return r}function L(t,e){var n=k(t);return"week"===n&&"object"==typeof e&&e.days&&(n="day"),n}function V(t,e,n){return null!=n?n.diff(e,t,!0):pt.isDuration(e)?e.as(t):e.end.diff(e.start,t,!0)}function G(t,e,n){var i;return U(n)?(e-t)/n:(i=n.asMonths(),Math.abs(i)>=1&&ut(i)?e.diff(t,"months",!0)/i:e.diff(t,"days",!0)/n.asDays())}function N(t,e){var n,i;return U(t)||U(e)?t/e:(n=t.asMonths(),i=e.asMonths(),Math.abs(n)>=1&&ut(n)&&Math.abs(i)>=1&&ut(i)?n/i:t.asDays()/e.asDays())}function j(t,e){var n;return U(t)?pt.duration(t*e):(n=t.asMonths(),Math.abs(n)>=1&&ut(n)?pt.duration({months:n*e}):pt.duration({days:t.asDays()*e}))}function U(t){return Boolean(t.hours()||t.minutes()||t.seconds()||t.milliseconds())}function W(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function q(t){return"string"==typeof t&&/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(t)}function Y(){for(var t=[],e=0;e=0;o--)if("object"==typeof(s=t[o][i]))r.unshift(s);else if(void 0!==s){l[i]=s;break}r.length&&(l[i]=Q(r))}for(n=t.length-1;n>=0;n--){a=t[n];for(i in a)i in l||(l[i]=a[i])}return l}function X(t,e){for(var n in t)$(t,n)&&(e[n]=t[n])}function $(t,e){return gt.call(t,e)}function K(t,e,n){if(ht.isFunction(t)&&(t=[t]),t){var i=void 0,r=void 0;for(i=0;i/g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
    ")}function rt(t){return t.replace(/&.*?;/g,"")}function ot(t){var e=[];return ht.each(t,function(t,n){null!=n&&e.push(t+":"+n)}),e.join(";")}function st(t){var e=[];return ht.each(t,function(t,n){null!=n&&e.push(t+'="'+it(n)+'"')}),e.join(" ")}function at(t){return t.charAt(0).toUpperCase()+t.slice(1)}function lt(t,e){return t-e}function ut(t){return t%1==0}function dt(t,e){var n=t[e];return function(){return n.apply(t,arguments)}}function ct(t,e,n){void 0===n&&(n=!1);var i,r,o,s,a,l=function(){var u=+new Date-s;ua&&s.push(new t(a,o.startMs)),o.endMs>a&&(a=o.endMs);return at.startMs)&&(null==this.startMs||null==t.endMs||this.startMs=this.startMs)&&(null==this.endMs||null!=t.endMs&&t.endMs<=this.endMs)},t.prototype.containsDate=function(t){var e=t.valueOf();return(null==this.startMs||e>=this.startMs)&&(null==this.endMs||e=this.endMs&&(e=this.endMs-1),e},t.prototype.equals=function(t){return this.startMs===t.startMs&&this.endMs===t.endMs},t.prototype.clone=function(){var e=new t(this.startMs,this.endMs);return e.isStart=this.isStart,e.isEnd=this.isEnd,e},t.prototype.getStart=function(){return null!=this.startMs?o.default.utc(this.startMs).stripZone():null},t.prototype.getEnd=function(){return null!=this.endMs?o.default.utc(this.endMs).stripZone():null},t.prototype.as=function(t){return r.utc(this.endMs).diff(r.utc(this.startMs),t,!0)},t}();e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(208),s=n(33),a=n(49),l=function(t){function e(n){var i=t.call(this)||this;return i.calendar=n,i.className=[],i.uid=String(e.uuid++),i}return i.__extends(e,t),e.parse=function(t,e){var n=new this(e);return!("object"!=typeof t||!n.applyProps(t))&&n},e.normalizeId=function(t){return t?String(t):null},e.prototype.fetch=function(t,e,n){},e.prototype.removeEventDefsById=function(t){},e.prototype.removeAllEventDefs=function(){},e.prototype.getPrimitive=function(t){},e.prototype.parseEventDefs=function(t){var e,n,i=[];for(e=0;e0},e}(o.default);e.default=s},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t,e){this.isAllDay=!1,this.unzonedRange=t,this.isAllDay=e}return t.prototype.toLegacy=function(t){return{start:t.msToMoment(this.unzonedRange.startMs,this.isAllDay),end:t.msToMoment(this.unzonedRange.endMs,this.isAllDay)}},t}();e.default=n},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(34),o=n(209),s=n(17),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.buildInstances=function(){return[this.buildInstance()]},e.prototype.buildInstance=function(){return new o.default(this,this.dateProfile)},e.prototype.isAllDay=function(){return this.dateProfile.isAllDay()},e.prototype.clone=function(){var e=t.prototype.clone.call(this);return e.dateProfile=this.dateProfile,e},e.prototype.rezone=function(){var t=this.source.calendar,e=this.dateProfile;this.dateProfile=new s.default(t.moment(e.start),e.end?t.moment(e.end):null,t)},e.prototype.applyManualStandardProps=function(e){var n=t.prototype.applyManualStandardProps.call(this,e),i=s.default.parse(e,this.source);return!!i&&(this.dateProfile=i,null!=e.date&&(this.miscProps.date=e.date),n)},e}(r.default);e.default=a,a.defineStandardProps({start:!1,date:!1,end:!1,allDay:!1})},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(){}return t.mixInto=function(t){var e=this;Object.getOwnPropertyNames(this.prototype).forEach(function(n){t.prototype[n]||(t.prototype[n]=e.prototype[n])})},t.mixOver=function(t){var e=this;Object.getOwnPropertyNames(this.prototype).forEach(function(n){t.prototype[n]=e.prototype[n]})},t}();e.default=n},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t){this.view=t._getView(),this.component=t}return t.prototype.opt=function(t){return this.view.opt(t)},t.prototype.end=function(){},t}();e.default=n},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0}),e.version="3.9.0",e.internalApiVersion=12;var i=n(4);e.applyAll=i.applyAll,e.debounce=i.debounce,e.isInt=i.isInt,e.htmlEscape=i.htmlEscape,e.cssToStr=i.cssToStr,e.proxy=i.proxy,e.capitaliseFirstLetter=i.capitaliseFirstLetter,e.getOuterRect=i.getOuterRect,e.getClientRect=i.getClientRect,e.getContentRect=i.getContentRect,e.getScrollbarWidths=i.getScrollbarWidths,e.preventDefault=i.preventDefault,e.parseFieldSpecs=i.parseFieldSpecs,e.compareByFieldSpecs=i.compareByFieldSpecs,e.compareByFieldSpec=i.compareByFieldSpec,e.flexibleCompare=i.flexibleCompare,e.computeGreatestUnit=i.computeGreatestUnit,e.divideRangeByDuration=i.divideRangeByDuration,e.divideDurationByDuration=i.divideDurationByDuration,e.multiplyDuration=i.multiplyDuration,e.durationHasTime=i.durationHasTime,e.log=i.log,e.warn=i.warn,e.removeExact=i.removeExact,e.intersectRects=i.intersectRects;var r=n(47);e.formatDate=r.formatDate,e.formatRange=r.formatRange,e.queryMostGranularFormatUnit=r.queryMostGranularFormatUnit;var o=n(31);e.datepickerLocale=o.datepickerLocale,e.locale=o.locale;var s=n(10);e.moment=s.default;var a=n(11);e.EmitterMixin=a.default;var l=n(7);e.ListenerMixin=l.default;var u=n(48);e.Model=u.default;var d=n(207);e.Constraints=d.default;var c=n(5);e.UnzonedRange=c.default;var p=n(12);e.ComponentFootprint=p.default;var h=n(212);e.BusinessHourGenerator=h.default;var f=n(34);e.EventDef=f.default;var g=n(37);e.EventDefMutation=g.default;var v=n(38);e.EventSourceParser=v.default;var y=n(6);e.EventSource=y.default;var m=n(51);e.defineThemeSystem=m.defineThemeSystem;var b=n(18);e.EventInstanceGroup=b.default;var w=n(52);e.ArrayEventSource=w.default;var D=n(215);e.FuncEventSource=D.default;var E=n(216);e.JsonFeedEventSource=E.default;var S=n(36);e.EventFootprint=S.default;var C=n(33);e.Class=C.default;var R=n(14);e.Mixin=R.default;var T=n(53);e.CoordCache=T.default;var M=n(54);e.DragListener=M.default;var I=n(20);e.Promise=I.default;var H=n(217);e.TaskQueue=H.default;var P=n(218);e.RenderQueue=P.default;var _=n(39);e.Scroller=_.default;var x=n(19);e.Theme=x.default;var O=n(219);e.DateComponent=O.default;var F=n(40);e.InteractiveDateComponent=F.default;var z=n(220);e.Calendar=z.default;var B=n(41);e.View=B.default;var A=n(22);e.defineView=A.defineView,e.getViewConfig=A.getViewConfig;var k=n(55);e.DayTableMixin=k.default;var L=n(56);e.BusinessHourRenderer=L.default;var V=n(42);e.EventRenderer=V.default;var G=n(57);e.FillRenderer=G.default;var N=n(58);e.HelperRenderer=N.default;var j=n(222);e.ExternalDropping=j.default;var U=n(223);e.EventResizing=U.default;var W=n(59);e.EventPointing=W.default;var q=n(224);e.EventDragging=q.default;var Y=n(225);e.DateSelecting=Y.default;var Z=n(60);e.StandardInteractionsMixin=Z.default;var Q=n(226);e.AgendaView=Q.default;var X=n(227);e.TimeGrid=X.default;var $=n(61);e.DayGrid=$.default;var K=n(62);e.BasicView=K.default;var J=n(229);e.MonthView=J.default;var tt=n(230);e.ListView=tt.default},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),r=function(){function t(t,e,n){this.start=t,this.end=e||null,this.unzonedRange=this.buildUnzonedRange(n)}return t.parse=function(e,n){var i=e.start||e.date,r=e.end;if(!i)return!1;var o=n.calendar,s=o.moment(i),a=r?o.moment(r):null,l=e.allDay,u=o.opt("forceEventDuration");return!!s.isValid()&&(!a||a.isValid()&&a.isAfter(s)||(a=null),null==l&&null==(l=n.allDayDefault)&&(l=o.opt("allDayDefault")),!0===l?(s.stripTime(),a&&a.stripTime()):!1===l&&(s.hasTime()||s.time(0),a&&!a.hasTime()&&a.time(0)),!a&&u&&(a=o.getDefaultEventEnd(!s.hasTime(),s)),new t(s,a,o))},t.isStandardProp=function(t){return"start"===t||"date"===t||"end"===t||"allDay"===t},t.prototype.isAllDay=function(){return!(this.start.hasTime()||this.end&&this.end.hasTime())},t.prototype.buildUnzonedRange=function(t){var e=this.start.clone().stripZone().valueOf(),n=this.getEnd(t).stripZone().valueOf();return new i.default(e,n)},t.prototype.getEnd=function(t){return this.end?this.end.clone():t.getDefaultEventEnd(this.isAllDay(),this.start)},t}();e.default=r},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(5),r=n(35),o=n(211),s=function(){function t(t){this.eventInstances=t||[]}return t.prototype.getAllEventRanges=function(t){return t?this.sliceNormalRenderRanges(t):this.eventInstances.map(r.eventInstanceToEventRange)},t.prototype.sliceRenderRanges=function(t){return this.isInverse()?this.sliceInverseRenderRanges(t):this.sliceNormalRenderRanges(t)},t.prototype.sliceNormalRenderRanges=function(t){var e,n,i,r=this.eventInstances,s=[];for(e=0;e
    ')},e.prototype.clear=function(){this.setHeight("auto"),this.applyOverflow()},e.prototype.destroy=function(){this.el.remove()},e.prototype.applyOverflow=function(){this.scrollEl.css({"overflow-x":this.overflowX,"overflow-y":this.overflowY})},e.prototype.lockOverflow=function(t){var e=this.overflowX,n=this.overflowY;t=t||this.getScrollbarWidths(),"auto"===e&&(e=t.top||t.bottom||this.scrollEl[0].scrollWidth-1>this.scrollEl[0].clientWidth?"scroll":"hidden"),"auto"===n&&(n=t.left||t.right||this.scrollEl[0].scrollHeight-1>this.scrollEl[0].clientHeight?"scroll":"hidden"),this.scrollEl.css({"overflow-x":e,"overflow-y":n})},e.prototype.setHeight=function(t){this.scrollEl.height(t)},e.prototype.getScrollTop=function(){return this.scrollEl.scrollTop()},e.prototype.setScrollTop=function(t){this.scrollEl.scrollTop(t)},e.prototype.getClientWidth=function(){return this.scrollEl[0].clientWidth},e.prototype.getClientHeight=function(){return this.scrollEl[0].clientHeight},e.prototype.getScrollbarWidths=function(){return o.getScrollbarWidths(this.scrollEl)},e}(s.default);e.default=a},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(4),s=n(219),a=n(21),l=function(t){function e(e,n){var i=t.call(this,e,n)||this;return i.segSelector=".fc-event-container > *",i.dateSelectingClass&&(i.dateClicking=new i.dateClickingClass(i)),i.dateSelectingClass&&(i.dateSelecting=new i.dateSelectingClass(i)),i.eventPointingClass&&(i.eventPointing=new i.eventPointingClass(i)),i.eventDraggingClass&&i.eventPointing&&(i.eventDragging=new i.eventDraggingClass(i,i.eventPointing)),i.eventResizingClass&&i.eventPointing&&(i.eventResizing=new i.eventResizingClass(i,i.eventPointing)),i.externalDroppingClass&&(i.externalDropping=new i.externalDroppingClass(i)),i}return i.__extends(e,t),e.prototype.setElement=function(e){t.prototype.setElement.call(this,e),this.dateClicking&&this.dateClicking.bindToEl(e),this.dateSelecting&&this.dateSelecting.bindToEl(e),this.bindAllSegHandlersToEl(e)},e.prototype.removeElement=function(){this.endInteractions(),t.prototype.removeElement.call(this)},e.prototype.executeEventUnrender=function(){this.endInteractions(),t.prototype.executeEventUnrender.call(this)},e.prototype.bindGlobalHandlers=function(){t.prototype.bindGlobalHandlers.call(this),this.externalDropping&&this.externalDropping.bindToDocument()},e.prototype.unbindGlobalHandlers=function(){t.prototype.unbindGlobalHandlers.call(this),this.externalDropping&&this.externalDropping.unbindFromDocument()},e.prototype.bindDateHandlerToEl=function(t,e,n){var i=this;this.el.on(e,function(t){if(!r(t.target).is(i.segSelector+":not(.fc-helper),"+i.segSelector+":not(.fc-helper) *,.fc-more,a[data-goto]"))return n.call(i,t)})},e.prototype.bindAllSegHandlersToEl=function(t){[this.eventPointing,this.eventDragging,this.eventResizing].forEach(function(e){e&&e.bindToEl(t)})},e.prototype.bindSegHandlerToEl=function(t,e,n){var i=this;t.on(e,this.segSelector,function(t){var e=r(t.currentTarget);if(!e.is(".fc-helper")){var o=e.data("fc-seg");if(o&&!i.shouldIgnoreEventPointing())return n.call(i,o,t)}})},e.prototype.shouldIgnoreMouse=function(){return a.default.get().shouldIgnoreMouse()},e.prototype.shouldIgnoreTouch=function(){var t=this._getView();return t.isSelected||t.selectedEvent},e.prototype.shouldIgnoreEventPointing=function(){return this.eventDragging&&this.eventDragging.isDragging||this.eventResizing&&this.eventResizing.isResizing},e.prototype.canStartSelection=function(t,e){return o.getEvIsTouch(e)&&!this.canStartResize(t,e)&&(this.isEventDefDraggable(t.footprint.eventDef)||this.isEventDefResizable(t.footprint.eventDef))},e.prototype.canStartDrag=function(t,e){return!this.canStartResize(t,e)&&this.isEventDefDraggable(t.footprint.eventDef)},e.prototype.canStartResize=function(t,e){var n=this._getView(),i=t.footprint.eventDef;return(!o.getEvIsTouch(e)||n.isEventDefSelected(i))&&this.isEventDefResizable(i)&&r(e.target).is(".fc-resizer")},e.prototype.endInteractions=function(){[this.dateClicking,this.dateSelecting,this.eventPointing,this.eventDragging,this.eventResizing].forEach(function(t){t&&t.end()})},e.prototype.isEventDefDraggable=function(t){return this.isEventDefStartEditable(t)},e.prototype.isEventDefStartEditable=function(t){var e=t.isStartExplicitlyEditable();return null==e&&null==(e=this.opt("eventStartEditable"))&&(e=this.isEventDefGenerallyEditable(t)),e},e.prototype.isEventDefGenerallyEditable=function(t){var e=t.isExplicitlyEditable();return null==e&&(e=this.opt("editable")),e},e.prototype.isEventDefResizableFromStart=function(t){return this.opt("eventResizableFromStart")&&this.isEventDefResizable(t)},e.prototype.isEventDefResizableFromEnd=function(t){return this.isEventDefResizable(t)},e.prototype.isEventDefResizable=function(t){var e=t.isDurationExplicitlyEditable();return null==e&&null==(e=this.opt("eventDurationEditable"))&&(e=this.isEventDefGenerallyEditable(t)),e},e.prototype.diffDates=function(t,e){return this.largeUnit?o.diffByUnit(t,e,this.largeUnit):o.diffDayTime(t,e)},e.prototype.isEventInstanceGroupAllowed=function(t){var e,n=this._getView(),i=this.dateProfile,r=this.eventRangesToEventFootprints(t.getAllEventRanges());for(e=0;e1?"ll":"LL"},e.prototype.setDate=function(t){var e=this.get("dateProfile"),n=this.dateProfileGenerator.build(t,void 0,!0);e&&e.activeUnzonedRange.equals(n.activeUnzonedRange)||this.set("dateProfile",n)},e.prototype.unsetDate=function(){this.unset("dateProfile")},e.prototype.fetchInitialEvents=function(t){var e=this.calendar,n=t.isRangeAllDay&&!this.usesMinMaxTime;return e.requestEvents(e.msToMoment(t.activeUnzonedRange.startMs,n),e.msToMoment(t.activeUnzonedRange.endMs,n))},e.prototype.bindEventChanges=function(){this.listenTo(this.calendar,"eventsReset",this.resetEvents)},e.prototype.unbindEventChanges=function(){this.stopListeningTo(this.calendar,"eventsReset")},e.prototype.setEvents=function(t){this.set("currentEvents",t),this.set("hasEvents",!0)},e.prototype.unsetEvents=function(){this.unset("currentEvents"),this.unset("hasEvents")},e.prototype.resetEvents=function(t){this.startBatchRender(),this.unsetEvents(),this.setEvents(t),this.stopBatchRender()},e.prototype.requestDateRender=function(t){var e=this;this.requestRender(function(){e.executeDateRender(t)},"date","init")},e.prototype.requestDateUnrender=function(){var t=this;this.requestRender(function(){t.executeDateUnrender()},"date","destroy")},e.prototype.executeDateRender=function(e){t.prototype.executeDateRender.call(this,e),this.render&&this.render(),this.trigger("datesRendered"),this.addScroll({isDateInit:!0}),this.startNowIndicator()},e.prototype.executeDateUnrender=function(){this.unselect(),this.stopNowIndicator(),this.trigger("before:datesUnrendered"),this.destroy&&this.destroy(),t.prototype.executeDateUnrender.call(this)},e.prototype.bindBaseRenderHandlers=function(){var t=this;this.on("datesRendered",function(){t.whenSizeUpdated(t.triggerViewRender)}),this.on("before:datesUnrendered",function(){t.triggerViewDestroy()})},e.prototype.triggerViewRender=function(){this.publiclyTrigger("viewRender",{context:this,args:[this,this.el]})},e.prototype.triggerViewDestroy=function(){this.publiclyTrigger("viewDestroy",{context:this,args:[this,this.el]})},e.prototype.requestEventsRender=function(t){var e=this;this.requestRender(function(){e.executeEventRender(t),e.whenSizeUpdated(e.triggerAfterEventsRendered)},"event","init")},e.prototype.requestEventsUnrender=function(){var t=this;this.requestRender(function(){t.triggerBeforeEventsDestroyed(),t.executeEventUnrender()},"event","destroy")},e.prototype.requestBusinessHoursRender=function(t){var e=this;this.requestRender(function(){e.renderBusinessHours(t)},"businessHours","init")},e.prototype.requestBusinessHoursUnrender=function(){var t=this;this.requestRender(function(){t.unrenderBusinessHours()},"businessHours","destroy")},e.prototype.bindGlobalHandlers=function(){t.prototype.bindGlobalHandlers.call(this),this.listenTo(d.default.get(),{touchstart:this.processUnselect,mousedown:this.handleDocumentMousedown})},e.prototype.unbindGlobalHandlers=function(){t.prototype.unbindGlobalHandlers.call(this),this.stopListeningTo(d.default.get())},e.prototype.startNowIndicator=function(){var t,e,n,i=this;this.opt("nowIndicator")&&(t=this.getNowIndicatorUnit())&&(e=s.proxy(this,"updateNowIndicator"),this.initialNowDate=this.calendar.getNow(),this.initialNowQueriedMs=(new Date).valueOf(),n=this.initialNowDate.clone().startOf(t).add(1,t).valueOf()-this.initialNowDate.valueOf(),this.nowIndicatorTimeoutID=setTimeout(function(){i.nowIndicatorTimeoutID=null,e(),n=+o.duration(1,t),n=Math.max(100,n),i.nowIndicatorIntervalID=setInterval(e,n)},n))},e.prototype.updateNowIndicator=function(){this.isDatesRendered&&this.initialNowDate&&(this.unrenderNowIndicator(),this.renderNowIndicator(this.initialNowDate.clone().add((new Date).valueOf()-this.initialNowQueriedMs)),this.isNowIndicatorRendered=!0)},e.prototype.stopNowIndicator=function(){this.isNowIndicatorRendered&&(this.nowIndicatorTimeoutID&&(clearTimeout(this.nowIndicatorTimeoutID),this.nowIndicatorTimeoutID=null),this.nowIndicatorIntervalID&&(clearInterval(this.nowIndicatorIntervalID),this.nowIndicatorIntervalID=null),this.unrenderNowIndicator(),this.isNowIndicatorRendered=!1)},e.prototype.updateSize=function(e,n,i){this.setHeight?this.setHeight(e,n):t.prototype.updateSize.call(this,e,n,i),this.updateNowIndicator()},e.prototype.addScroll=function(t){var e=this.queuedScroll||(this.queuedScroll={});r.extend(e,t)},e.prototype.popScroll=function(){this.applyQueuedScroll(),this.queuedScroll=null},e.prototype.applyQueuedScroll=function(){this.queuedScroll&&this.applyScroll(this.queuedScroll)},e.prototype.queryScroll=function(){var t={};return this.isDatesRendered&&r.extend(t,this.queryDateScroll()),t},e.prototype.applyScroll=function(t){t.isDateInit&&this.isDatesRendered&&r.extend(t,this.computeInitialDateScroll()),this.isDatesRendered&&this.applyDateScroll(t)},e.prototype.computeInitialDateScroll=function(){return{}},e.prototype.queryDateScroll=function(){return{}},e.prototype.applyDateScroll=function(t){},e.prototype.reportEventDrop=function(t,e,n,i){var r=this.calendar.eventManager,s=r.mutateEventsWithId(t.def.id,e),a=e.dateMutation;a&&(t.dateProfile=a.buildNewDateProfile(t.dateProfile,this.calendar)),this.triggerEventDrop(t,a&&a.dateDelta||o.duration(),s,n,i)},e.prototype.triggerEventDrop=function(t,e,n,i,r){this.publiclyTrigger("eventDrop",{context:i[0],args:[t.toLegacy(),e,n,r,{},this]})},e.prototype.reportExternalDrop=function(t,e,n,i,r,o){e&&this.calendar.eventManager.addEventDef(t,n),this.triggerExternalDrop(t,e,i,r,o)},e.prototype.triggerExternalDrop=function(t,e,n,i,r){this.publiclyTrigger("drop",{context:n[0],args:[t.dateProfile.start.clone(),i,r,this]}),e&&this.publiclyTrigger("eventReceive",{context:this,args:[t.buildInstance().toLegacy(),this]})},e.prototype.reportEventResize=function(t,e,n,i){var r=this.calendar.eventManager,o=r.mutateEventsWithId(t.def.id,e);t.dateProfile=e.dateMutation.buildNewDateProfile(t.dateProfile,this.calendar),this.triggerEventResize(t,e.dateMutation.endDelta,o,n,i)},e.prototype.triggerEventResize=function(t,e,n,i,r){this.publiclyTrigger("eventResize",{context:i[0],args:[t.toLegacy(),e,n,r,{},this]})},e.prototype.select=function(t,e){this.unselect(e),this.renderSelectionFootprint(t),this.reportSelection(t,e)},e.prototype.renderSelectionFootprint=function(e){this.renderSelection?this.renderSelection(e.toLegacy(this.calendar)):t.prototype.renderSelectionFootprint.call(this,e)},e.prototype.reportSelection=function(t,e){this.isSelected=!0,this.triggerSelect(t,e)},e.prototype.triggerSelect=function(t,e){var n=this.calendar.footprintToDateProfile(t);this.publiclyTrigger("select",{context:this,args:[n.start,n.end,e,this]})},e.prototype.unselect=function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.publiclyTrigger("unselect",{context:this,args:[t,this]}))},e.prototype.selectEventInstance=function(t){this.selectedEventInstance&&this.selectedEventInstance===t||(this.unselectEventInstance(),this.getEventSegs().forEach(function(e){e.footprint.eventInstance===t&&e.el&&e.el.addClass("fc-selected")}),this.selectedEventInstance=t)},e.prototype.unselectEventInstance=function(){this.selectedEventInstance&&(this.getEventSegs().forEach(function(t){t.el&&t.el.removeClass("fc-selected")}),this.selectedEventInstance=null)},e.prototype.isEventDefSelected=function(t){return this.selectedEventInstance&&this.selectedEventInstance.def.id===t.id},e.prototype.handleDocumentMousedown=function(t){s.isPrimaryMouseButton(t)&&this.processUnselect(t)},e.prototype.processUnselect=function(t){this.processRangeUnselect(t),this.processEventUnselect(t)},e.prototype.processRangeUnselect=function(t){var e;this.isSelected&&this.opt("unselectAuto")&&((e=this.opt("unselectCancel"))&&r(t.target).closest(e).length||this.unselect(t))},e.prototype.processEventUnselect=function(t){this.selectedEventInstance&&(r(t.target).closest(".fc-selected").length||this.unselectEventInstance())},e.prototype.triggerBaseRendered=function(){this.publiclyTrigger("viewRender",{context:this,args:[this,this.el]})},e.prototype.triggerBaseUnrendered=function(){this.publiclyTrigger("viewDestroy",{context:this,args:[this,this.el]})},e.prototype.triggerDayClick=function(t,e,n){var i=this.calendar.footprintToDateProfile(t);this.publiclyTrigger("dayClick",{context:e,args:[i.start,n,this]})},e.prototype.isDateInOtherMonth=function(t,e){return!1},e.prototype.getUnzonedRangeOption=function(t){var e=this.opt(t);if("function"==typeof e&&(e=e.apply(null,Array.prototype.slice.call(arguments,1))),e)return this.calendar.parseUnzonedRange(e)},e.prototype.initHiddenDays=function(){var t,e=this.opt("hiddenDays")||[],n=[],i=0;for(!1===this.opt("weekends")&&e.push(0,6),t=0;t<7;t++)(n[t]=-1!==r.inArray(t,e))||i++;if(!i)throw new Error("invalid hiddenDays");this.isHiddenDayHash=n},e.prototype.trimHiddenDays=function(t){var e=t.getStart(),n=t.getEnd();return e&&(e=this.skipHiddenDays(e)),n&&(n=this.skipHiddenDays(n,-1,!0)),null===e||null===n||eo&&(!l[s]||u.isSame(d,l[s]))&&(s-1!==o||"."!==c[s]);s--)v=c[s]+v;for(a=o;a<=s;a++)y+=c[a],m+=p[a];return(y||m)&&(b=r?m+i+y:y+i+m),g(h+b+v)}function a(t){return C[t]||(C[t]=l(t))}function l(t){var e=u(t);return{fakeFormatString:c(e),sameUnits:p(e)}}function u(t){for(var e,n=[],i=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=i.exec(t);)e[1]?n.push.apply(n,d(e[1])):e[2]?n.push({maybe:u(e[2])}):e[3]?n.push({token:e[3]}):e[5]&&n.push.apply(n,d(e[5]));return n}function d(t){return". "===t?["."," "]:[t]}function c(t){var e,n,i=[];for(e=0;er.value)&&(r=i);return r?r.unit:null}Object.defineProperty(e,"__esModule",{value:!0});var y=n(10);y.newMomentProto.format=function(){return this._fullCalendar&&arguments[0]?r(this,arguments[0]):this._ambigTime?y.oldMomentFormat(i(this),"YYYY-MM-DD"):this._ambigZone?y.oldMomentFormat(i(this),"YYYY-MM-DD[T]HH:mm:ss"):this._fullCalendar?y.oldMomentFormat(i(this)):y.oldMomentProto.format.apply(this,arguments)},y.newMomentProto.toISOString=function(){return this._ambigTime?y.oldMomentFormat(i(this),"YYYY-MM-DD"):this._ambigZone?y.oldMomentFormat(i(this),"YYYY-MM-DD[T]HH:mm:ss"):this._fullCalendar?y.oldMomentProto.toISOString.apply(i(this),arguments):y.oldMomentProto.toISOString.apply(this,arguments)};var m="\v",b="",w="",D=new RegExp(w+"([^"+w+"]*)"+w,"g"),E={t:function(t){return y.oldMomentFormat(t,"a").charAt(0)},T:function(t){return y.oldMomentFormat(t,"A").charAt(0)}},S={Y:{value:1,unit:"year"},M:{value:2,unit:"month"},W:{value:3,unit:"week"},w:{value:3,unit:"week"},D:{value:4,unit:"day"},d:{value:4,unit:"day"}};e.formatDate=r,e.formatRange=o;var C={};e.queryMostGranularFormatUnit=v},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(33),o=n(11),s=n(7),a=function(t){function e(){var e=t.call(this)||this;return e._watchers={},e._props={},e.applyGlobalWatchers(),e.constructed(),e}return i.__extends(e,t),e.watch=function(t){for(var e=[],n=1;n0&&(t=this.els.eq(0).offsetParent()),this.origin=t?t.offset():null,this.boundingRect=this.queryBoundingRect(),this.isHorizontal&&this.buildElHorizontals(),this.isVertical&&this.buildElVerticals()},t.prototype.clear=function(){this.origin=null,this.boundingRect=null,this.lefts=null,this.rights=null,this.tops=null,this.bottoms=null},t.prototype.ensureBuilt=function(){this.origin||this.build()},t.prototype.buildElHorizontals=function(){var t=[],e=[];this.els.each(function(n,r){var o=i(r),s=o.offset().left,a=o.outerWidth();t.push(s),e.push(s+a)}),this.lefts=t,this.rights=e},t.prototype.buildElVerticals=function(){var t=[],e=[];this.els.each(function(n,r){var o=i(r),s=o.offset().top,a=o.outerHeight();t.push(s),e.push(s+a)}),this.tops=t,this.bottoms=e},t.prototype.getHorizontalIndex=function(t){this.ensureBuilt();var e,n=this.lefts,i=this.rights,r=n.length;for(e=0;e=n[e]&&t=n[e]&&t0&&(t=r.getScrollParent(this.els.eq(0)),!t.is(document))?r.getClientRect(t):null},t.prototype.isPointInBounds=function(t,e){return this.isLeftInBounds(t)&&this.isTopInBounds(e)},t.prototype.isLeftInBounds=function(t){return!this.boundingRect||t>=this.boundingRect.left&&t=this.boundingRect.top&&t=i*i&&this.handleDistanceSurpassed(t),this.isDragging&&this.handleDrag(e,n,t)},t.prototype.handleDrag=function(t,e,n){this.trigger("drag",t,e,n),this.updateAutoScroll(n)},t.prototype.endDrag=function(t){this.isDragging&&(this.isDragging=!1,this.handleDragEnd(t))},t.prototype.handleDragEnd=function(t){this.trigger("dragEnd",t)},t.prototype.startDelay=function(t){var e=this;this.delay?this.delayTimeoutId=setTimeout(function(){e.handleDelayEnd(t)},this.delay):this.handleDelayEnd(t)},t.prototype.handleDelayEnd=function(t){this.isDelayEnded=!0,this.isDistanceSurpassed&&this.startDrag(t)},t.prototype.handleDistanceSurpassed=function(t){this.isDistanceSurpassed=!0,this.isDelayEnded&&this.startDrag(t)},t.prototype.handleTouchMove=function(t){this.isDragging&&this.shouldCancelTouchScroll&&t.preventDefault(),this.handleMove(t)},t.prototype.handleMouseMove=function(t){this.handleMove(t)},t.prototype.handleTouchScroll=function(t){this.isDragging&&!this.scrollAlwaysKills||this.endInteraction(t,!0)},t.prototype.trigger=function(t){for(var e=[],n=1;n=0&&e<=1?l=e*this.scrollSpeed*-1:n>=0&&n<=1&&(l=n*this.scrollSpeed),i>=0&&i<=1?u=i*this.scrollSpeed*-1:o>=0&&o<=1&&(u=o*this.scrollSpeed)),this.setScrollVel(l,u)},t.prototype.setScrollVel=function(t,e){this.scrollTopVel=t,this.scrollLeftVel=e,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(r.proxy(this,"scrollIntervalFunc"),this.scrollIntervalMs))},t.prototype.constrainScrollVel=function(){var t=this.scrollEl;this.scrollTopVel<0?t.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?t.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},t.prototype.scrollIntervalFunc=function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.endAutoScroll()},t.prototype.endAutoScroll=function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.handleScrollEnd())},t.prototype.handleDebouncedScroll=function(){this.scrollIntervalId||this.handleScrollEnd()},t.prototype.handleScrollEnd=function(){},t}();e.default=a,o.default.mixInto(a)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(14),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.updateDayTable=function(){for(var t,e,n,i=this,r=i.view,o=r.calendar,s=o.msToUtcMoment(i.dateProfile.renderUnzonedRange.startMs,!0),a=o.msToUtcMoment(i.dateProfile.renderUnzonedRange.endMs,!0),l=-1,u=[],d=[];s.isBefore(a);)r.isHiddenDay(s)?u.push(l+.5):(l++,u.push(l),d.push(s.clone())),s.add(1,"days");if(this.breakOnWeeks){for(e=d[0].day(),t=1;t=e.length?e[e.length-1]+1:e[n]},e.prototype.computeColHeadFormat=function(){return this.rowCnt>1||this.colCnt>10?"ddd":this.colCnt>1?this.opt("dayOfMonthFormat"):"dddd"},e.prototype.sliceRangeByRow=function(t){var e,n,i,r,o,s=this.daysPerRow,a=this.view.computeDayRange(t),l=this.getDateDayIndex(a.start),u=this.getDateDayIndex(a.end.clone().subtract(1,"days")),d=[];for(e=0;e'+this.renderHeadTrHtml()+"
    "},e.prototype.renderHeadIntroHtml=function(){return this.renderIntroHtml()},e.prototype.renderHeadTrHtml=function(){return""+(this.isRTL?"":this.renderHeadIntroHtml())+this.renderHeadDateCellsHtml()+(this.isRTL?this.renderHeadIntroHtml():"")+""},e.prototype.renderHeadDateCellsHtml=function(){var t,e,n=[];for(t=0;t1?' colspan="'+e+'"':"")+(n?" "+n:"")+">"+(a?s.buildGotoAnchorHtml({date:t,forceOff:o.rowCnt>1||1===o.colCnt},i):i)+""},e.prototype.renderBgTrHtml=function(t){return""+(this.isRTL?"":this.renderBgIntroHtml(t))+this.renderBgCellsHtml(t)+(this.isRTL?this.renderBgIntroHtml(t):"")+""},e.prototype.renderBgIntroHtml=function(t){return this.renderIntroHtml()},e.prototype.renderBgCellsHtml=function(t){var e,n,i=[];for(e=0;e"},e.prototype.renderIntroHtml=function(){},e.prototype.bookendCells=function(t){var e=this.renderIntroHtml();e&&(this.isRTL?t.append(e):t.prepend(e))},e}(o.default);e.default=s},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t,e){this.component=t,this.fillRenderer=e}return t.prototype.render=function(t){var e=this.component,n=e._getDateProfile().activeUnzonedRange,i=t.buildEventInstanceGroup(e.hasAllDayBusinessHours,n),r=i?e.eventRangesToEventFootprints(i.sliceRenderRanges(n)):[];this.renderEventFootprints(r)},t.prototype.renderEventFootprints=function(t){var e=this.component.eventFootprintsToSegs(t);this.renderSegs(e),this.segs=e},t.prototype.renderSegs=function(t){this.fillRenderer&&this.fillRenderer.renderSegs("businessHours",t,{getClasses:function(t){return["fc-nonbusiness","fc-bgevent"]}})},t.prototype.unrender=function(){this.fillRenderer&&this.fillRenderer.unrender("businessHours"),this.segs=null},t.prototype.getSegs=function(){return this.segs||[]},t}();e.default=n},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(3),r=n(4),o=function(){function t(t){this.fillSegTag="div",this.component=t,this.elsByFill={}}return t.prototype.renderFootprint=function(t,e,n){this.renderSegs(t,this.component.componentFootprintToSegs(e),n)},t.prototype.renderSegs=function(t,e,n){var i;return e=this.buildSegEls(t,e,n),i=this.attachSegEls(t,e),i&&this.reportEls(t,i),e},t.prototype.unrender=function(t){var e=this.elsByFill[t];e&&(e.remove(),delete this.elsByFill[t])},t.prototype.buildSegEls=function(t,e,n){var r,o=this,s="",a=[];if(e.length){for(r=0;r"},t.prototype.attachSegEls=function(t,e){},t.prototype.reportEls=function(t,e){this.elsByFill[t]?this.elsByFill[t]=this.elsByFill[t].add(e):this.elsByFill[t]=i(e)},t}();e.default=o},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(13),r=n(36),o=n(6),s=function(){function t(t,e){this.view=t._getView(),this.component=t,this.eventRenderer=e}return t.prototype.renderComponentFootprint=function(t){this.renderEventFootprints([this.fabricateEventFootprint(t)])},t.prototype.renderEventDraggingFootprints=function(t,e,n){this.renderEventFootprints(t,e,"fc-dragging",n?null:this.view.opt("dragOpacity"))},t.prototype.renderEventResizingFootprints=function(t,e,n){this.renderEventFootprints(t,e,"fc-resizing")},t.prototype.renderEventFootprints=function(t,e,n,i){var r,o=this.component.eventFootprintsToSegs(t),s="fc-helper "+(n||"");for(o=this.eventRenderer.renderFgSegEls(o),r=0;r
    '+this.renderBgTrHtml(t)+'
    '+(this.getIsNumbersVisible()?""+this.renderNumberTrHtml(t)+"":"")+"
    "},e.prototype.getIsNumbersVisible=function(){return this.getIsDayNumbersVisible()||this.cellWeekNumbersVisible},e.prototype.getIsDayNumbersVisible=function(){return this.rowCnt>1},e.prototype.renderNumberTrHtml=function(t){return""+(this.isRTL?"":this.renderNumberIntroHtml(t))+this.renderNumberCellsHtml(t)+(this.isRTL?this.renderNumberIntroHtml(t):"")+""},e.prototype.renderNumberIntroHtml=function(t){return this.renderIntroHtml()},e.prototype.renderNumberCellsHtml=function(t){var e,n,i=[];for(e=0;e",this.cellWeekNumbersVisible&&t.day()===n&&(r+=i.buildGotoAnchorHtml({date:t,type:"week"},{class:"fc-week-number"},t.format("w"))),s&&(r+=i.buildGotoAnchorHtml(t,{class:"fc-day-number"},t.format("D"))),r+=""):""},e.prototype.prepareHits=function(){this.colCoordCache.build(),this.rowCoordCache.build(),this.rowCoordCache.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},e.prototype.releaseHits=function(){this.colCoordCache.clear(),this.rowCoordCache.clear()},e.prototype.queryHit=function(t,e){if(this.colCoordCache.isLeftInBounds(t)&&this.rowCoordCache.isTopInBounds(e)){var n=this.colCoordCache.getHorizontalIndex(t),i=this.rowCoordCache.getVerticalIndex(e);if(null!=i&&null!=n)return this.getCellHit(i,n)}},e.prototype.getHitFootprint=function(t){var e=this.getCellRange(t.row,t.col);return new u.default(new l.default(e.start,e.end),!0)},e.prototype.getHitEl=function(t){return this.getCellEl(t.row,t.col)},e.prototype.getCellHit=function(t,e){return{row:t,col:e,component:this,left:this.colCoordCache.getLeftOffset(e),right:this.colCoordCache.getRightOffset(e),top:this.rowCoordCache.getTopOffset(t),bottom:this.rowCoordCache.getBottomOffset(t)}},e.prototype.getCellEl=function(t,e){return this.cellEls.eq(t*this.colCnt+e)},e.prototype.executeEventUnrender=function(){this.removeSegPopover(),t.prototype.executeEventUnrender.call(this)},e.prototype.getOwnEventSegs=function(){ +return t.prototype.getOwnEventSegs.call(this).concat(this.popoverSegs||[])},e.prototype.renderDrag=function(t,e,n){var i;for(i=0;i td > :first-child").each(e),i.position().top+o>a)return n;return!1},e.prototype.limitRow=function(t,e){var n,i,o,s,a,l,u,d,c,p,h,f,g,v,y,m=this,b=this.eventRenderer.rowStructs[t],w=[],D=0,E=function(n){for(;D").append(y),c.append(v),w.push(v[0])),D++};if(e&&e').attr("rowspan",p),l=d[f],y=this.renderMoreLink(t,a.leftCol+f,[a].concat(l)),v=r("
    ").append(y),g.append(v),h.push(g[0]),w.push(g[0]);c.addClass("fc-limited").after(r(h)),o.push(c[0])}}E(this.colCnt),b.moreEls=r(w),b.limitedEls=r(o)}},e.prototype.unlimitRow=function(t){var e=this.eventRenderer.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},e.prototype.renderMoreLink=function(t,e,n){var i=this,o=this.view;return r('').text(this.getMoreLinkText(n.length)).on("click",function(s){var a=i.opt("eventLimitClick"),l=i.getCellDate(t,e),u=r(s.currentTarget),d=i.getCellEl(t,e),c=i.getCellSegs(t,e),p=i.resliceDaySegs(c,l),h=i.resliceDaySegs(n,l);"function"==typeof a&&(a=i.publiclyTrigger("eventLimitClick",{context:o,args:[{date:l.clone(),dayEl:d,moreEl:u,segs:p,hiddenSegs:h},s,o]})),"popover"===a?i.showSegPopover(t,e,u,p):"string"==typeof a&&o.calendar.zoomTo(l,a)})},e.prototype.showSegPopover=function(t,e,n,i){var r,o,s=this,l=this.view,u=n.parent();r=1===this.rowCnt?l.el:this.rowEls.eq(t),o={className:"fc-more-popover "+l.calendar.theme.getClass("popover"),content:this.renderSegPopoverContent(t,e,i),parentEl:l.el,top:r.offset().top,autoHide:!0,viewportConstrain:this.opt("popoverViewportConstrain"),hide:function(){s.popoverSegs&&s.triggerBeforeEventSegsDestroyed(s.popoverSegs),s.segPopover.removeElement(),s.segPopover=null,s.popoverSegs=null}},this.isRTL?o.right=u.offset().left+u.outerWidth()+1:o.left=u.offset().left-1,this.segPopover=new a.default(o),this.segPopover.show(),this.bindAllSegHandlersToEl(this.segPopover.el),this.triggerAfterEventSegsRendered(i)},e.prototype.renderSegPopoverContent=function(t,e,n){var i,s=this.view,a=s.calendar.theme,l=this.getCellDate(t,e).format(this.opt("dayPopoverFormat")),u=r('
    '+o.htmlEscape(l)+'
    '),d=u.find(".fc-event-container");for(n=this.eventRenderer.renderFgSegEls(n,!0),this.popoverSegs=n,i=0;i"+s.htmlEscape(this.opt("weekNumberTitle"))+"":""},e.prototype.renderNumberIntroHtml=function(t){var e=this.view,n=this.getCellDate(t,0);return this.colWeekNumbersVisible?'"+e.buildGotoAnchorHtml({date:n,type:"week",forceOff:1===this.colCnt},n.format("w"))+"":""},e.prototype.renderBgIntroHtml=function(){var t=this.view;return this.colWeekNumbersVisible?'":""},e.prototype.renderIntroHtml=function(){var t=this.view;return this.colWeekNumbersVisible?'":""},e.prototype.getIsNumbersVisible=function(){return d.default.prototype.getIsNumbersVisible.apply(this,arguments)||this.colWeekNumbersVisible},e}(t)}Object.defineProperty(e,"__esModule",{value:!0});var r=n(2),o=n(3),s=n(4),a=n(39),l=n(41),u=n(228),d=n(61),c=function(t){function e(e,n){var i=t.call(this,e,n)||this;return i.dayGrid=i.instantiateDayGrid(),i.dayGrid.isRigid=i.hasRigidRows(),i.opt("weekNumbers")&&(i.opt("weekNumbersWithinDays")?(i.dayGrid.cellWeekNumbersVisible=!0,i.dayGrid.colWeekNumbersVisible=!1):(i.dayGrid.cellWeekNumbersVisible=!1,i.dayGrid.colWeekNumbersVisible=!0)),i.addChild(i.dayGrid),i.scroller=new a.default({overflowX:"hidden",overflowY:"auto"}),i}return r.__extends(e,t),e.prototype.instantiateDayGrid=function(){return new(i(this.dayGridClass))(this)},e.prototype.executeDateRender=function(e){this.dayGrid.breakOnWeeks=/year|month|week/.test(e.currentRangeUnit),t.prototype.executeDateRender.call(this,e)},e.prototype.renderSkeleton=function(){var t,e;this.el.addClass("fc-basic-view").html(this.renderSkeletonHtml()),this.scroller.render(),t=this.scroller.el.addClass("fc-day-grid-container"),e=o('
    ').appendTo(t),this.el.find(".fc-body > tr > td").append(t),this.dayGrid.headContainerEl=this.el.find(".fc-head-container"),this.dayGrid.setElement(e)},e.prototype.unrenderSkeleton=function(){this.dayGrid.removeElement(),this.scroller.destroy()},e.prototype.renderSkeletonHtml=function(){var t=this.calendar.theme;return''+(this.opt("columnHeader")?'':"")+'
     
    '},e.prototype.weekNumberStyleAttr=function(){return null!=this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},e.prototype.hasRigidRows=function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},e.prototype.updateSize=function(e,n,i){var r,o,a=this.opt("eventLimit"),l=this.dayGrid.headContainerEl.find(".fc-row");if(!this.dayGrid.rowEls)return void(n||(r=this.computeScrollerHeight(e),this.scroller.setHeight(r)));t.prototype.updateSize.call(this,e,n,i),this.dayGrid.colWeekNumbersVisible&&(this.weekNumberWidth=s.matchCellWidths(this.el.find(".fc-week-number"))),this.scroller.clear(),s.uncompensateScroll(l),this.dayGrid.removeSegPopover(),a&&"number"==typeof a&&this.dayGrid.limitRows(a),r=this.computeScrollerHeight(e),this.setGridHeight(r,n),a&&"number"!=typeof a&&this.dayGrid.limitRows(a),n||(this.scroller.setHeight(r),o=this.scroller.getScrollbarWidths(),(o.left||o.right)&&(s.compensateScroll(l,o),r=this.computeScrollerHeight(e),this.scroller.setHeight(r)),this.scroller.lockOverflow(o))},e.prototype.computeScrollerHeight=function(t){return t-s.subtractInnerElHeight(this.el,this.scroller.el)},e.prototype.setGridHeight=function(t,e){e?s.undistributeHeight(this.dayGrid.rowEls):s.distributeHeight(this.dayGrid.rowEls,t,!0)},e.prototype.computeInitialDateScroll=function(){return{top:0}},e.prototype.queryDateScroll=function(){return{top:this.scroller.getScrollTop()}},e.prototype.applyDateScroll=function(t){void 0!==t.top&&this.scroller.setScrollTop(t.top)},e}(l.default);e.default=c,c.prototype.dateProfileGeneratorClass=u.default,c.prototype.dayGridClass=d.default},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(t,e,n){function i(t,e,n){var i;for(i=0;i=0;e--)switch(n=i[e],n.type){case"init":r=!1;case"add":case"remove":i.splice(e,1)}return r&&i.push(t),r},e}(r.default);e.default=o},function(t,e,n){function i(t){var e,n,i,r=[];for(e in t)for(n=t[e].eventInstances,i=0;i'+n+"
    ":""+n+""},e.prototype.getAllDayHtml=function(){return this.opt("allDayHtml")||a.htmlEscape(this.opt("allDayText"))},e.prototype.getDayClasses=function(t,e){var n,i=this._getView(),r=[] +;return this.dateProfile.activeUnzonedRange.containsDate(t)?(r.push("fc-"+a.dayIDs[t.day()]),i.isDateInOtherMonth(t,this.dateProfile)&&r.push("fc-other-month"),n=i.calendar.getNow(),t.isSame(n,"day")?(r.push("fc-today"),!0!==e&&r.push(i.calendar.theme.getClass("today"))):t=this.nextDayThreshold&&o.add(1,"days"),o<=n&&(o=n.clone().add(1,"days")),{start:n,end:o}},e.prototype.isMultiDayRange=function(t){var e=this.computeDayRange(t);return e.end.diff(e.start,"days")>1},e.guid=0,e}(d.default);e.default=p},function(t,e,n){function i(t,e){return null==e?t:r.isFunction(e)?t.filter(e):(e+="",t.filter(function(t){return t.id==e||t._id===e}))}Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),o=n(0),s=n(4),a=n(32),l=n(238),u=n(21),d=n(11),c=n(7),p=n(239),h=n(240),f=n(241),g=n(207),v=n(31),y=n(10),m=n(5),b=n(12),w=n(17),D=n(242),E=n(212),S=n(38),C=n(49),R=n(13),T=n(37),M=n(6),I=n(51),H=function(){function t(t,e){this.loadingLevel=0,this.ignoreUpdateViewSize=0,this.freezeContentHeightDepth=0,u.default.needed(),this.el=t,this.viewsByType={},this.optionsManager=new h.default(this,e),this.viewSpecManager=new f.default(this.optionsManager,this),this.initMomentInternals(),this.initCurrentDate(),this.initEventManager(),this.constraints=new g.default(this.eventManager,this),this.constructed()}return t.prototype.constructed=function(){},t.prototype.getView=function(){return this.view},t.prototype.publiclyTrigger=function(t,e){var n,i,o=this.opt(t);if(r.isPlainObject(e)?(n=e.context,i=e.args):r.isArray(e)&&(i=e),null==n&&(n=this.el[0]),i||(i=[]),this.triggerWith(t,n,i),o)return o.apply(n,i)},t.prototype.hasPublicHandlers=function(t){return this.hasHandlers(t)||this.opt(t)},t.prototype.option=function(t,e){var n;if("string"==typeof t){if(void 0===e)return this.optionsManager.get(t);n={},n[t]=e,this.optionsManager.add(n)}else"object"==typeof t&&this.optionsManager.add(t)},t.prototype.opt=function(t){return this.optionsManager.get(t)},t.prototype.instantiateView=function(t){var e=this.viewSpecManager.getViewSpec(t);if(!e)throw new Error('View type "'+t+'" is not valid');return new e.class(this,e)},t.prototype.isValidViewType=function(t){return Boolean(this.viewSpecManager.getViewSpec(t))},t.prototype.changeView=function(t,e){e&&(e.start&&e.end?this.optionsManager.recordOverrides({visibleRange:e}):this.currentDate=this.moment(e).stripZone()),this.renderView(t)},t.prototype.zoomTo=function(t,e){var n;e=e||"day",n=this.viewSpecManager.getViewSpec(e)||this.viewSpecManager.getUnitViewSpec(e),this.currentDate=t.clone(),this.renderView(n?n.type:null)},t.prototype.initCurrentDate=function(){var t=this.opt("defaultDate");this.currentDate=null!=t?this.moment(t).stripZone():this.getNow()},t.prototype.prev=function(){var t=this.view,e=t.dateProfileGenerator.buildPrev(t.get("dateProfile"));e.isValid&&(this.currentDate=e.date,this.renderView())},t.prototype.next=function(){var t=this.view,e=t.dateProfileGenerator.buildNext(t.get("dateProfile"));e.isValid&&(this.currentDate=e.date,this.renderView())},t.prototype.prevYear=function(){this.currentDate.add(-1,"years"),this.renderView()},t.prototype.nextYear=function(){this.currentDate.add(1,"years"),this.renderView()},t.prototype.today=function(){this.currentDate=this.getNow(),this.renderView()},t.prototype.gotoDate=function(t){this.currentDate=this.moment(t).stripZone(),this.renderView()},t.prototype.incrementDate=function(t){this.currentDate.add(o.duration(t)),this.renderView()},t.prototype.getDate=function(){return this.applyTimezone(this.currentDate)},t.prototype.pushLoading=function(){this.loadingLevel++||this.publiclyTrigger("loading",[!0,this.view])},t.prototype.popLoading=function(){--this.loadingLevel||this.publiclyTrigger("loading",[!1,this.view])},t.prototype.render=function(){this.contentEl?this.elementVisible()&&(this.calcSize(),this.updateViewSize()):this.initialRender()},t.prototype.initialRender=function(){var t=this,e=this.el;e.addClass("fc"),e.on("click.fc","a[data-goto]",function(e){var n=r(e.currentTarget),i=n.data("goto"),o=t.moment(i.date),a=i.type,l=t.view.opt("navLink"+s.capitaliseFirstLetter(a)+"Click");"function"==typeof l?l(o,e):("string"==typeof l&&(a=l),t.zoomTo(o,a))}),this.optionsManager.watch("settingTheme",["?theme","?themeSystem"],function(n){var i=I.getThemeSystemClass(n.themeSystem||n.theme),r=new i(t.optionsManager),o=r.getClass("widget");t.theme=r,o&&e.addClass(o)},function(){var n=t.theme.getClass("widget");t.theme=null,n&&e.removeClass(n)}),this.optionsManager.watch("settingBusinessHourGenerator",["?businessHours"],function(e){t.businessHourGenerator=new E.default(e.businessHours,t),t.view&&t.view.set("businessHourGenerator",t.businessHourGenerator)},function(){t.businessHourGenerator=null}),this.optionsManager.watch("applyingDirClasses",["?isRTL","?locale"],function(t){e.toggleClass("fc-ltr",!t.isRTL),e.toggleClass("fc-rtl",t.isRTL)}),this.contentEl=r("
    ").prependTo(e),this.initToolbars(),this.renderHeader(),this.renderFooter(),this.renderView(this.opt("defaultView")),this.opt("handleWindowResize")&&r(window).resize(this.windowResizeProxy=s.debounce(this.windowResize.bind(this),this.opt("windowResizeDelay")))},t.prototype.destroy=function(){this.view&&this.clearView(),this.toolbarsManager.proxyCall("removeElement"),this.contentEl.remove(),this.el.removeClass("fc fc-ltr fc-rtl"),this.optionsManager.unwatch("settingTheme"),this.optionsManager.unwatch("settingBusinessHourGenerator"),this.el.off(".fc"),this.windowResizeProxy&&(r(window).unbind("resize",this.windowResizeProxy),this.windowResizeProxy=null),u.default.unneeded()},t.prototype.elementVisible=function(){return this.el.is(":visible")},t.prototype.bindViewHandlers=function(t){var e=this;t.watch("titleForCalendar",["title"],function(n){t===e.view&&e.setToolbarsTitle(n.title)}),t.watch("dateProfileForCalendar",["dateProfile"],function(n){t===e.view&&(e.currentDate=n.dateProfile.date,e.updateToolbarButtons(n.dateProfile))})},t.prototype.unbindViewHandlers=function(t){t.unwatch("titleForCalendar"),t.unwatch("dateProfileForCalendar")},t.prototype.renderView=function(t){var e,n=this.view;this.freezeContentHeight(),n&&t&&n.type!==t&&this.clearView(),!this.view&&t&&(e=this.view=this.viewsByType[t]||(this.viewsByType[t]=this.instantiateView(t)),this.bindViewHandlers(e),e.startBatchRender(),e.setElement(r("
    ").appendTo(this.contentEl)),this.toolbarsManager.proxyCall("activateButton",t)),this.view&&(this.view.get("businessHourGenerator")!==this.businessHourGenerator&&this.view.set("businessHourGenerator",this.businessHourGenerator),this.view.setDate(this.currentDate),e&&e.stopBatchRender()),this.thawContentHeight()},t.prototype.clearView=function(){var t=this.view;this.toolbarsManager.proxyCall("deactivateButton",t.type),this.unbindViewHandlers(t),t.removeElement(),t.unsetDate(),this.view=null},t.prototype.reinitView=function(){var t=this.view,e=t.queryScroll();this.freezeContentHeight(),this.clearView(),this.calcSize(),this.renderView(t.type),this.view.applyScroll(e),this.thawContentHeight()},t.prototype.getSuggestedViewHeight=function(){return null==this.suggestedViewHeight&&this.calcSize(),this.suggestedViewHeight},t.prototype.isHeightAuto=function(){return"auto"===this.opt("contentHeight")||"auto"===this.opt("height")},t.prototype.updateViewSize=function(t){void 0===t&&(t=!1);var e,n=this.view;if(!this.ignoreUpdateViewSize&&n)return t&&(this.calcSize(),e=n.queryScroll()),this.ignoreUpdateViewSize++,n.updateSize(this.getSuggestedViewHeight(),this.isHeightAuto(),t),this.ignoreUpdateViewSize--,t&&n.applyScroll(e),!0},t.prototype.calcSize=function(){this.elementVisible()&&this._calcSize()},t.prototype._calcSize=function(){var t=this.opt("contentHeight"),e=this.opt("height");this.suggestedViewHeight="number"==typeof t?t:"function"==typeof t?t():"number"==typeof e?e-this.queryToolbarsHeight():"function"==typeof e?e()-this.queryToolbarsHeight():"parent"===e?this.el.parent().height()-this.queryToolbarsHeight():Math.round(this.contentEl.width()/Math.max(this.opt("aspectRatio"),.5))},t.prototype.windowResize=function(t){t.target===window&&this.view&&this.view.isDatesRendered&&this.updateViewSize(!0)&&this.publiclyTrigger("windowResize",[this.view])},t.prototype.freezeContentHeight=function(){this.freezeContentHeightDepth++||this.forceFreezeContentHeight()},t.prototype.forceFreezeContentHeight=function(){this.contentEl.css({width:"100%",height:this.contentEl.height(),overflow:"hidden"})},t.prototype.thawContentHeight=function(){this.freezeContentHeightDepth--,this.contentEl.css({width:"",height:"",overflow:""}),this.freezeContentHeightDepth&&this.forceFreezeContentHeight()},t.prototype.initToolbars=function(){this.header=new p.default(this,this.computeHeaderOptions()),this.footer=new p.default(this,this.computeFooterOptions()),this.toolbarsManager=new l.default([this.header,this.footer])},t.prototype.computeHeaderOptions=function(){return{extraClasses:"fc-header-toolbar",layout:this.opt("header")}},t.prototype.computeFooterOptions=function(){return{extraClasses:"fc-footer-toolbar",layout:this.opt("footer")}},t.prototype.renderHeader=function(){var t=this.header;t.setToolbarOptions(this.computeHeaderOptions()),t.render(),t.el&&this.el.prepend(t.el)},t.prototype.renderFooter=function(){var t=this.footer;t.setToolbarOptions(this.computeFooterOptions()),t.render(),t.el&&this.el.append(t.el)},t.prototype.setToolbarsTitle=function(t){this.toolbarsManager.proxyCall("updateTitle",t)},t.prototype.updateToolbarButtons=function(t){var e=this.getNow(),n=this.view,i=n.dateProfileGenerator.build(e),r=n.dateProfileGenerator.buildPrev(n.get("dateProfile")),o=n.dateProfileGenerator.buildNext(n.get("dateProfile"));this.toolbarsManager.proxyCall(i.isValid&&!t.currentUnzonedRange.containsDate(e)?"enableButton":"disableButton","today"),this.toolbarsManager.proxyCall(r.isValid?"enableButton":"disableButton","prev"),this.toolbarsManager.proxyCall(o.isValid?"enableButton":"disableButton","next")},t.prototype.queryToolbarsHeight=function(){return this.toolbarsManager.items.reduce(function(t,e){return t+(e.el?e.el.outerHeight(!0):0)},0)},t.prototype.select=function(t,e){this.view.select(this.buildSelectFootprint.apply(this,arguments))},t.prototype.unselect=function(){this.view&&this.view.unselect()},t.prototype.buildSelectFootprint=function(t,e){var n,i=this.moment(t).stripZone();return n=e?this.moment(e).stripZone():i.hasTime()?i.clone().add(this.defaultTimedEventDuration):i.clone().add(this.defaultAllDayEventDuration),new b.default(new m.default(i,n),!i.hasTime())},t.prototype.initMomentInternals=function(){var t=this;this.defaultAllDayEventDuration=o.duration(this.opt("defaultAllDayEventDuration")),this.defaultTimedEventDuration=o.duration(this.opt("defaultTimedEventDuration")),this.optionsManager.watch("buildingMomentLocale",["?locale","?monthNames","?monthNamesShort","?dayNames","?dayNamesShort","?firstDay","?weekNumberCalculation"],function(e){var n,i=e.weekNumberCalculation,r=e.firstDay;"iso"===i&&(i="ISO");var o=Object.create(v.getMomentLocaleData(e.locale));e.monthNames&&(o._months=e.monthNames),e.monthNamesShort&&(o._monthsShort=e.monthNamesShort),e.dayNames&&(o._weekdays=e.dayNames),e.dayNamesShort&&(o._weekdaysShort=e.dayNamesShort),null==r&&"ISO"===i&&(r=1),null!=r&&(n=Object.create(o._week),n.dow=r,o._week=n),"ISO"!==i&&"local"!==i&&"function"!=typeof i||(o._fullCalendar_weekCalc=i),t.localeData=o,t.currentDate&&t.localizeMoment(t.currentDate)})},t.prototype.moment=function(){for(var t=[],e=0;e864e5&&r.time(n-864e5)),new o.default(i,r)},t.prototype.buildRangeFromDuration=function(t,e,n,s){function a(){d=t.clone().startOf(h),c=d.clone().add(n),p=new o.default(d,c)}var l,u,d,c,p,h=this.opt("dateAlignment");return h||(l=this.opt("dateIncrement"),l?(u=i.duration(l),h=uo.getStart()&&(i=new a.default,i.setEndDelta(l),r=new s.default,r.setDateMutation(i),r)},e}(u.default);e.default=d},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(37),s=n(50),a=n(54),l=n(23),u=n(244),d=n(15),c=function(t){function e(e,n){var i=t.call(this,e)||this;return i.isDragging=!1,i.eventPointing=n,i}return i.__extends(e,t),e.prototype.end=function(){this.dragListener&&this.dragListener.endInteraction()},e.prototype.getSelectionDelay=function(){var t=this.opt("eventLongPressDelay");return null==t&&(t=this.opt("longPressDelay")),t},e.prototype.bindToEl=function(t){var e=this.component;e.bindSegHandlerToEl(t,"mousedown",this.handleMousedown.bind(this)),e.bindSegHandlerToEl(t,"touchstart",this.handleTouchStart.bind(this))},e.prototype.handleMousedown=function(t,e){!this.component.shouldIgnoreMouse()&&this.component.canStartDrag(t,e)&&this.buildDragListener(t).startInteraction(e,{distance:5})},e.prototype.handleTouchStart=function(t,e){var n=this.component,i={delay:this.view.isEventDefSelected(t.footprint.eventDef)?0:this.getSelectionDelay()};n.canStartDrag(t,e)?this.buildDragListener(t).startInteraction(e,i):n.canStartSelection(t,e)&&this.buildSelectListener(t).startInteraction(e,i)},e.prototype.buildSelectListener=function(t){var e=this,n=this.view,i=t.footprint.eventDef,r=t.footprint.eventInstance;if(this.dragListener)return this.dragListener;var o=this.dragListener=new a.default({dragStart:function(t){o.isTouch&&!n.isEventDefSelected(i)&&r&&n.selectEventInstance(r)},interactionEnd:function(t){e.dragListener=null}});return o},e.prototype.buildDragListener=function(t){var e,n,i,o=this,s=this.component,a=this.view,d=a.calendar,c=d.eventManager,p=t.el,h=t.footprint.eventDef,f=t.footprint.eventInstance;if(this.dragListener)return this.dragListener;var g=this.dragListener=new l.default(a,{scroll:this.opt("dragScroll"),subjectEl:p,subjectCenter:!0,interactionStart:function(i){t.component=s,e=!1,n=new u.default(t.el,{additionalClass:"fc-dragging",parentEl:a.el,opacity:g.isTouch?null:o.opt("dragOpacity"),revertDuration:o.opt("dragRevertDuration"),zIndex:2}),n.hide(),n.start(i)},dragStart:function(n){g.isTouch&&!a.isEventDefSelected(h)&&f&&a.selectEventInstance(f),e=!0,o.eventPointing.handleMouseout(t,n),o.segDragStart(t,n),a.hideEventsWithId(t.footprint.eventDef.id)},hitOver:function(e,l,u){var p,f,v,y=!0;t.hit&&(u=t.hit),p=u.component.getSafeHitFootprint(u),f=e.component.getSafeHitFootprint(e),p&&f?(i=o.computeEventDropMutation(p,f,h),i?(v=c.buildMutatedEventInstanceGroup(h.id,i),y=s.isEventInstanceGroupAllowed(v)):y=!1):y=!1,y||(i=null,r.disableCursor()),i&&a.renderDrag(s.eventRangesToEventFootprints(v.sliceRenderRanges(s.dateProfile.renderUnzonedRange,d)),t,g.isTouch)?n.hide():n.show(),l&&(i=null)},hitOut:function(){a.unrenderDrag(t),n.show(),i=null},hitDone:function(){r.enableCursor()},interactionEnd:function(r){delete t.component,n.stop(!i,function(){e&&(a.unrenderDrag(t),o.segDragStop(t,r)),a.showEventsWithId(t.footprint.eventDef.id),i&&a.reportEventDrop(f,i,p,r)}),o.dragListener=null}});return g},e.prototype.segDragStart=function(t,e){this.isDragging=!0,this.component.publiclyTrigger("eventDragStart",{context:t.el[0],args:[t.footprint.getEventLegacy(),e,{},this.view]})},e.prototype.segDragStop=function(t,e){this.isDragging=!1,this.component.publiclyTrigger("eventDragStop",{context:t.el[0],args:[t.footprint.getEventLegacy(),e,{},this.view]})},e.prototype.computeEventDropMutation=function(t,e,n){var i=new o.default;return i.setDateMutation(this.computeEventDateMutation(t,e)),i},e.prototype.computeEventDateMutation=function(t,e){var n,i,r=t.unzonedRange.getStart(),o=e.unzonedRange.getStart(),a=!1,l=!1,u=!1;return t.isAllDay!==e.isAllDay&&(a=!0,e.isAllDay?(u=!0,r.stripTime()):l=!0),n=this.component.diffDates(o,r),i=new s.default,i.clearEnd=a,i.forceTimed=l,i.forceAllDay=u,i.setDateDelta(n),i},e}(d.default);e.default=c},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(23),s=n(12),a=n(5),l=n(15),u=function(t){function e(e){var n=t.call(this,e)||this;return n.dragListener=n.buildDragListener(),n}return i.__extends(e,t),e.prototype.end=function(){this.dragListener.endInteraction()},e.prototype.getDelay=function(){var t=this.opt("selectLongPressDelay");return null==t&&(t=this.opt("longPressDelay")),t},e.prototype.bindToEl=function(t){var e=this,n=this.component,i=this.dragListener;n.bindDateHandlerToEl(t,"mousedown",function(t){e.opt("selectable")&&!n.shouldIgnoreMouse()&&i.startInteraction(t,{distance:e.opt("selectMinDistance")})}),n.bindDateHandlerToEl(t,"touchstart",function(t){e.opt("selectable")&&!n.shouldIgnoreTouch()&&i.startInteraction(t,{delay:e.getDelay()})}),r.preventSelection(t)},e.prototype.buildDragListener=function(){var t,e=this,n=this.component;return new o.default(n,{scroll:this.opt("dragScroll"),interactionStart:function(){t=null},dragStart:function(t){e.view.unselect(t)},hitOver:function(i,o,s){var a,l;s&&(a=n.getSafeHitFootprint(s),l=n.getSafeHitFootprint(i),t=a&&l?e.computeSelection(a,l):null,t?n.renderSelectionFootprint(t):!1===t&&r.disableCursor())},hitOut:function(){t=null,n.unrenderSelection()},hitDone:function(){r.enableCursor()},interactionEnd:function(n,i){!i&&t&&e.view.reportSelection(t,n)}})},e.prototype.computeSelection=function(t,e){var n=this.computeSelectionFootprint(t,e);return!(n&&!this.isSelectionFootprintAllowed(n))&&n},e.prototype.computeSelectionFootprint=function(t,e){var n=[t.unzonedRange.startMs,t.unzonedRange.endMs,e.unzonedRange.startMs,e.unzonedRange.endMs];return n.sort(r.compareNumbers),new s.default(new a.default(n[0],n[3]),t.isAllDay)},e.prototype.isSelectionFootprintAllowed=function(t){return this.component.dateProfile.validUnzonedRange.containsRange(t.unzonedRange)&&this.view.calendar.constraints.isSelectionFootprintAllowed(t)},e}(l.default);e.default=u},function(t,e,n){function i(t){var e,n=[],i=[];for(e=0;e').appendTo(t),this.el.find(".fc-body > tr > td").append(t),this.timeGrid.headContainerEl=this.el.find(".fc-head-container"),this.timeGrid.setElement(e),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight())},e.prototype.unrenderSkeleton=function(){this.timeGrid.removeElement(),this.dayGrid&&this.dayGrid.removeElement(),this.scroller.destroy()},e.prototype.renderSkeletonHtml=function(){var t=this.calendar.theme;return''+(this.opt("columnHeader")?'':"")+'
     
    '+(this.dayGrid?'

    ':"")+"
    "},e.prototype.axisStyleAttr=function(){return null!=this.axisWidth?'style="width:'+this.axisWidth+'px"':""},e.prototype.getNowIndicatorUnit=function(){return this.timeGrid.getNowIndicatorUnit()},e.prototype.updateSize=function(e,n,i){var r,o,s;if(t.prototype.updateSize.call(this,e,n,i),this.axisWidth=u.matchCellWidths(this.el.find(".fc-axis")),!this.timeGrid.colEls)return void(n||(o=this.computeScrollerHeight(e),this.scroller.setHeight(o)));var a=this.el.find(".fc-row:not(.fc-scroller *)");this.timeGrid.bottomRuleEl.hide(),this.scroller.clear(),u.uncompensateScroll(a),this.dayGrid&&(this.dayGrid.removeSegPopover(),r=this.opt("eventLimit"),r&&"number"!=typeof r&&(r=5),r&&this.dayGrid.limitRows(r)),n||(o=this.computeScrollerHeight(e),this.scroller.setHeight(o),s=this.scroller.getScrollbarWidths(),(s.left||s.right)&&(u.compensateScroll(a,s),o=this.computeScrollerHeight(e),this.scroller.setHeight(o)),this.scroller.lockOverflow(s),this.timeGrid.getTotalSlatHeight()"+e.buildGotoAnchorHtml({date:i,type:"week",forceOff:this.colCnt>1},u.htmlEscape(t))+""):'"},renderBgIntroHtml:function(){var t=this.view;return'"},renderIntroHtml:function(){return'"}},o={renderBgIntroHtml:function(){var t=this.view;return'"+t.getAllDayHtml()+""},renderIntroHtml:function(){return'"}}},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(0),s=n(4),a=n(40),l=n(56),u=n(60),d=n(55),c=n(53),p=n(5),h=n(12),f=n(246),g=n(247),v=n(248),y=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}],m=function(t){function e(e){var n=t.call(this,e)||this;return n.processOptions(),n}return i.__extends(e,t),e.prototype.componentFootprintToSegs=function(t){var e,n=this.sliceRangeByTimes(t.unzonedRange);for(e=0;e=0;e--)if(n=o.duration(y[e]),i=s.divideDurationByDuration(n,t),s.isInt(i)&&i>1)return n;return o.duration(t)},e.prototype.renderDates=function(t){this.dateProfile=t,this.updateDayTable(),this.renderSlats(),this.renderColumns()},e.prototype.unrenderDates=function(){this.unrenderColumns()},e.prototype.renderSkeleton=function(){var t=this.view.calendar.theme;this.el.html('
    '),this.bottomRuleEl=this.el.find("hr")},e.prototype.renderSlats=function(){var t=this.view.calendar.theme;this.slatContainerEl=this.el.find("> .fc-slats").html(''+this.renderSlatRowHtml()+"
    "),this.slatEls=this.slatContainerEl.find("tr"),this.slatCoordCache=new c.default({els:this.slatEls,isVertical:!0})},e.prototype.renderSlatRowHtml=function(){for(var t,e,n,i=this.view,r=i.calendar,a=r.theme,l=this.isRTL,u=this.dateProfile,d="",c=o.duration(+u.minTime),p=o.duration(0);c"+(e?""+s.htmlEscape(t.format(this.labelFormat))+"":"")+"",d+='"+(l?"":n)+''+(l?n:"")+"",c.add(this.slotDuration),p.add(this.slotDuration);return d},e.prototype.renderColumns=function(){var t=this.dateProfile,e=this.view.calendar.theme;this.dayRanges=this.dayDates.map(function(e){return new p.default(e.clone().add(t.minTime),e.clone().add(t.maxTime))}),this.headContainerEl&&this.headContainerEl.html(this.renderHeadHtml()),this.el.find("> .fc-bg").html(''+this.renderBgTrHtml(0)+"
    "),this.colEls=this.el.find(".fc-day, .fc-disabled-day"),this.colCoordCache=new c.default({els:this.colEls,isHorizontal:!0}),this.renderContentSkeleton()},e.prototype.unrenderColumns=function(){this.unrenderContentSkeleton()},e.prototype.renderContentSkeleton=function(){var t,e,n="";for(t=0;t
    ';e=this.contentSkeletonEl=r('
    '+n+"
    "),this.colContainerEls=e.find(".fc-content-col"),this.helperContainerEls=e.find(".fc-helper-container"),this.fgContainerEls=e.find(".fc-event-container:not(.fc-helper-container)"),this.bgContainerEls=e.find(".fc-bgevent-container"),this.highlightContainerEls=e.find(".fc-highlight-container"),this.businessContainerEls=e.find(".fc-business-container"),this.bookendCells(e.find("tr")),this.el.append(e)},e.prototype.unrenderContentSkeleton=function(){this.contentSkeletonEl&&(this.contentSkeletonEl.remove(),this.contentSkeletonEl=null,this.colContainerEls=null,this.helperContainerEls=null,this.fgContainerEls=null,this.bgContainerEls=null,this.highlightContainerEls=null,this.businessContainerEls=null)},e.prototype.groupSegsByCol=function(t){var e,n=[];for(e=0;e
    ').css("top",i).appendTo(this.colContainerEls.eq(n[e].col))[0]);n.length>0&&o.push(r('
    ').css("top",i).appendTo(this.el.find(".fc-content-skeleton"))[0]),this.nowIndicatorEls=r(o)}},e.prototype.unrenderNowIndicator=function(){this.nowIndicatorEls&&(this.nowIndicatorEls.remove(),this.nowIndicatorEls=null)},e.prototype.updateSize=function(e,n,i){t.prototype.updateSize.call(this,e,n,i),this.slatCoordCache.build(),i&&this.updateSegVerticals([].concat(this.eventRenderer.getSegs(),this.businessSegs||[]))},e.prototype.getTotalSlatHeight=function(){return this.slatContainerEl.outerHeight()},e.prototype.computeDateTop=function(t,e){return this.computeTimeTop(o.duration(t-e.clone().stripTime()))},e.prototype.computeTimeTop=function(t){var e,n,i=this.slatEls.length,r=this.dateProfile,o=(t-r.minTime)/this.slotDuration;return o=Math.max(0,o),o=Math.min(i,o),e=Math.floor(o),e=Math.min(e,i-1),n=o-e,this.slatCoordCache.getTopPosition(e)+this.slatCoordCache.getHeight(e)*n},e.prototype.updateSegVerticals=function(t){this.computeSegVerticals(t),this.assignSegVerticals(t)},e.prototype.computeSegVerticals=function(t){var e,n,i,r=this.opt("agendaEventMinHeight");for(e=0;e
    '+o.htmlEscape(this.opt("noEventsMessage"))+"
    ")},e.prototype.renderSegList=function(t){var e,n,i,o=this.groupSegsByDay(t),s=r('
    '),a=s.find("tbody");for(e=0;e'+(e?this.buildGotoAnchorHtml(t,{class:"fc-list-heading-main"},o.htmlEscape(t.format(e))):"")+(n?this.buildGotoAnchorHtml(t,{class:"fc-list-heading-alt"},o.htmlEscape(t.format(n))):"")+""},e}(a.default);e.default=c,c.prototype.eventRendererClass=u.default,c.prototype.eventPointingClass=d.default},,,,,,function(t,e,n){var i=n(3),r=n(16),o=n(4),s=n(220);n(10),n(47),n(256),n(257),n(260),n(261),n(262),n(263),i.fullCalendar=r,i.fn.fullCalendar=function(t){var e=Array.prototype.slice.call(arguments,1),n=this;return this.each(function(r,a){var l,u=i(a),d=u.data("fullCalendar");"string"==typeof t?"getCalendar"===t?r||(n=d):"destroy"===t?d&&(d.destroy(),u.removeData("fullCalendar")):d?i.isFunction(d[t])?(l=d[t].apply(d,e),r||(n=l),"destroy"===t&&u.removeData("fullCalendar")):o.warn("'"+t+"' is an unknown FullCalendar method."):o.warn("Attempting to call a FullCalendar method on an element with no calendar."):d||(d=new s.default(u,t),u.data("fullCalendar",d),d.render())}),n},t.exports=r},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(48),o=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.setElement=function(t){this.el=t,this.bindGlobalHandlers(),this.renderSkeleton(),this.set("isInDom",!0)},e.prototype.removeElement=function(){this.unset("isInDom"),this.unrenderSkeleton(),this.unbindGlobalHandlers(),this.el.remove()},e.prototype.bindGlobalHandlers=function(){},e.prototype.unbindGlobalHandlers=function(){},e.prototype.renderSkeleton=function(){},e.prototype.unrenderSkeleton=function(){},e}(r.default);e.default=o},function(t,e){Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(t){this.items=t||[]}return t.prototype.proxyCall=function(t){for(var e=[],n=1;n"),e.append(this.renderSection("left")).append(this.renderSection("right")).append(this.renderSection("center")).append('
    ')):this.removeElement()},t.prototype.removeElement=function(){this.el&&(this.el.remove(),this.el=null)},t.prototype.renderSection=function(t){var e=this,n=this.calendar,o=n.theme,s=n.optionsManager,a=n.viewSpecManager,l=i('
    '),u=this.toolbarOptions.layout[t],d=s.get("customButtons")||{},c=s.overrides.buttonText||{},p=s.get("buttonText")||{};return u&&i.each(u.split(" "),function(t,s){var u,h=i(),f=!0;i.each(s.split(","),function(t,s){var l,u,g,v,y,m,b,w,D;"title"===s?(h=h.add(i("

     

    ")),f=!1):((l=d[s])?(g=function(t){l.click&&l.click.call(w[0],t)},(v=o.getCustomButtonIconClass(l))||(v=o.getIconClass(s))||(y=l.text)):(u=a.getViewSpec(s))?(e.viewsWithButtons.push(s),g=function(){n.changeView(s)},(y=u.buttonTextOverride)||(v=o.getIconClass(s))||(y=u.buttonTextDefault)):n[s]&&(g=function(){n[s]()},(y=c[s])||(v=o.getIconClass(s))||(y=p[s])),g&&(b=["fc-"+s+"-button",o.getClass("button"),o.getClass("stateDefault")],y?(m=r.htmlEscape(y),D=""):v&&(m="",D=' aria-label="'+s+'"'),w=i('").click(function(t){w.hasClass(o.getClass("stateDisabled"))||(g(t),(w.hasClass(o.getClass("stateActive"))||w.hasClass(o.getClass("stateDisabled")))&&w.removeClass(o.getClass("stateHover")))}).mousedown(function(){w.not("."+o.getClass("stateActive")).not("."+o.getClass("stateDisabled")).addClass(o.getClass("stateDown"))}).mouseup(function(){w.removeClass(o.getClass("stateDown"))}).hover(function(){w.not("."+o.getClass("stateActive")).not("."+o.getClass("stateDisabled")).addClass(o.getClass("stateHover"))},function(){w.removeClass(o.getClass("stateHover")).removeClass(o.getClass("stateDown"))}),h=h.add(w)))}),f&&h.first().addClass(o.getClass("cornerLeft")).end().last().addClass(o.getClass("cornerRight")).end(),h.length>1?(u=i("
    "),f&&u.addClass(o.getClass("buttonGroup")),u.append(h),l.append(u)):l.append(h)}),l},t.prototype.updateTitle=function(t){this.el&&this.el.find("h2").text(t)},t.prototype.activateButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").addClass(this.calendar.theme.getClass("stateActive"))},t.prototype.deactivateButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").removeClass(this.calendar.theme.getClass("stateActive"))},t.prototype.disableButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").prop("disabled",!0).addClass(this.calendar.theme.getClass("stateDisabled"))},t.prototype.enableButton=function(t){this.el&&this.el.find(".fc-"+t+"-button").prop("disabled",!1).removeClass(this.calendar.theme.getClass("stateDisabled"))},t.prototype.getViewsWithButtons=function(){return this.viewsWithButtons},t}();e.default=o},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(4),s=n(32),a=n(31),l=n(48),u=function(t){function e(e,n){var i=t.call(this)||this;return i._calendar=e,i.overrides=r.extend({},n),i.dynamicOverrides={},i.compute(),i}return i.__extends(e,t),e.prototype.add=function(t){var e,n=0;this.recordOverrides(t);for(e in t)n++;if(1===n){if("height"===e||"contentHeight"===e||"aspectRatio"===e)return void this._calendar.updateViewSize(!0);if("defaultDate"===e)return;if("businessHours"===e)return;if(/^(event|select)(Overlap|Constraint|Allow)$/.test(e))return;if("timezone"===e)return void this._calendar.view.flash("initialEvents")}this._calendar.renderHeader(),this._calendar.renderFooter(),this._calendar.viewsByType={},this._calendar.reinitView()},e.prototype.compute=function(){var t,e,n,i,r;t=o.firstDefined(this.dynamicOverrides.locale,this.overrides.locale),e=a.localeOptionHash[t],e||(t=s.globalDefaults.locale,e=a.localeOptionHash[t]||{}),n=o.firstDefined(this.dynamicOverrides.isRTL,this.overrides.isRTL,e.isRTL,s.globalDefaults.isRTL),i=n?s.rtlDefaults:{},this.dirDefaults=i,this.localeDefaults=e,r=s.mergeOptions([s.globalDefaults,i,e,this.overrides,this.dynamicOverrides]),a.populateInstanceComputableOptions(r),this.reset(r)},e.prototype.recordOverrides=function(t){var e;for(e in t)this.dynamicOverrides[e]=t[e];this._calendar.viewSpecManager.clearCache(),this.compute()},e}(l.default);e.default=u},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(0),r=n(3),o=n(22),s=n(4),a=n(32),l=n(31),u=function(){function t(t,e){this.optionsManager=t,this._calendar=e,this.clearCache()}return t.prototype.clearCache=function(){this.viewSpecCache={}},t.prototype.getViewSpec=function(t){var e=this.viewSpecCache;return e[t]||(e[t]=this.buildViewSpec(t))},t.prototype.getUnitViewSpec=function(t){var e,n,i;if(-1!==r.inArray(t,s.unitsDesc))for(e=this._calendar.header.getViewsWithButtons(),r.each(o.viewHash,function(t){e.push(t)}),n=0;ne.top&&t.top
    '+(n?'
    '+u.htmlEscape(n)+"
    ":"")+(d.title?'
    '+u.htmlEscape(d.title)+"
    ":"")+'
    '+(h?'
    ':"")+""},e.prototype.updateFgSegCoords=function(t){this.timeGrid.computeSegVerticals(t),this.computeFgSegHorizontals(t),this.timeGrid.assignSegVerticals(t),this.assignFgSegHorizontals(t)},e.prototype.computeFgSegHorizontals=function(t){var e,n,s;if(this.sortEventSegs(t),e=i(t),r(e),n=e[0]){for(s=0;s').addClass(e.className||"").css({top:0,left:0}).append(e.content).appendTo(e.parentEl),this.el.on("click",".fc-close",function(){t.hide()}),e.autoHide&&this.listenTo(i(document),"mousedown",this.documentMousedown)},t.prototype.documentMousedown=function(t){this.el&&!i(t.target).closest(this.el).length&&this.hide()},t.prototype.removeElement=function(){this.hide(),this.el&&(this.el.remove(),this.el=null),this.stopListeningTo(i(document),"mousedown")},t.prototype.position=function(){var t,e,n,o,s,a=this.options,l=this.el.offsetParent().offset(),u=this.el.outerWidth(),d=this.el.outerHeight(),c=i(window),p=r.getScrollParent(this.el);o=a.top||0,s=void 0!==a.left?a.left:void 0!==a.right?a.right-u:0,p.is(window)||p.is(document)?(p=c,t=0,e=0):(n=p.offset(),t=n.top,e=n.left),t+=c.scrollTop(),e+=c.scrollLeft(),!1!==a.viewportConstrain&&(o=Math.min(o,t+p.outerHeight()-d-this.margin),o=Math.max(o,t+this.margin),s=Math.min(s,e+p.outerWidth()-u-this.margin),s=Math.max(s,e+this.margin)),this.el.css({top:o-l.top,left:s-l.left})},t.prototype.trigger=function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))},t}();e.default=s,o.default.mixInto(s)},function(t,e,n){function i(t,e){var n,i;for(n=0;n=t.leftCol)return!0;return!1}function r(t,e){return t.leftCol-e.leftCol}Object.defineProperty(e,"__esModule",{value:!0});var o=n(2),s=n(3),a=n(4),l=n(42),u=function(t){function e(e,n){var i=t.call(this,e,n)||this;return i.dayGrid=e,i}return o.__extends(e,t),e.prototype.renderBgRanges=function(e){e=s.grep(e,function(t){return t.eventDef.isAllDay()}),t.prototype.renderBgRanges.call(this,e)},e.prototype.renderFgSegs=function(t){var e=this.rowStructs=this.renderSegRows(t);this.dayGrid.rowEls.each(function(t,n){s(n).find(".fc-content-skeleton > table").append(e[t].tbodyEl)})},e.prototype.unrenderFgSegs=function(){for(var t,e=this.rowStructs||[];t=e.pop();)t.tbodyEl.remove();this.rowStructs=null},e.prototype.renderSegRows=function(t){var e,n,i=[];for(e=this.groupSegRows(t),n=0;n"),a.append(d)),v[i][o]=d,y[i][o]=d,o++}var i,r,o,a,l,u,d,c=this.dayGrid.colCnt,p=this.buildSegLevels(e),h=Math.max(1,p.length),f=s(""),g=[],v=[],y=[];for(i=0;i"),g.push([]),v.push([]),y.push([]),r)for(l=0;l').append(u.el),u.leftCol!==u.rightCol?d.attr("colspan",u.rightCol-u.leftCol+1):y[i][o]=d;o<=u.rightCol;)v[i][o]=d,g[i][o]=u,o++;a.append(d)}n(c),this.dayGrid.bookendCells(a),f.append(a)}return{row:t,tbodyEl:f,cellMatrix:v,segMatrix:g,segLevels:p,segs:e}},e.prototype.buildSegLevels=function(t){var e,n,o,s=[];for(this.sortEventSegs(t),e=0;e'+a.htmlEscape(n)+""),i=''+(a.htmlEscape(o.title||"")||" ")+"",'
    '+(this.dayGrid.isRTL?i+" "+h:h+" "+i)+"
    "+(u?'
    ':"")+(d?'
    ':"")+""},e}(l.default);e.default=u},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(58),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.renderSegs=function(t,e){var n,i=[];return n=this.eventRenderer.renderSegRows(t),this.component.rowEls.each(function(t,o){var s,a,l=r(o),u=r('
    ');e&&e.row===t?a=e.el.position().top:(s=l.find(".fc-content-skeleton tbody"),s.length||(s=l.find(".fc-content-skeleton table")),a=s.position().top),u.css("top",a).find("table").append(n[t].tbodyEl),l.append(u),i.push(u[0])}),r(i)},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(57),s=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.fillSegTag="td",e}return i.__extends(e,t),e.prototype.attachSegEls=function(t,e){var n,i,r,o=[];for(n=0;n
    '),o=i.find("tr"),a>0&&o.append(''),o.append(e.el.attr("colspan",l-a)),l'),this.component.bookendCells(o),i},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(228),o=n(5),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.buildRenderRange=function(e,n,i){var r,s=t.prototype.buildRenderRange.call(this,e,n,i),a=this.msToUtcMoment(s.startMs,i),l=this.msToUtcMoment(s.endMs,i);return this.opt("fixedWeekCount")&&(r=Math.ceil(l.diff(a,"weeks",!0)),l.add(6-r,"weeks")),new o.default(a,l)},e}(r.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(4),o=n(42),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.renderFgSegs=function(t){t.length?this.component.renderSegList(t):this.component.renderEmptyMessage()},e.prototype.fgSegHtml=function(t){var e,n=this.view,i=n.calendar,o=i.theme,s=t.footprint,a=s.eventDef,l=s.componentFootprint,u=a.url,d=["fc-list-item"].concat(this.getClasses(a)),c=this.getBgColor(a);return e=l.isAllDay?n.getAllDayHtml():n.isMultiDayRange(l.unzonedRange)?t.isStart||t.isEnd?r.htmlEscape(this._getTimeText(i.msToMoment(t.startMs),i.msToMoment(t.endMs),l.isAllDay)):n.getAllDayHtml():r.htmlEscape(this.getTimeText(s)),u&&d.push("fc-has-url"),''+(this.displayEventTime?''+(e||"")+"":"")+'"+r.htmlEscape(a.title||"")+""},e.prototype.computeEventTimeFormat=function(){return this.opt("mediumTimeFormat")},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(3),o=n(59),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e.prototype.handleClick=function(e,n){var i;t.prototype.handleClick.call(this,e,n),r(n.target).closest("a[href]").length||(i=e.footprint.eventDef.url)&&!n.isDefaultPrevented()&&(window.location.href=i)},e}(o.default);e.default=s},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(38),r=n(52),o=n(215),s=n(216);i.default.registerClass(r.default),i.default.registerClass(o.default),i.default.registerClass(s.default)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(51),r=n(213),o=n(214),s=n(258),a=n(259);i.defineThemeSystem("standard",r.default),i.defineThemeSystem("jquery-ui",o.default),i.defineThemeSystem("bootstrap3",s.default),i.defineThemeSystem("bootstrap4",a.default)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(19),o=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e}(r.default);e.default=o,o.prototype.classes={widget:"fc-bootstrap3",tableGrid:"table-bordered",tableList:"table",tableListHeading:"active",buttonGroup:"btn-group",button:"btn btn-default",stateActive:"active",stateDisabled:"disabled",today:"alert alert-info",popover:"panel panel-default",popoverHeader:"panel-heading",popoverContent:"panel-body",headerRow:"panel-default",dayRow:"panel-default",listView:"panel panel-default"},o.prototype.baseIconClass="glyphicon",o.prototype.iconClasses={close:"glyphicon-remove",prev:"glyphicon-chevron-left",next:"glyphicon-chevron-right",prevYear:"glyphicon-backward",nextYear:"glyphicon-forward"},o.prototype.iconOverrideOption="bootstrapGlyphicons",o.prototype.iconOverrideCustomButtonOption="bootstrapGlyphicon",o.prototype.iconOverridePrefix="glyphicon-"},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(2),r=n(19),o=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i.__extends(e,t),e}(r.default);e.default=o,o.prototype.classes={widget:"fc-bootstrap4",tableGrid:"table-bordered",tableList:"table",tableListHeading:"table-active",buttonGroup:"btn-group",button:"btn btn-primary",stateActive:"active",stateDisabled:"disabled",today:"alert alert-info",popover:"card card-primary",popoverHeader:"card-header",popoverContent:"card-body",headerRow:"table-bordered",dayRow:"table-bordered",listView:"card card-primary"},o.prototype.baseIconClass="fa",o.prototype.iconClasses={close:"fa-times",prev:"fa-chevron-left",next:"fa-chevron-right",prevYear:"fa-angle-double-left",nextYear:"fa-angle-double-right"},o.prototype.iconOverrideOption="bootstrapFontAwesome",o.prototype.iconOverrideCustomButtonOption="bootstrapFontAwesome",o.prototype.iconOverridePrefix="fa-"},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),r=n(62),o=n(229);i.defineView("basic",{class:r.default}),i.defineView("basicDay",{type:"basic",duration:{days:1}}),i.defineView("basicWeek",{type:"basic",duration:{weeks:1}}),i.defineView("month",{class:o.default,duration:{months:1},defaults:{fixedWeekCount:!0}})},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),r=n(226);i.defineView("agenda",{class:r.default,defaults:{allDaySlot:!0,slotDuration:"00:30:00",slotEventOverlap:!0}}),i.defineView("agendaDay",{type:"agenda",duration:{days:1}}),i.defineView("agendaWeek",{type:"agenda",duration:{weeks:1}})},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var i=n(22),r=n(230);i.defineView("list",{class:r.default,buttonTextKey:"list",defaults:{buttonText:"list",listDayFormat:"LL",noEventsMessage:"No events to display"}}),i.defineView("listDay",{type:"list",duration:{days:1},defaults:{listDayFormat:"dddd"}}),i.defineView("listWeek",{type:"list",duration:{weeks:1},defaults:{listDayFormat:"dddd",listDayAltFormat:"LL"}}),i.defineView("listMonth",{type:"list",duration:{month:1},defaults:{listDayAltFormat:"dddd"}}),i.defineView("listYear",{type:"list",duration:{year:1},defaults:{listDayAltFormat:"dddd"}})},function(t,e){Object.defineProperty(e,"__esModule",{value:!0})}])}); \ No newline at end of file diff --git a/public/lib/fc/fullcalendar.print.css b/public/lib/fc/fullcalendar.print.css new file mode 100644 index 0000000000..fb858cd790 --- /dev/null +++ b/public/lib/fc/fullcalendar.print.css @@ -0,0 +1,176 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +/*! + * FullCalendar v3.9.0 Print Stylesheet + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */ +/* + * Include this stylesheet on your page to get a more printer-friendly calendar. + * When including this stylesheet, use the media='print' attribute of the tag. + * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. + */ +.fc { + max-width: 100% !important; } + +/* Global Event Restyling +--------------------------------------------------------------------------------------------------*/ +.fc-event { + background: #fff !important; + color: #000 !important; + page-break-inside: avoid; } + +.fc-event .fc-resizer { + display: none; } + +/* Table & Day-Row Restyling +--------------------------------------------------------------------------------------------------*/ +.fc th, +.fc td, +.fc hr, +.fc thead, +.fc tbody, +.fc-row { + border-color: #ccc !important; + background: #fff !important; } + +/* kill the overlaid, absolutely-positioned components */ +/* common... */ +.fc-bg, +.fc-bgevent-skeleton, +.fc-highlight-skeleton, +.fc-helper-skeleton, +.fc-bgevent-container, +.fc-business-container, +.fc-highlight-container, +.fc-helper-container { + display: none; } + +/* don't force a min-height on rows (for DayGrid) */ +.fc tbody .fc-row { + height: auto !important; + /* undo height that JS set in distributeHeight */ + min-height: 0 !important; + /* undo the min-height from each view's specific stylesheet */ } + +.fc tbody .fc-row .fc-content-skeleton { + position: static; + /* undo .fc-rigid */ + padding-bottom: 0 !important; + /* use a more border-friendly method for this... */ } + +.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { + /* only works in newer browsers */ + padding-bottom: 1em; + /* ...gives space within the skeleton. also ensures min height in a way */ } + +.fc tbody .fc-row .fc-content-skeleton table { + /* provides a min-height for the row, but only effective for IE, which exaggerates this value, + making it look more like 3em. for other browers, it will already be this tall */ + height: 1em; } + +/* Undo month-view event limiting. Display all events and hide the "more" links +--------------------------------------------------------------------------------------------------*/ +.fc-more-cell, +.fc-more { + display: none !important; } + +.fc tr.fc-limited { + display: table-row !important; } + +.fc td.fc-limited { + display: table-cell !important; } + +.fc-popover { + display: none; + /* never display the "more.." popover in print mode */ } + +/* TimeGrid Restyling +--------------------------------------------------------------------------------------------------*/ +/* undo the min-height 100% trick used to fill the container's height */ +.fc-time-grid { + min-height: 0 !important; } + +/* don't display the side axis at all ("all-day" and time cells) */ +.fc-agenda-view .fc-axis { + display: none; } + +/* don't display the horizontal lines */ +.fc-slats, +.fc-time-grid hr { + /* this hr is used when height is underused and needs to be filled */ + display: none !important; + /* important overrides inline declaration */ } + +/* let the container that holds the events be naturally positioned and create real height */ +.fc-time-grid .fc-content-skeleton { + position: static; } + +/* in case there are no events, we still want some height */ +.fc-time-grid .fc-content-skeleton table { + height: 4em; } + +/* kill the horizontal spacing made by the event container. event margins will be done below */ +.fc-time-grid .fc-event-container { + margin: 0 !important; } + +/* TimeGrid *Event* Restyling +--------------------------------------------------------------------------------------------------*/ +/* naturally position events, vertically stacking them */ +.fc-time-grid .fc-event { + position: static !important; + margin: 3px 2px !important; } + +/* for events that continue to a future day, give the bottom border back */ +.fc-time-grid .fc-event.fc-not-end { + border-bottom-width: 1px !important; } + +/* indicate the event continues via "..." text */ +.fc-time-grid .fc-event.fc-not-end:after { + content: "..."; } + +/* for events that are continuations from previous days, give the top border back */ +.fc-time-grid .fc-event.fc-not-start { + border-top-width: 1px !important; } + +/* indicate the event is a continuation via "..." text */ +.fc-time-grid .fc-event.fc-not-start:before { + content: "..."; } + +/* time */ +/* undo a previous declaration and let the time text span to a second line */ +.fc-time-grid .fc-event .fc-time { + white-space: normal !important; } + +/* hide the the time that is normally displayed... */ +.fc-time-grid .fc-event .fc-time span { + display: none; } + +/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ +.fc-time-grid .fc-event .fc-time:after { + content: attr(data-full); } + +/* Vertical Scroller & Containers +--------------------------------------------------------------------------------------------------*/ +/* kill the scrollbars and allow natural height */ +.fc-scroller, +.fc-day-grid-container, +.fc-time-grid-container { + /* */ + overflow: visible !important; + height: auto !important; } + +/* kill the horizontal border/padding used to compensate for scrollbars */ +.fc-row { + border: 0 !important; + margin: 0 !important; } + +/* Button Controls +--------------------------------------------------------------------------------------------------*/ +.fc-button-group, +.fc button { + display: none; + /* don't display any button-related controls */ } diff --git a/public/lib/fc/fullcalendar.print.min.css b/public/lib/fc/fullcalendar.print.min.css new file mode 100644 index 0000000000..59a405c0a4 --- /dev/null +++ b/public/lib/fc/fullcalendar.print.min.css @@ -0,0 +1,9 @@ +/*! + * FullCalendar v3.9.0 + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + *//*! + * FullCalendar v3.9.0 Print Stylesheet + * Docs & License: https://fullcalendar.io/ + * (c) 2018 Adam Shaw + */.fc-bg,.fc-bgevent-container,.fc-bgevent-skeleton,.fc-business-container,.fc-event .fc-resizer,.fc-helper-container,.fc-helper-skeleton,.fc-highlight-container,.fc-highlight-skeleton{display:none}.fc tbody .fc-row,.fc-time-grid{min-height:0!important}.fc-time-grid .fc-event.fc-not-end:after,.fc-time-grid .fc-event.fc-not-start:before{content:"..."}.fc{max-width:100%!important}.fc-event{background:#fff!important;color:#000!important;page-break-inside:avoid}.fc hr,.fc tbody,.fc td,.fc th,.fc thead,.fc-row{border-color:#ccc!important;background:#fff!important}.fc tbody .fc-row{height:auto!important}.fc tbody .fc-row .fc-content-skeleton{position:static;padding-bottom:0!important}.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td{padding-bottom:1em}.fc tbody .fc-row .fc-content-skeleton table{height:1em}.fc-more,.fc-more-cell{display:none!important}.fc tr.fc-limited{display:table-row!important}.fc td.fc-limited{display:table-cell!important}.fc-agenda-view .fc-axis,.fc-popover{display:none}.fc-slats,.fc-time-grid hr{display:none!important}.fc button,.fc-button-group,.fc-time-grid .fc-event .fc-time span{display:none}.fc-time-grid .fc-content-skeleton{position:static}.fc-time-grid .fc-content-skeleton table{height:4em}.fc-time-grid .fc-event-container{margin:0!important}.fc-time-grid .fc-event{position:static!important;margin:3px 2px!important}.fc-time-grid .fc-event.fc-not-end{border-bottom-width:1px!important}.fc-time-grid .fc-event.fc-not-start{border-top-width:1px!important}.fc-time-grid .fc-event .fc-time{white-space:normal!important}.fc-time-grid .fc-event .fc-time:after{content:attr(data-full)}.fc-day-grid-container,.fc-scroller,.fc-time-grid-container{overflow:visible!important;height:auto!important}.fc-row{border:0!important;margin:0!important} \ No newline at end of file diff --git a/resources/views/recurring/calendar.twig b/resources/views/recurring/calendar.twig new file mode 100644 index 0000000000..2b3446b3fb --- /dev/null +++ b/resources/views/recurring/calendar.twig @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 70b1a034e2..08b5e35fac 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -134,19 +134,19 @@ {# row with submit stuff. #}
    -
    -
    -

    {{ 'options'|_ }}

    +
    +
    +

    {{ 'options'|_ }}

    +
    +
    + {{ ExpandedForm.optionsList('create','recurrence') }} +
    +
    -
    - {{ ExpandedForm.optionsList('create','recurrence') }} -
    - -
    {# @@ -164,15 +164,41 @@
    #} + + {# calendar modal #} + + + + + {% endblock %} {% block scripts %} + {% endblock %} @@ -181,4 +207,5 @@ + {% endblock %} diff --git a/routes/web.php b/routes/web.php index fb991d829c..176b037608 100755 --- a/routes/web.php +++ b/routes/web.php @@ -618,7 +618,7 @@ Route::group( Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']); Route::get('suggest', ['uses' => 'IndexController@suggest', 'as' => 'suggest']); - Route::get('calendar', ['uses' => 'IndexController@calendar', 'as' => 'calendar']); + Route::get('events', ['uses' => 'IndexController@events', 'as' => 'events']); Route::get('show/{recurrence}', ['uses' => 'IndexController@show', 'as' => 'show']); Route::get('create', ['uses' => 'CreateController@create', 'as' => 'create']); Route::get('edit/{recurrence}', ['uses' => 'EditController@edit', 'as' => 'edit']); From c19a700662fcdc14af07fe8fd6abaf56b5293064 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 12 Jun 2018 21:41:58 +0200 Subject: [PATCH 020/134] Set demo user back to English at login. --- app/Handlers/Events/UserEventHandler.php | 21 +++++++++++++++++++++ app/Providers/EventServiceProvider.php | 1 + 2 files changed, 22 insertions(+) diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index e953e039d1..c0b6acaa5d 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -108,6 +108,27 @@ class UserEventHandler return true; } + /** + * @param Login $event + * + * @return bool + */ + function demoUserBackToEnglish(Login $event): bool + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + + /** @var User $user */ + $user = $event->user; + if ($repository->hasRole($user, 'demo')) { + // set user back to English. + app('preferences')->setForUser($user, 'language', 'en_US'); + app('preferences')->mark(); + } + + return true; + } + /** * @param UserChangedEmail $event * diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 352e357d22..9e0a82dcc5 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -64,6 +64,7 @@ class EventServiceProvider extends ServiceProvider // is a User related event. Login::class => [ 'FireflyIII\Handlers\Events\UserEventHandler@checkSingleUserIsAdmin', + 'FireflyIII\Handlers\Events\UserEventHandler@demoUserBackToEnglish', ], RequestedVersionCheckStatus::class => [ From 281de63e0d3737eec5c76eea8a479b27591870a9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 12 Jun 2018 21:44:03 +0200 Subject: [PATCH 021/134] Spelcheck plz [skip ci] --- resources/views/recurring/create.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 08b5e35fac..b03247764a 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -198,7 +198,7 @@ {% endblock %} From f4b66b980bd54add06e77122e93a7b3c02e538db Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 13 Jun 2018 08:22:10 +0200 Subject: [PATCH 022/134] Prevent index error --- app/Http/Requests/SplitJournalFormRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index 207754df5b..bc989b5f7a 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -91,7 +91,7 @@ class SplitJournalFormRequest extends Request 'currency_id' => $this->integer('journal_currency_id'), 'currency_code' => null, 'description' => $transaction['transaction_description'] ?? '', - 'amount' => $transaction['amount'], + 'amount' => $transaction['amount'] ?? '', 'budget_id' => (int)($transaction['budget_id'] ?? 0.0), 'budget_name' => null, 'category_id' => null, From 477a3c7eb2e105a1b93c511dd76d18f0a24e79bc Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 13 Jun 2018 19:03:18 +0200 Subject: [PATCH 023/134] Fix various bugs in the import routine, discovered by Doug. --- app/Factory/TransactionFactory.php | 11 +++++++++++ app/Helpers/Collector/JournalCollector.php | 8 ++++++++ app/Helpers/Collector/JournalCollectorInterface.php | 7 +++++++ app/Import/Storage/ImportArrayStorage.php | 11 +++++++++-- app/Repositories/Journal/JournalRepository.php | 4 ++-- .../Journal/JournalRepositoryInterface.php | 3 ++- .../Internal/Support/TransactionServiceTrait.php | 1 + app/Support/Import/Placeholder/ImportTransaction.php | 4 +++- .../Import/Routine/File/ImportableConverter.php | 5 ++++- .../Import/Routine/File/MappedValuesValidator.php | 3 +++ 10 files changed, 50 insertions(+), 7 deletions(-) diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index d9199b0ab1..bb5baf3cbe 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -26,6 +26,7 @@ namespace FireflyIII\Factory; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -105,6 +106,16 @@ class TransactionFactory $destinationType = $this->accountType($journal, 'destination'); Log::debug(sprintf('Expect source destination to be of type %s', $destinationType)); $destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']); + + Log::debug(sprintf('Source type is "%s", destination type is "%s"', $sourceType, $destinationType)); + // throw big fat error when source type === dest type + if ($sourceAccount->accountType->type === $destinationAccount->accountType->type) { + throw new FireflyException(sprintf('Source and destination account cannot be both of the type "%s"', $destinationAccount->accountType->type)); + } + if ($sourceAccount->accountType->type !== AccountType::ASSET && $destinationAccount->accountType->type !== AccountType::ASSET) { + throw new FireflyException('At least one of the accounts must be an asset account.'); + } + // first make a "negative" (source) transaction based on the data in the array. $source = $this->create( [ diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index b2eb90dc1a..a68b4cbec1 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -768,6 +768,14 @@ class JournalCollector implements JournalCollectorInterface return $this; } + /** + * @return EloquentBuilder + */ + public function getQuery(): EloquentBuilder + { + return $this->query; + } + /** * @param Collection $set * diff --git a/app/Helpers/Collector/JournalCollectorInterface.php b/app/Helpers/Collector/JournalCollectorInterface.php index b710326175..b9c29ff4bf 100644 --- a/app/Helpers/Collector/JournalCollectorInterface.php +++ b/app/Helpers/Collector/JournalCollectorInterface.php @@ -27,6 +27,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\User; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -35,6 +36,7 @@ use Illuminate\Support\Collection; */ interface JournalCollectorInterface { + /** * @param string $filter * @@ -78,6 +80,11 @@ interface JournalCollectorInterface */ public function getPaginatedJournals(): LengthAwarePaginator; + /** + * @return EloquentBuilder + */ + public function getQuery(): EloquentBuilder; + /** * @return JournalCollectorInterface */ diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index dde6971a8e..c7edae2e6f 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -303,7 +303,7 @@ class ImportArrayStorage 'existing' => $existingId, 'description' => $transaction['description'] ?? '', 'amount' => $transaction['transactions'][0]['amount'] ?? 0, - 'date' => isset($transaction['date']) ? $transaction['date'] : '', + 'date' => $transaction['date'] ?? '', ] ); @@ -413,7 +413,14 @@ class ImportArrayStorage $store['date'] = Carbon::createFromFormat('Y-m-d', $store['date']); $store['description'] = $store['description'] === '' ? '(empty description)' : $store['description']; // store the journal. - $journal = $this->journalRepos->store($store); + try { + $journal = $this->journalRepos->store($store); + } catch(FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + $this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage())); + continue; + } Log::debug(sprintf('Stored as journal #%d', $journal->id)); $collection->push($journal); } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 6f71187a31..4dc900bb95 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -24,6 +24,7 @@ namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Factory\TransactionJournalMetaFactory; use FireflyIII\Models\Account; @@ -738,8 +739,7 @@ class JournalRepository implements JournalRepositoryInterface * * @return TransactionJournal * - * @throws \FireflyIII\Exceptions\FireflyException - * @throws \FireflyIII\Exceptions\FireflyException + * @throws FireflyException */ public function store(array $data): TransactionJournal { diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 1c55997a76..dc6bfe9b26 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Note; use FireflyIII\Models\Transaction; @@ -325,7 +326,7 @@ interface JournalRepositoryInterface /** * @param array $data - * + * @throws FireflyException * @return TransactionJournal */ public function store(array $data): TransactionJournal; diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php index 06fe1fe99c..d0cb0a3f8f 100644 --- a/app/Services/Internal/Support/TransactionServiceTrait.php +++ b/app/Services/Internal/Support/TransactionServiceTrait.php @@ -190,6 +190,7 @@ trait TransactionServiceTrait */ protected function findCategory(?int $categoryId, ?string $categoryName): ?Category { + Log::debug(sprintf('Going to find or create category #%d, with name "%s"', $categoryId, $categoryName)); /** @var CategoryFactory $factory */ $factory = app(CategoryFactory::class); $factory->setUser($this->user); diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php index a9ce98af63..bdda187930 100644 --- a/app/Support/Import/Placeholder/ImportTransaction.php +++ b/app/Support/Import/Placeholder/ImportTransaction.php @@ -179,7 +179,9 @@ class ImportTransaction $this->budgetName = $columnValue->getValue(); break; case 'category-id': - $this->categoryId = $this->getMappedValue($columnValue); + $value = $this->getMappedValue($columnValue); + Log::debug(sprintf('Set category ID to %d in ImportTransaction object', $value)); + $this->categoryId = $value; break; case 'category-name': $this->categoryName = $columnValue->getValue(); diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 5084f24743..73c3963354 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -279,11 +279,14 @@ class ImportableConverter */ private function verifyObjectId(string $key, int $objectId): ?int { + if (isset($this->mappedValues[$key]) && \in_array($objectId, $this->mappedValues[$key], true)) { + Log::debug(sprintf('verifyObjectId(%s, %d) is valid!',$key, $objectId)); return $objectId; } - return null; + Log::debug(sprintf('verifyObjectId(%s, %d) is NOT in the list, but it could still be valid.',$key, $objectId)); + return $objectId; } diff --git a/app/Support/Import/Routine/File/MappedValuesValidator.php b/app/Support/Import/Routine/File/MappedValuesValidator.php index 8526b2b1f7..e9229a275a 100644 --- a/app/Support/Import/Routine/File/MappedValuesValidator.php +++ b/app/Support/Import/Routine/File/MappedValuesValidator.php @@ -87,6 +87,7 @@ class MappedValuesValidator $return = []; Log::debug('Now in validateMappedValues()'); foreach ($mappings as $role => $values) { + Log::debug(sprintf('Now at role "%s"', $role)); $values = array_unique($values); if (\count($values) > 0) { switch ($role) { @@ -115,9 +116,11 @@ class MappedValuesValidator $return[$role] = $valid; break; case 'category-id': + Log::debug('Going to validate these category ids: ', $values); $set = $this->catRepos->getByIds($values); $valid = $set->pluck('id')->toArray(); $return[$role] = $valid; + Log::debug('Valid category IDs are: ', $valid); break; } } From 955cde3ed9105528fb6675eda4e12764b5f4c73e Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 14 Jun 2018 19:52:20 +0200 Subject: [PATCH 024/134] Don't report authentication exceptions. --- app/Exceptions/Handler.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 59b59b8bdd..6a72b8ab29 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -133,18 +133,13 @@ class Handler extends ExceptionHandler if ( // if the user wants us to mail: $doMailError === true - && (( - // and if is one of these error instances - $exception instanceof FireflyException - || $exception instanceof ErrorException - || $exception instanceof OAuthServerException + && ( + // and if is one of these error instances + $exception instanceof FireflyException + || $exception instanceof ErrorException + || $exception instanceof OAuthServerException - ) - || ( - // or this one, but it's a JSON exception. - $exception instanceof AuthenticationException - && Request::expectsJson() === true - )) + ) ) { // then, send email $userData = [ From 181c23b07c2a44c1896eae5d10f102e3e0b77f85 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 15 Jun 2018 22:06:33 +0200 Subject: [PATCH 025/134] Various updated code for recurring transactions. --- .deploy/docker/entrypoint.sh | 4 +- app/Http/Controllers/DebugController.php | 108 +++++++++++++++++- app/Http/Controllers/HomeController.php | 97 ---------------- .../Controllers/Recurring/IndexController.php | 53 ++++++++- .../Recurring/RecurringRepository.php | 2 +- .../RecurringRepositoryInterface.php | 2 +- app/Transformers/RecurrenceTransformer.php | 2 +- public/js/ff/recurring/create.js | 16 ++- resources/views/recurring/calendar.twig | 16 --- routes/web.php | 8 +- .../Controllers/DebugControllerTest.php | 58 ++++++++++ .../Controllers/HomeControllerTest.php | 54 --------- 12 files changed, 234 insertions(+), 186 deletions(-) delete mode 100644 resources/views/recurring/calendar.twig diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 17d62270c3..d95e3c4626 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -1,8 +1,8 @@ #!/bin/bash # make sure we own the volumes: -chown -R www-data:www-data -R $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/cache -chmod -R 775 $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/cache +chown -R www-data:www-data -R $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/framework/cache +chmod -R 775 $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/upload $FIREFLY_PATH/storage/logs $FIREFLY_PATH/storage/framework/cache # remove any lingering files that may break upgrades: rm -f $FIREFLY_PATH/storage/logs/laravel.log diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 3b0a444d23..a5e7b81036 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -23,13 +23,18 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; +use Artisan; use Carbon\Carbon; use DB; use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Middleware\IsDemoUser; use Illuminate\Http\Request; +use Illuminate\Routing\Route; use Log; use Monolog\Handler\RotatingFileHandler; +use Preferences; +use Route as RouteFacade; /** * Class DebugController @@ -45,6 +50,52 @@ class DebugController extends Controller $this->middleware(IsDemoUser::class); } + /** + * @throws FireflyException + */ + public function displayError() + { + Log::debug('This is a test message at the DEBUG level.'); + Log::info('This is a test message at the INFO level.'); + Log::notice('This is a test message at the NOTICE level.'); + Log::warning('This is a test message at the WARNING level.'); + Log::error('This is a test message at the ERROR level.'); + Log::critical('This is a test message at the CRITICAL level.'); + Log::alert('This is a test message at the ALERT level.'); + Log::emergency('This is a test message at the EMERGENCY level.'); + throw new FireflyException('A very simple test error.'); + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function flush(Request $request) + { + Preferences::mark(); + $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); + Log::debug('Call cache:clear...'); + Artisan::call('cache:clear'); + Log::debug('Call config:clear...'); + Artisan::call('config:clear'); + Log::debug('Call route:clear...'); + Artisan::call('route:clear'); + Log::debug('Call twig:clean...'); + try { + Artisan::call('twig:clean'); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + // don't care + Log::debug('Called twig:clean.'); + } + // @codeCoverageIgnoreEnd + Log::debug('Call view:clear...'); + Artisan::call('view:clear'); + Log::debug('Done! Redirecting...'); + + return redirect(route('index')); + } /** * @param Request $request @@ -120,6 +171,61 @@ class DebugController extends Controller ); } + /** + * @return string + */ + public function routes(): string + { + $set = RouteFacade::getRoutes(); + $ignore = ['chart.', 'javascript.', 'json.', 'report-data.', 'popup.', 'debugbar.', 'attachments.download', 'attachments.preview', + 'bills.rescan', 'budgets.income', 'currencies.def', 'error', 'flush', 'help.show', 'import.file', + 'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change', + 'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down', + 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', + 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', + 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', + 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', + 'transactions.clone', 'two-factor.index', 'api.v1', 'installer.','attachments.view','import.create', + 'import.job.download','import.job.start','import.job.status.json','import.job.store','recurring.events', + 'recurring.suggest' + ]; + $return = ' '; + /** @var Route $route */ + foreach ($set as $route) { + $name = $route->getName(); + if (null !== $name && \strlen($name) > 0 && \in_array('GET', $route->methods(), true)) { + + $found = false; + foreach ($ignore as $string) { + if (!(false === stripos($name, $string))) { + $found = true; + break; + } + } + if ($found === false) { + $return .= 'touch ' . $route->getName() . '.md;'; + } + } + } + + return $return; + } + + /** + * @param Request $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function testFlash(Request $request) + { + $request->session()->flash('success', 'This is a success message.'); + $request->session()->flash('info', 'This is an info message.'); + $request->session()->flash('warning', 'This is a warning.'); + $request->session()->flash('error', 'This is an error!'); + + return redirect(route('home')); + } + /** * Some common combinations. * @@ -151,7 +257,7 @@ class DebugController extends Controller private function collectPackages(): array { $packages = []; - $file = realpath(__DIR__ . '/../../../vendor/composer/installed.json'); + $file = \dirname(__DIR__, 3) . '/vendor/composer/installed.json'; if (!($file === false) && file_exists($file)) { // file exists! $content = file_get_contents($file); diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 993d3273e5..dcca1c2d3f 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -99,52 +99,7 @@ class HomeController extends Controller } - /** - * @throws FireflyException - */ - public function displayError() - { - Log::debug('This is a test message at the DEBUG level.'); - Log::info('This is a test message at the INFO level.'); - Log::notice('This is a test message at the NOTICE level.'); - Log::warning('This is a test message at the WARNING level.'); - Log::error('This is a test message at the ERROR level.'); - Log::critical('This is a test message at the CRITICAL level.'); - Log::alert('This is a test message at the ALERT level.'); - Log::emergency('This is a test message at the EMERGENCY level.'); - throw new FireflyException('A very simple test error.'); - } - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function flush(Request $request) - { - Preferences::mark(); - $request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range']); - Log::debug('Call cache:clear...'); - Artisan::call('cache:clear'); - Log::debug('Call config:clear...'); - Artisan::call('config:clear'); - Log::debug('Call route:clear...'); - Artisan::call('route:clear'); - Log::debug('Call twig:clean...'); - try { - Artisan::call('twig:clean'); - // @codeCoverageIgnoreStart - } catch (Exception $e) { - // don't care - Log::debug('Called twig:clean.'); - } - // @codeCoverageIgnoreEnd - Log::debug('Call view:clear...'); - Artisan::call('view:clear'); - Log::debug('Done! Redirecting...'); - - return redirect(route('index')); - } /** * @param AccountRepositoryInterface $repository @@ -193,56 +148,4 @@ class HomeController extends Controller ); } - /** - * @return string - */ - public function routes() - { - $set = RouteFacade::getRoutes(); - $ignore = ['chart.', 'javascript.', 'json.', 'report-data.', 'popup.', 'debugbar.', 'attachments.download', 'attachments.preview', - 'bills.rescan', 'budgets.income', 'currencies.def', 'error', 'flush', 'help.show', 'import.file', - 'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change', - 'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down', - 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', - 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', - 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', - 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', - 'transactions.clone', 'two-factor.index', - ]; - $return = ' '; - /** @var Route $route */ - foreach ($set as $route) { - $name = $route->getName(); - if (null !== $name && \in_array('GET', $route->methods()) && \strlen($name) > 0) { - - $found = false; - foreach ($ignore as $string) { - if (!(false === stripos($name, $string))) { - $found = true; - break; - } - } - if ($found === false) { - $return .= 'touch ' . $route->getName() . '.md;'; - } - } - } - - return $return; - } - - /** - * @param Request $request - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function testFlash(Request $request) - { - $request->session()->flash('success', 'This is a success message.'); - $request->session()->flash('info', 'This is an info message.'); - $request->session()->flash('warning', 'This is a warning.'); - $request->session()->flash('error', 'This is an error!'); - - return redirect(route('home')); - } } diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index c11541832b..ed25962773 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -25,8 +25,10 @@ namespace FireflyIII\Http\Controllers\Recurring; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use FireflyIII\Transformers\RecurrenceTransformer; use Illuminate\Http\JsonResponse; @@ -66,13 +68,58 @@ class IndexController extends Controller /** * @param Request $request * - * @return string + * @throws FireflyException + * @return JsonResponse */ - public function calendar(Request $request) + function events(RecurringRepositoryInterface $repository, Request $request): JsonResponse { - return view('recurring.calendar'); + $return = []; + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $endsAt = (string)$request->get('ends'); + $repetitionType = explode(',', $request->get('type'))[0]; + $repetitionMoment = ''; + + switch ($repetitionType) { + default: + throw new FireflyException(sprintf('Cannot handle repetition type "%s"', $repetitionType)); + case 'daily': + break; + case 'weekly': + case 'monthly': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1'; + break; + case 'ndom': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1,1'; + break; + case 'yearly': + $repetitionMoment = explode(',', $request->get('type'))[1] ?? '2018-01-01'; + break; + } + + $repetition = new RecurrenceRepetition; + $repetition->repetition_type = $repetitionType; + $repetition->repetition_moment = $repetitionMoment; + $repetition->repetition_skip = (int)$request->get('skip'); + + var_dump($repository->getXOccurrences($repetition, $start, 5)); + exit; + + + // calculate events in range, depending on type: + switch ($endsAt) { + default: + throw new FireflyException(sprintf('Cannot generate events for "%s"', $endsAt)); + case 'forever': + break; + + } + + + return Response::json($return); } + /** * @param Request $request * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 79905c1019..2bf58c2066 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -82,7 +82,7 @@ class RecurringRepository implements RecurringRepositoryInterface * @return array * @throws FireflyException */ - public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array { $return = []; $mutator = clone $date; diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index d72738b785..f0e7646827 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -63,7 +63,7 @@ interface RecurringRepositoryInterface * * @return array */ - public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array; + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array; /** * Parse the repetition in a string that is user readable. diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index db984a4c85..713928ac3b 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -141,7 +141,7 @@ class RecurrenceTransformer extends TransformerAbstract ]; // get the (future) occurrences for this specific type of repetition: - $occurrences = $this->repository->getOccurrences($repetition, $fromDate, 5); + $occurrences = $this->repository->getXOccurrences($repetition, $fromDate, 5); /** @var Carbon $carbon */ foreach ($occurrences as $carbon) { $repetitionArray['occurrences'][] = $carbon->format('Y-m-d'); diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index 5f2317fe68..083f42f9aa 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -44,6 +44,15 @@ $(document).ready(function () { * */ function showRepCalendar() { + + // pre-append URL with repetition info: + var newEventsUri = eventsUri + '?type=' + $('#ffInput_repetition_type').val(); + newEventsUri += '&skip=' + $('#ffInput_skip').val(); + newEventsUri += '&ends=' + $('#ffInput_repetition_end').val(); + newEventsUri += '&endDate=' + $('#ffInput_repeat_until').val(); + newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); + + $('#recurring_calendar').fullCalendar( { defaultDate: '2018-06-13', @@ -53,12 +62,7 @@ function showRepCalendar() { contentHeight: 300, aspectRatio: 1.25, eventLimit: true, // allow "more" link when too many events - events: [ - { - title: '', - start: '2018-06-14' - } - ] + events: newEventsUri }); $('#calendarModal').modal('show'); return false; diff --git a/resources/views/recurring/calendar.twig b/resources/views/recurring/calendar.twig deleted file mode 100644 index 2b3446b3fb..0000000000 --- a/resources/views/recurring/calendar.twig +++ /dev/null @@ -1,16 +0,0 @@ - \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 176b037608..8493509643 100755 --- a/routes/web.php +++ b/routes/web.php @@ -65,10 +65,10 @@ Route::group( */ Route::group( ['middleware' => 'user-simple-auth', 'namespace' => 'FireflyIII\Http\Controllers'], function () { - Route::get('error', ['uses' => 'HomeController@displayError', 'as' => 'error']); + Route::get('error', ['uses' => 'DebugController@displayError', 'as' => 'error']); Route::any('logout', ['uses' => 'Auth\LoginController@logout', 'as' => 'logout']); - Route::get('flush', ['uses' => 'HomeController@flush', 'as' => 'flush']); - Route::get('routes', ['uses' => 'HomeController@routes', 'as' => 'routes']); + Route::get('flush', ['uses' => 'DebugController@flush', 'as' => 'flush']); + Route::get('routes', ['uses' => 'DebugController@routes', 'as' => 'routes']); Route::get('debug', 'DebugController@index')->name('debug'); } ); @@ -96,7 +96,7 @@ Route::group( Route::group( ['middleware' => ['user-full-auth'], 'namespace' => 'FireflyIII\Http\Controllers'], function () { Route::get('/', ['uses' => 'HomeController@index', 'as' => 'index']); - Route::get('/flash', ['uses' => 'HomeController@testFlash', 'as' => 'test-flash']); + Route::get('/flash', ['uses' => 'DebugController@testFlash', 'as' => 'test-flash']); Route::get('/home', ['uses' => 'HomeController@index', 'as' => 'home']); Route::post('/daterange', ['uses' => 'HomeController@dateRange', 'as' => 'daterange']); } diff --git a/tests/Feature/Controllers/DebugControllerTest.php b/tests/Feature/Controllers/DebugControllerTest.php index 960b9dabed..e55c809226 100644 --- a/tests/Feature/Controllers/DebugControllerTest.php +++ b/tests/Feature/Controllers/DebugControllerTest.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace Tests\Feature\Controllers; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Log; use Tests\TestCase; @@ -43,6 +45,34 @@ class DebugControllerTest extends TestCase Log::debug(sprintf('Now in %s.', \get_class($this))); } + /** + * @covers \FireflyIII\Http\Controllers\DebugController::displayError + */ + public function testDisplayError(): void + { + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); + + $this->be($this->user()); + $response = $this->get(route('error')); + $response->assertStatus(500); + } + + /** + * @covers \FireflyIII\Http\Controllers\DebugController::flush + */ + public function testFlush(): void + { + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); + + $this->be($this->user()); + $response = $this->get(route('flush')); + $response->assertStatus(302); + } + /** * @covers \FireflyIII\Http\Controllers\DebugController::index * @covers \FireflyIII\Http\Controllers\DebugController::__construct @@ -56,4 +86,32 @@ class DebugControllerTest extends TestCase $response->assertStatus(200); } + /** + * @covers \FireflyIII\Http\Controllers\DebugController::routes() + */ + public function testRoutes(): void + { + $this->be($this->user()); + $response = $this->get(route('routes')); + $response->assertStatus(200); + } + + /** + * @covers \FireflyIII\Http\Controllers\DebugController::testFlash + */ + public function testTestFlash(): void + { + // mock stuff + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); + + $this->be($this->user()); + $response = $this->get(route('test-flash')); + $response->assertStatus(302); + $response->assertSessionHas('success'); + $response->assertSessionHas('info'); + $response->assertSessionHas('warning'); + $response->assertSessionHas('error'); + } + } diff --git a/tests/Feature/Controllers/HomeControllerTest.php b/tests/Feature/Controllers/HomeControllerTest.php index 807c9d0dd2..a2b68c59b8 100644 --- a/tests/Feature/Controllers/HomeControllerTest.php +++ b/tests/Feature/Controllers/HomeControllerTest.php @@ -100,34 +100,6 @@ class HomeControllerTest extends TestCase $response->assertSessionHas('warning', '91 days of data may take a while to load.'); } - /** - * @covers \FireflyIII\Http\Controllers\HomeController::displayError - */ - public function testDisplayError(): void - { - // mock stuff - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); - - $this->be($this->user()); - $response = $this->get(route('error')); - $response->assertStatus(500); - } - - /** - * @covers \FireflyIII\Http\Controllers\HomeController::flush - */ - public function testFlush(): void - { - // mock stuff - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); - - $this->be($this->user()); - $response = $this->get(route('flush')); - $response->assertStatus(302); - } - /** * @covers \FireflyIII\Http\Controllers\HomeController::index * @covers \FireflyIII\Http\Controllers\HomeController::__construct @@ -187,31 +159,5 @@ class HomeControllerTest extends TestCase $response->assertStatus(302); } - /** - * @covers \FireflyIII\Http\Controllers\HomeController::routes() - */ - public function testRoutes(): void - { - $this->be($this->user()); - $response = $this->get(route('routes')); - $response->assertStatus(200); - } - /** - * @covers \FireflyIII\Http\Controllers\HomeController::testFlash - */ - public function testTestFlash(): void - { - // mock stuff - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); - - $this->be($this->user()); - $response = $this->get(route('test-flash')); - $response->assertStatus(302); - $response->assertSessionHas('success'); - $response->assertSessionHas('info'); - $response->assertSessionHas('warning'); - $response->assertSessionHas('error'); - } } From 968abd26e8a04b7efeb973d898cd1cde6a94c4f2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 16 Jun 2018 07:23:54 +0200 Subject: [PATCH 026/134] Extend mail config and help. #1487 --- .env.docker | 7 +++++++ .env.example | 9 +++++++++ .env.heroku | 9 +++++++++ .env.sandstorm | 9 +++++++++ .env.testing | 11 ++++++++--- config/services.php | 6 ++++-- 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/.env.docker b/.env.docker index 20f4436f77..502b81ef49 100644 --- a/.env.docker +++ b/.env.docker @@ -50,6 +50,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=${MAIL_DRIVER} MAIL_HOST=${MAIL_HOST} MAIL_PORT=${MAIL_PORT} @@ -58,6 +59,12 @@ MAIL_USERNAME=${MAIL_USERNAME} MAIL_PASSWORD=${MAIL_PASSWORD} MAIL_ENCRYPTION=${MAIL_ENCRYPTION} +# Other mail drivers: +MAILGUN_DOMAIN=${MAILGUN_DOMAIN} +MAILGUN_SECRET=${MAILGUN_SECRET} +MANDRILL_SECRET=${MANDRILL_SECRET} +SPARKPOST_SECRET=${SPARKPOST_SECRET} + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=false diff --git a/.env.example b/.env.example index e45753a2ca..73afaf0470 100644 --- a/.env.example +++ b/.env.example @@ -54,6 +54,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -62,6 +63,12 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -100,3 +107,5 @@ IS_DOCKER=false IS_SANDSTORM=false BUNQ_USE_SANDBOX=false IS_HEROKU=false +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.heroku b/.env.heroku index 91e273659a..55237bd98b 100644 --- a/.env.heroku +++ b/.env.heroku @@ -54,6 +54,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -62,6 +63,12 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -100,3 +107,5 @@ IS_DOCKER=false IS_SANDSTORM=false BUNQ_USE_SANDBOX=false IS_HEROKU=true +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.sandstorm b/.env.sandstorm index c72676704c..5895f63467 100755 --- a/.env.sandstorm +++ b/.env.sandstorm @@ -54,6 +54,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -62,6 +63,12 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= + # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true SEND_ERROR_MESSAGE=true @@ -100,3 +107,5 @@ IS_DOCKER=false IS_SANDSTORM=true BUNQ_USE_SANDBOX=false IS_HEROKU=false +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.testing b/.env.testing index 943ab13502..6eec082239 100644 --- a/.env.testing +++ b/.env.testing @@ -49,6 +49,7 @@ COOKIE_DOMAIN= COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings +# For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html MAIL_DRIVER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 @@ -57,9 +58,11 @@ MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null -# Firefly III can send you the following messages -SEND_REGISTRATION_MAIL=true -SEND_ERROR_MESSAGE=false +# Other mail drivers: +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. @@ -96,3 +99,5 @@ IS_DOCKER=false IS_SANDSTORM=false BUNQ_USE_SANDBOX=true IS_HEROKU=false +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/config/services.php b/config/services.php index 01c5a3d90e..d72eea32f3 100644 --- a/config/services.php +++ b/config/services.php @@ -51,10 +51,12 @@ return [ 'secret' => env('SPARKPOST_SECRET'), ], - 'stripe' => [ + 'stripe' => [ 'model' => FireflyIII\User::class, 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ], - + 'mandrill' => [ + 'secret' => env('MANDRILL_SECRET'), + ], ]; From 1cf91c78f820cebd7d4c85fd927404884eeb5a56 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 16 Jun 2018 21:47:51 +0200 Subject: [PATCH 027/134] Lots of new code for recurring transactions. #1469 --- app/Factory/RecurrenceFactory.php | 124 ++++++++++++ app/Factory/TransactionJournalFactory.php | 24 +-- .../Recurring/CreateController.php | 26 ++- .../Controllers/Recurring/IndexController.php | 48 ++++- .../Transaction/SingleController.php | 8 +- app/Http/Requests/RecurrenceFormRequest.php | 189 ++++++++++++++++++ app/Http/Requests/Request.php | 10 + app/Models/Recurrence.php | 7 +- app/Models/RecurrenceTransaction.php | 7 +- .../Recurring/RecurringRepository.php | 185 +++++++++++++++-- .../RecurringRepositoryInterface.php | 25 ++- app/Rules/ValidRecurrenceRepetitionType.php | 72 +++++++ .../Internal/Support/TransactionTypeTrait.php | 57 ++++++ public/js/ff/recurring/create.js | 35 ++-- resources/lang/en_US/firefly.php | 5 +- resources/lang/en_US/validation.php | 1 + resources/views/recurring/create.twig | 5 +- resources/views/recurring/index.twig | 11 +- resources/views/recurring/show.twig | 9 +- 19 files changed, 769 insertions(+), 79 deletions(-) create mode 100644 app/Factory/RecurrenceFactory.php create mode 100644 app/Http/Requests/RecurrenceFormRequest.php create mode 100644 app/Rules/ValidRecurrenceRepetitionType.php create mode 100644 app/Services/Internal/Support/TransactionTypeTrait.php diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php new file mode 100644 index 0000000000..cb34751236 --- /dev/null +++ b/app/Factory/RecurrenceFactory.php @@ -0,0 +1,124 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Factory; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\TransactionType; +use FireflyIII\Services\Internal\Support\TransactionServiceTrait; +use FireflyIII\Services\Internal\Support\TransactionTypeTrait; +use FireflyIII\User; + +/** + * Class RecurrenceFactory + */ +class RecurrenceFactory +{ + use TransactionTypeTrait, TransactionServiceTrait; + + /** @var User */ + private $user; + + /** + * @param array $data + * + * @throws FireflyException + * @return Recurrence + */ + public function create(array $data): Recurrence + { + echo '
    ';
    +        print_r($data);
    +        echo '
    '; + $type = $this->findTransactionType(ucfirst($data['recurrence']['type'])); + $recurrence = new Recurrence( + [ + 'user_id' => $this->user->id, + 'transaction_type_id' => $type->id, + 'title' => $data['recurrence']['title'], + 'description' => $data['recurrence']['description'], + 'first_date' => $data['recurrence']['first_date']->format('Y-m-d'), + 'repeat_until' => $data['recurrence']['repeat_until'], + 'latest_date' => null, + 'repetitions' => $data['recurrence']['repetitions'], + 'apply_rules' => $data['recurrence']['apply_rules'], + 'active' => $data['recurrence']['active'], + ] + ); + $recurrence->save(); + var_dump($recurrence->toArray()); + + // create transactions + foreach ($data['transactions'] as $trArray) { + $source = null; + $destination = null; + // search source account, depends on type + switch ($type->type) { + default: + throw new FireflyException(sprintf('Cannot create "%s".', $type->type)); + case TransactionType::WITHDRAWAL: + $source = $this->findAccount(AccountType::ASSET, $trArray['source_account_id'], null); + $destination = $this->findAccount(AccountType::EXPENSE, null, $trArray['destination_account_name']); + break; + } + + // search destination account + + $transaction = new RecurrenceTransaction( + [ + 'recurrence_id' => $recurrence->id, + 'transaction_currency_id' => $trArray['transaction_currency_id'], + 'foreign_currency_id' => '' === (string)$trArray['foreign_amount'] ? null : $trArray['foreign_currency_id'], + 'source_account_id' => $source->id, + 'destination_account_id' => $destination->id, + 'amount' => $trArray['amount'], + 'foreign_amount' => '' === (string)$trArray['foreign_amount'] ? null : (string)$trArray['foreign_amount'], + 'description' => $trArray['description'], + ] + ); + $transaction->save(); + var_dump($transaction->toArray()); + } + + // create meta data: + if(\count($data['meta']['tags']) > 0) { + // todo store tags + } + + exit; + + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + +} \ No newline at end of file diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index d7c169e2e0..afdb26eb21 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -28,6 +28,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Support\JournalServiceTrait; +use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; use Log; @@ -36,7 +37,7 @@ use Log; */ class TransactionJournalFactory { - use JournalServiceTrait; + use JournalServiceTrait, TransactionTypeTrait; /** @var User */ private $user; @@ -137,25 +138,4 @@ class TransactionJournalFactory } } - /** - * Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always - * use TransactionType repository. - * - * @param string $type - * - * @return TransactionType - * @throws FireflyException - */ - protected function findTransactionType(string $type): TransactionType - { - $factory = app(TransactionTypeFactory::class); - $transactionType = $factory->find($type); - if (null === $transactionType) { - Log::error(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore - throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore - } - - return $transactionType; - } - } diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php index 52e12bf7c7..c2c477b883 100644 --- a/app/Http/Controllers/Recurring/CreateController.php +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -26,7 +26,9 @@ namespace FireflyIII\Http\Controllers\Recurring; use Carbon\Carbon; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\RecurrenceFormRequest; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use Illuminate\Http\Request; @@ -38,6 +40,8 @@ class CreateController extends Controller { /** @var BudgetRepositoryInterface */ private $budgets; + /** @var PiggyBankRepositoryInterface */ + private $piggyBanks; /** @var RecurringRepositoryInterface */ private $recurring; @@ -55,8 +59,9 @@ class CreateController extends Controller app('view')->share('title', trans('firefly.recurrences')); app('view')->share('subTitle', trans('firefly.create_new_recurrence')); - $this->recurring = app(RecurringRepositoryInterface::class); - $this->budgets = app(BudgetRepositoryInterface::class); + $this->recurring = app(RecurringRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + $this->piggyBanks = app(PiggyBankRepositoryInterface::class); return $next($request); } @@ -64,6 +69,8 @@ class CreateController extends Controller } /** + * @param Request $request + * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ public function create(Request $request) @@ -71,6 +78,8 @@ class CreateController extends Controller // todo refactor to expandedform method. $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $defaultCurrency = app('amount')->getDefaultCurrency(); + $piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount(); + $piggies = app('expandedform')->makeSelectListWithEmpty($piggyBanks); $tomorrow = new Carbon; $tomorrow->addDay(); @@ -90,7 +99,18 @@ class CreateController extends Controller ]; $request->session()->flash('preFilled', $preFilled); - return view('recurring.create', compact('tomorrow', 'preFilled','typesOfRepetitions', 'defaultCurrency', 'budgets')); + return view('recurring.create', compact('tomorrow', 'preFilled', 'piggies', 'typesOfRepetitions', 'defaultCurrency', 'budgets')); + } + + /** + * @param RecurrenceFormRequest $request + */ + public function store(RecurrenceFormRequest $request) + { + $data = $request->getAll(); + $this->recurring->store($data); + var_dump($data); + exit; } } \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index ed25962773..810000d2fc 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -76,9 +76,20 @@ class IndexController extends Controller $return = []; $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $firstDate = Carbon::createFromFormat('Y-m-d', $request->get('first_date')); + $endDate = '' !== (string)$request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null; $endsAt = (string)$request->get('ends'); $repetitionType = explode(',', $request->get('type'))[0]; + $repetitions = (int)$request->get('reps'); $repetitionMoment = ''; + $start->startOfDay(); + + // if $firstDate is beyond $end, simply return an empty array. + if ($firstDate->gt($end)) { + return Response::json([]); + } + // if $firstDate is beyond start, use that one: + $actualStart = clone $firstDate; switch ($repetitionType) { default: @@ -90,32 +101,51 @@ class IndexController extends Controller $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1'; break; case 'ndom': - $repetitionMoment = explode(',', $request->get('type'))[1] ?? '1,1'; + $repetitionMoment = str_ireplace('ndom,', '', $request->get('type')); break; case 'yearly': $repetitionMoment = explode(',', $request->get('type'))[1] ?? '2018-01-01'; break; } - $repetition = new RecurrenceRepetition; $repetition->repetition_type = $repetitionType; $repetition->repetition_moment = $repetitionMoment; $repetition->repetition_skip = (int)$request->get('skip'); - var_dump($repository->getXOccurrences($repetition, $start, 5)); - exit; - - - // calculate events in range, depending on type: + $actualEnd = clone $end; switch ($endsAt) { default: - throw new FireflyException(sprintf('Cannot generate events for "%s"', $endsAt)); + throw new FireflyException(sprintf('Cannot generate events for type that ends at "%s".', $endsAt)); case 'forever': + // simply generate up until $end. No change from default behavior. + $occurrences = $repository->getOccurrencesInRange($repetition, $actualStart, $actualEnd); + break; + case 'until_date': + $actualEnd = $endDate ?? clone $end; + $occurrences = $repository->getOccurrencesInRange($repetition, $actualStart, $actualEnd); + break; + case 'times': + $occurrences = $repository->getXOccurrences($repetition, $actualStart, $repetitions); break; - } + /** @var Carbon $current */ + foreach ($occurrences as $current) { + if ($current->gte($start)) { + $event = [ + 'id' => $repetitionType . $firstDate->format('Ymd'), + 'title' => 'X', + 'allDay' => true, + 'start' => $current->format('Y-m-d'), + 'end' => $current->format('Y-m-d'), + 'editable' => false, + 'rendering' => 'background', + ]; + $return[] = $event; + } + } + return Response::json($return); } diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 0f65bf03ff..af415c0ee8 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -37,6 +37,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Log; use Preferences; @@ -218,7 +219,7 @@ class SingleController extends Controller * * @internal param JournalRepositoryInterface $repository */ - public function destroy(TransactionJournal $transactionJournal) + public function destroy(TransactionJournal $transactionJournal): RedirectResponse { // @codeCoverageIgnoreStart if ($this->isOpeningBalance($transactionJournal)) { @@ -329,9 +330,10 @@ class SingleController extends Controller * @param JournalFormRequest $request * @param JournalRepositoryInterface $repository * - * @return \Illuminate\Http\RedirectResponse + * @return RedirectResponse + * @throws \FireflyIII\Exceptions\FireflyException */ - public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) + public function store(JournalFormRequest $request, JournalRepositoryInterface $repository): RedirectResponse { $doSplit = 1 === (int)$request->get('split_journal'); $createAnother = 1 === (int)$request->get('create_another'); diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php new file mode 100644 index 0000000000..f87a32295d --- /dev/null +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -0,0 +1,189 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Requests; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionType; +use FireflyIII\Rules\ValidRecurrenceRepetitionType; + +/** + * Class RecurrenceFormRequest + */ +class RecurrenceFormRequest extends Request +{ + + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow logged in users + return auth()->check(); + } + + /** + * @return array + * @throws FireflyException + */ + public function getAll(): array + { + $data = $this->all(); + $return = [ + 'recurrence' => [ + 'type' => $this->string('transaction_type'), + 'title' => $this->string('title'), + 'description' => $this->string('recurring_description'), + 'first_date' => $this->date('first_date'), + 'repeat_until' => $this->date('repeat_until'), + 'repetitions' => $this->integer('repetitions'), + 'apply_rules' => $this->boolean('apply_rules'), + 'active' => $this->boolean('active'), + ], + 'transactions' => [ + [ + 'transaction_currency_id' => $this->integer('transaction_currency_id'), + 'type' => $this->string('transaction_type'), + 'description' => $this->string('transaction_description'), + 'amount' => $this->string('amount'), + 'foreign_amount' => null, + 'foreign_currency_id' => null, + 'budget_id' => $this->integer('budget_id'), + 'category_name' => $this->string('category'), + + ], + ], + 'meta' => [ + // tags and piggy bank ID. + 'tags' => explode(',', $this->string('tags')), + 'piggy_bank_id' => $this->integer('piggy_bank_id'), + ], + 'repetitions' => [ + [ + 'skip' => $this->integer('skip'), + ], + ], + + ]; + + // fill in foreign currency data + if (null !== $this->float('foreign_amount')) { + $return['transactions'][0]['foreign_amount'] = $this->string('foreign_amount'); + $return['transactions'][0]['foreign_currency_id'] = $this->integer('foreign_currency_id'); + } + + // fill in source and destination account data + switch ($this->string('transaction_type')) { + default: + throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); + case 'withdrawal': + $return['transactions'][0]['source_account_id'] = $this->integer('source_account_id'); + $return['transactions'][0]['destination_account_name'] = $this->string('destination_account_name'); + break; + } + + return $return; + } + + /** + * @return array + * @throws FireflyException + */ + public function rules(): array + { + $today = new Carbon; + $tomorrow = clone $today; + $tomorrow->addDay(); + $rules = [ + // mandatory info for recurrence. + //'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', + 'title' => 'required|between:1,255', + 'first_date' => 'required|date|after:' . $today->format('Y-m-d'), + 'repetition_type' => ['required', new ValidRecurrenceRepetitionType, 'between:1,20'], + 'skip' => 'required|numeric|between:0,31', + + // optional for recurrence: + 'recurring_description' => 'between:0,65000', + 'active' => 'numeric|between:0,1', + 'apply_rules' => 'numeric|between:0,1', + + // mandatory for transaction: + 'transaction_description' => 'required|between:1,255', + 'transaction_type' => 'required|in:withdrawal,deposit,transfer', + 'transaction_currency_id' => 'required|exists:transaction_currencies,id', + 'amount' => 'numeric|required|more:0', + // mandatory account info: + 'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'source_account_name' => 'between:1,255|nullable', + 'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'destination_account_name' => 'between:1,255|nullable', + + // foreign amount data: + 'foreign_currency_id' => 'exists:transaction_currencies,id', + 'foreign_amount' => 'nullable|more:0', + + // optional fields: + 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', + 'category' => 'between:1,255|nullable', + 'tags' => 'between:1,255|nullable', + ]; + + // if ends after X repetitions, set another rule + if ($this->string('repetition_end') === 'times') { + $rules['repetitions'] = 'required|numeric|between:0,254'; + } + // if foreign amount, currency must be different. + if ($this->float('foreign_amount') !== 0.0) { + $rules['foreign_currency_id'] = 'exists:transaction_currencies,id|different:transaction_currency_id'; + } + + // if ends at date X, set another rule. + if ($this->string('repetition_end') === 'until_date') { + $rules['repeat_until'] = 'required|date|after:' . $tomorrow->format('Y-m-d'); + } + + // switchc on type to expand rules for source and destination accounts: + switch ($this->string('transaction_type')) { + case strtolower(TransactionType::WITHDRAWAL): + $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['destination_account_name'] = 'between:1,255|nullable'; + break; + case strtolower(TransactionType::DEPOSIT): + $rules['source_account_name'] = 'between:1,255|nullable'; + $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + break; + case strtolower(TransactionType::TRANSFER): + // this may not work: + $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id'; + $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id'; + + break; + default: + throw new FireflyException(sprintf('Cannot handle transaction type of type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore + } + + + return $rules; + } +} \ No newline at end of file diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 1146b31017..c293b6237e 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -47,6 +47,16 @@ class Request extends FormRequest return 1 === (int)$this->input($field); } + /** + * @param string $field + * + * @return float + */ + public function float(string $field): float + { + return (float)$this->get($field); + } + /** * @param string $field * diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php index 9d321a4a25..b3ce26d0f3 100644 --- a/app/Models/Recurrence.php +++ b/app/Models/Recurrence.php @@ -64,8 +64,7 @@ class Recurrence extends Model * @var array */ protected $casts - = [ - + = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', 'first_date' => 'date', @@ -73,6 +72,10 @@ class Recurrence extends Model 'active' => 'bool', 'apply_rules' => 'bool', ]; + /** @var array */ + protected $fillable + = ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active']; + /** @var string */ protected $table = 'recurrences'; /** diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php index 482f16151a..7d729c8fdc 100644 --- a/app/Models/RecurrenceTransaction.php +++ b/app/Models/RecurrenceTransaction.php @@ -47,6 +47,11 @@ use Illuminate\Database\Eloquent\Relations\HasMany; */ class RecurrenceTransaction extends Model { + /** @var array */ + protected $fillable + = ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_account_id', 'destination_account_id', 'amount', 'foreign_amount', + 'description']; + /** @var string */ protected $table = 'recurrences_transactions'; /** @@ -82,7 +87,7 @@ class RecurrenceTransaction extends Model */ public function recurrenceTransactionMeta(): HasMany { - return $this->hasMany(RecurrenceTransactionMeta::class,'rt_id'); + return $this->hasMany(RecurrenceTransactionMeta::class, 'rt_id'); } /** diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 2bf58c2066..57cce34211 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\Recurring; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\RecurrenceFactory; use FireflyIII\Models\Note; use FireflyIII\Models\Preference; use FireflyIII\Models\Recurrence; @@ -73,28 +74,35 @@ class RecurringRepository implements RecurringRepositoryInterface } /** - * Calculate the next X iterations starting on the date given in $date. + * Generate events in the date range. * * @param RecurrenceRepetition $repetition - * @param Carbon $date - * @param int $count + * @param Carbon $start + * @param Carbon $end + * + * @throws FireflyException * * @return array - * @throws FireflyException */ - public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array + public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array { $return = []; - $mutator = clone $date; + $mutator = clone $start; + $mutator->startOfDay(); + $skipMod = $repetition->repetition_skip + 1; + $attempts = 0; switch ($repetition->repetition_type) { default: throw new FireflyException( sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type) ); case 'daily': - for ($i = 0; $i < $count; $i++) { + while ($mutator <= $end) { + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + } $mutator->addDay(); - $return[] = clone $mutator; + $attempts++; } break; case 'weekly': @@ -110,35 +118,38 @@ class RecurringRepository implements RecurringRepositoryInterface // today is friday (5), expected is monday (1), subtract four days. $dayDifference = $dayOfWeek - $mutator->dayOfWeekIso; $mutator->addDays($dayDifference); - for ($i = 0; $i < $count; $i++) { - $return[] = clone $mutator; + while ($mutator <= $end) { + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + } + $attempts++; $mutator->addWeek(); } break; case 'monthly': - $mutator->addDay(); // always assume today has passed. $dayOfMonth = (int)$repetition->repetition_moment; if ($mutator->day > $dayOfMonth) { // day has passed already, add a month. $mutator->addMonth(); } - for ($i = 0; $i < $count; $i++) { + while ($mutator < $end) { $domCorrected = min($dayOfMonth, $mutator->daysInMonth); $mutator->day = $domCorrected; - $return[] = clone $mutator; + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + } + $attempts++; $mutator->endOfMonth()->addDay(); } break; case 'ndom': - $mutator->addDay(); // always assume today has passed. $mutator->startOfMonth(); // this feels a bit like a cop out but why reinvent the wheel? - $string = '%s %s of %s %s'; $counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',]; $daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',]; $parts = explode(',', $repetition->repetition_moment); - for ($i = 0; $i < $count; $i++) { + while ($mutator <= $end) { $string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y')); $newCarbon = new Carbon($string); $return[] = clone $newCarbon; @@ -150,11 +161,131 @@ class RecurringRepository implements RecurringRepositoryInterface $date->year = $mutator->year; if ($mutator > $date) { $date->addYear(); + } - for ($i = 0; $i < $count; $i++) { - $obj = clone $date; - $obj->addYears($i); - $return[] = $obj; + + // is $date between $start and $end? + $obj = clone $date; + $count = 0; + while ($obj <= $end && $obj >= $mutator && $count < 10) { + + $return[] = clone $obj; + $obj->addYears(1); + $count++; + } + break; + } + + return $return; + } + + /** + * Calculate the next X iterations starting on the date given in $date. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count + * + * @return array + * @throws FireflyException + */ + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array + { + $return = []; + $mutator = clone $date; + $skipMod = $repetition->repetition_skip + 1; + $total = 0; + $attempts = 0; + switch ($repetition->repetition_type) { + default: + throw new FireflyException( + sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type) + ); + case 'daily': + while ($total < $count) { + $mutator->addDay(); + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + $total++; + } + $attempts++; + } + break; + case 'weekly': + // monday = 1 + // sunday = 7 + $mutator->addDay(); // always assume today has passed. + $dayOfWeek = (int)$repetition->repetition_moment; + if ($mutator->dayOfWeekIso > $dayOfWeek) { + // day has already passed this week, add one week: + $mutator->addWeek(); + } + // today is wednesday (3), expected is friday (5): add two days. + // today is friday (5), expected is monday (1), subtract four days. + $dayDifference = $dayOfWeek - $mutator->dayOfWeekIso; + $mutator->addDays($dayDifference); + + while ($total < $count) { + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + $total++; + } + $attempts++; + $mutator->addWeek(); + } + break; + case 'monthly': + $mutator->addDay(); // always assume today has passed. + $dayOfMonth = (int)$repetition->repetition_moment; + if ($mutator->day > $dayOfMonth) { + // day has passed already, add a month. + $mutator->addMonth(); + } + + while ($total < $count) { + $domCorrected = min($dayOfMonth, $mutator->daysInMonth); + $mutator->day = $domCorrected; + if ($attempts % $skipMod === 0) { + $return[] = clone $mutator; + $total++; + } + $attempts++; + $mutator->endOfMonth()->addDay(); + } + break; + case 'ndom': + $mutator->addDay(); // always assume today has passed. + $mutator->startOfMonth(); + // this feels a bit like a cop out but why reinvent the wheel? + $counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',]; + $daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',]; + $parts = explode(',', $repetition->repetition_moment); + + while ($total < $count) { + $string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y')); + $newCarbon = new Carbon($string); + if ($attempts % $skipMod === 0) { + $return[] = clone $newCarbon; + $total++; + } + $attempts++; + $mutator->endOfMonth()->addDay(); + } + break; + case 'yearly': + $date = new Carbon($repetition->repetition_moment); + $date->year = $mutator->year; + if ($mutator > $date) { + $date->addYear(); + } + $obj = clone $date; + while ($total < $count) { + if ($attempts % $skipMod === 0) { + $return[] = clone $obj; + $total++; + } + $obj->addYears(1); + $attempts++; } break; } @@ -223,4 +354,18 @@ class RecurringRepository implements RecurringRepositoryInterface { $this->user = $user; } + + /** + * @param array $data + * + * @throws FireflyException + * @return Recurrence + */ + public function store(array $data): Recurrence + { + $factory = new RecurrenceFactory; + $factory->setUser($this->user); + + return $factory->create($data); + } } \ No newline at end of file diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index f0e7646827..1770309800 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Recurring; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\User; @@ -53,6 +54,19 @@ interface RecurringRepositoryInterface */ public function getNoteText(Recurrence $recurrence): string; + /** + * Generate events in the date range. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $start + * @param Carbon $end + * + * @throws FireflyException + * + * @return array + */ + public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array; + /** * Calculate the next X iterations starting on the date given in $date. * Returns an array of Carbon objects. @@ -61,9 +75,10 @@ interface RecurringRepositoryInterface * @param Carbon $date * @param int $count * + * @throws FireflyException * @return array */ - public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array; + public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array; /** * Parse the repetition in a string that is user readable. @@ -81,4 +96,12 @@ interface RecurringRepositoryInterface */ public function setUser(User $user): void; + /** + * @param array $data + * + * @throws FireflyException + * @return Recurrence + */ + public function store(array $data): Recurrence; + } \ No newline at end of file diff --git a/app/Rules/ValidRecurrenceRepetitionType.php b/app/Rules/ValidRecurrenceRepetitionType.php new file mode 100644 index 0000000000..c8aa3eedbd --- /dev/null +++ b/app/Rules/ValidRecurrenceRepetitionType.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Rules; + +use Illuminate\Contracts\Validation\Rule; + +/** + * Class ValidRecurrenceRepetitionType + */ +class ValidRecurrenceRepetitionType implements Rule +{ + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return trans('validation.valid_recurrence_rep_type'); + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * + * @return bool + */ + public function passes($attribute, $value): bool + { + $value = (string)$value; + if ($value === 'daily') { + return true; + } + //monthly,17 + //ndom,3,7 + if (\in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { + return true; + } + if (0 === strpos($value, 'monthly')) { + return true; + } + if (0 === strpos($value, 'ndom')) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/app/Services/Internal/Support/TransactionTypeTrait.php b/app/Services/Internal/Support/TransactionTypeTrait.php new file mode 100644 index 0000000000..204110c66b --- /dev/null +++ b/app/Services/Internal/Support/TransactionTypeTrait.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Support; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionTypeFactory; +use FireflyIII\Models\TransactionType; +use Log; + +/** + * Trait TransactionTypeTrait + * + * @package FireflyIII\Services\Internal\Support + */ +trait TransactionTypeTrait +{ + /** + * Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always + * use TransactionType repository. + * + * @param string $type + * + * @return TransactionType + * @throws FireflyException + */ + protected function findTransactionType(string $type): TransactionType + { + $factory = app(TransactionTypeFactory::class); + $transactionType = $factory->find($type); + if (null === $transactionType) { + Log::error(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore + throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore + } + + return $transactionType; + } +} \ No newline at end of file diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index 083f42f9aa..a2b8140c65 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -20,6 +20,8 @@ /** global: Modernizr, currencies */ +var calendar; + $(document).ready(function () { "use strict"; if (!Modernizr.inputtypes.date) { @@ -37,6 +39,19 @@ $(document).ready(function () { $('#ffInput_repetition_end').on('change', respondToRepetitionEnd); $('#ffInput_first_date').on('change', respondToFirstDateChange); + // create calendar on load: + calendar = $('#recurring_calendar').fullCalendar( + { + defaultDate: '2018-06-13', + editable: false, + height: 400, + width: 200, + contentHeight: 400, + aspectRatio: 1.25, + eventLimit: true, + eventSources: [], + }); + $('#calendar-link').on('click', showRepCalendar); }); @@ -49,22 +64,17 @@ function showRepCalendar() { var newEventsUri = eventsUri + '?type=' + $('#ffInput_repetition_type').val(); newEventsUri += '&skip=' + $('#ffInput_skip').val(); newEventsUri += '&ends=' + $('#ffInput_repetition_end').val(); - newEventsUri += '&endDate=' + $('#ffInput_repeat_until').val(); + newEventsUri += '&end_date=' + $('#ffInput_repeat_until').val(); newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); + newEventsUri += '&first_date=' + $('#ffInput_first_date').val(); + // remove all event sources from calendar: + calendar.fullCalendar('removeEventSources'); - $('#recurring_calendar').fullCalendar( - { - defaultDate: '2018-06-13', - editable: false, - height: 400, - width: 200, - contentHeight: 300, - aspectRatio: 1.25, - eventLimit: true, // allow "more" link when too many events - events: newEventsUri - }); + // add a new one: + calendar.fullCalendar('addEventSource', newEventsUri); $('#calendarModal').modal('show'); + return false; } @@ -169,6 +179,7 @@ function initializeButtons() { console.log('Value is ' + btn.data('value')); if (btn.data('value') === transactionType) { btn.addClass('btn-info disabled').removeClass('btn-default'); + $('input[name="transaction_type"]').val(transactionType); } else { btn.removeClass('btn-info disabled').addClass('btn-default'); } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index e839af9f50..7c6f46707f 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1239,6 +1239,7 @@ return [ 'repeat_forever' => 'Repeat forever', 'repeat_until_date' => 'Repeat until date', 'repeat_times' => 'Repeat a number of times', - - + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', ]; diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 9ca9ec0f61..d8764ef5a9 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'The total amount cannot be zero', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'email address', 'description' => 'description', diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index b03247764a..23425ecc5b 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -16,7 +16,7 @@

    {{ 'mandatory_for_recurring'|_ }}

    - {{ ExpandedForm.text('name') }} + {{ ExpandedForm.text('title') }} {{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }} {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} {{ ExpandedForm.number('skip', 0) }} @@ -78,6 +78,7 @@
    + {# end of three buttons#} {{ ExpandedForm.text('transaction_description') }} @@ -125,7 +126,7 @@ {{ ExpandedForm.text('tags') }} {# RELATE THIS TRANSFER TO A PIGGY BANK #} - {{ ExpandedForm.select('piggy_bank_id', [], '0') }} + {{ ExpandedForm.select('piggy_bank_id', piggies, 0) }}
    diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig index 63f12e8605..c6214daa1a 100644 --- a/resources/views/recurring/index.twig +++ b/resources/views/recurring/index.twig @@ -83,7 +83,16 @@
      {% for rep in rt.repetitions %} -
    • {{ rep.description }}
    • +
    • {{ rep.description }} + {% if rep.repetition_skip == 1 %} + ({{ trans('firefly.recurring_skips_one')|lower }}) + {% endif %} + {% if rep.repetition_skip > 1 %} + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) + {% endif %} + + +
    • {% endfor %}
    diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index 492a15fffe..77df4511ab 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -42,7 +42,14 @@
      {% for rep in array.repetitions %} -
    • {{ rep.description }} +
    • + {{ rep.description }} + {% if rep.repetition_skip == 1 %} + ({{ trans('firefly.recurring_skips_one')|lower }}) + {% endif %} + {% if rep.repetition_skip > 1 %} + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) + {% endif %}
        {% for occ in rep.occurrences %}
      • {{ occ.formatLocalized(trans('config.month_and_date_day')) }}
      • From dcf90b615947bc44d0ccf8349b3a3e74e3416f6a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 17 Jun 2018 07:44:59 +0200 Subject: [PATCH 028/134] Cannot create transfer --- app/Factory/TransactionFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index bb5baf3cbe..cc615dcc7d 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -109,7 +109,7 @@ class TransactionFactory Log::debug(sprintf('Source type is "%s", destination type is "%s"', $sourceType, $destinationType)); // throw big fat error when source type === dest type - if ($sourceAccount->accountType->type === $destinationAccount->accountType->type) { + if ($sourceAccount->accountType->type === $destinationAccount->accountType->type && $journal->transactionType->type !== TransactionType::TRANSFER) { throw new FireflyException(sprintf('Source and destination account cannot be both of the type "%s"', $destinationAccount->accountType->type)); } if ($sourceAccount->accountType->type !== AccountType::ASSET && $destinationAccount->accountType->type !== AccountType::ASSET) { From abf218fc2133ae3a16e7b22bfddfc0885f095cbc Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 17 Jun 2018 07:45:10 +0200 Subject: [PATCH 029/134] Prevent empty box. --- app/Http/Controllers/Json/BoxController.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php index 27c857d97b..e61c522daf 100644 --- a/app/Http/Controllers/Json/BoxController.php +++ b/app/Http/Controllers/Json/BoxController.php @@ -153,6 +153,12 @@ class BoxController extends Controller $incomes[$currencyId] = Amount::formatAnything($currency, $incomes[$currencyId] ?? '0', false); $expenses[$currencyId] = Amount::formatAnything($currency, $expenses[$currencyId] ?? '0', false); } + if (\count($sums) === 0) { + $currency = app('amount')->getDefaultCurrency(); + $sums[$currency->id] = Amount::formatAnything($currency, '0', false); + $incomes[$currency->id] = Amount::formatAnything($currency, '0', false); + $expenses[$currency->id] = Amount::formatAnything($currency, '0', false); + } $response = [ 'incomes' => $incomes, @@ -161,6 +167,7 @@ class BoxController extends Controller 'size' => \count($sums), ]; + $cache->store($response); return response()->json($response); From 54e3e3f0515b86c6ac5bebf07748c45f568a1bf2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 17 Jun 2018 07:45:21 +0200 Subject: [PATCH 030/134] Play around with boxes layout. --- resources/views/partials/boxes.twig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/resources/views/partials/boxes.twig b/resources/views/partials/boxes.twig index 68708cbc25..95fc6ea5d8 100644 --- a/resources/views/partials/boxes.twig +++ b/resources/views/partials/boxes.twig @@ -1,7 +1,6 @@ -
    • -
    • +
    • {{ 'moneyManagement'|_ }} diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 23425ecc5b..644630d7d8 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -126,7 +126,7 @@ {{ ExpandedForm.text('tags') }} {# RELATE THIS TRANSFER TO A PIGGY BANK #} - {{ ExpandedForm.select('piggy_bank_id', piggies, 0) }} + {{ ExpandedForm.piggyBankList('piggy_bank_id',0) }}
    @@ -200,6 +200,7 @@ var transactionType = "{{ preFilled.transaction_type }}"; var suggestUri = "{{ route('recurring.suggest') }}"; var eventsUri = "{{ route('recurring.events') }}"; + var oldRepetitionType= "{{ oldRepetitionType }}"; {% endblock %} diff --git a/resources/views/recurring/edit.twig b/resources/views/recurring/edit.twig new file mode 100644 index 0000000000..f9560d436a --- /dev/null +++ b/resources/views/recurring/edit.twig @@ -0,0 +1,214 @@ +{% extends "./layout/default" %} +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }} +{% endblock %} +{% block content %} + +
    + + {# row with recurrence information #} + + +
    +
    + {# mandatory transaction information #} +
    +
    +

    {{ 'mandatory_for_transaction'|_ }}

    +
    +
    +

    {{ 'mandatory_fields_for_tranaction'|_ }}

    + {# three buttons to distinguish type of transaction#} +
    + + {# end of three buttons#} + + {{ ExpandedForm.text('transaction_description') }} + {# transaction information (mandatory) #} + {{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('amount', []) }} + + {# source account if withdrawal, or if transfer: #} + {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + + {# source account name for deposits: #} + {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} + + {# destination if deposit or transfer: #} + {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + + {# destination account name for withdrawals #} + {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} +
    +
    +
    + +
    + {# optional transaction information #} +
    +
    +

    {{ 'optional_for_transaction'|_ }}

    +
    +
    + {# transaction information (optional) #} + {{ ExpandedForm.currencyList('foreign_currency_id', defaultCurrency.id) }} + {{ ExpandedForm.amountNoCurrency('foreign_amount', []) }} + + {# BUDGET ONLY WHEN CREATING A WITHDRAWAL #} + {% if budgets|length > 1 %} + {{ ExpandedForm.select('budget_id', [], null) }} {#budgets#} + {% else %} + {{ ExpandedForm.select('budget_id', [], null, {helpText: trans('firefly.no_budget_pointer')}) }} + {#budgets#} + {% endif %} + + {# CATEGORY ALWAYS #} + {{ ExpandedForm.text('category') }} + + {# TAGS #} + {{ ExpandedForm.text('tags') }} + + {# RELATE THIS TRANSFER TO A PIGGY BANK #} + {{ ExpandedForm.select('piggy_bank_id', [], 0) }} {#piggies#} +
    +
    +
    +
    + + {# row with submit stuff. #} +
    +
    +
    +
    +

    {{ 'options'|_ }}

    +
    +
    + {{ ExpandedForm.optionsList('create','recurrence') }} +
    + +
    +
    +
    + {# +
    +
    +
    +

    {{ 'expected_repetitions'|_ }}

    +
    +
    + Here. +
    +
    +
    +
    +
    + #} + + + {# calendar modal #} + + + + + +{% endblock %} +{% block scripts %} + + + + + + + +{% endblock %} + +{% block styles %} + + + + +{% endblock %} diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig index c6214daa1a..4bcf9d582f 100644 --- a/resources/views/recurring/index.twig +++ b/resources/views/recurring/index.twig @@ -49,8 +49,7 @@
    -
    diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index 44202ec883..f6d8e7b02a 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -86,8 +86,7 @@ {{ ExpandedForm.text('tags') }} {# RELATE THIS TRANSFER TO A PIGGY BANK #} - {{ ExpandedForm.select('piggy_bank_id', piggies, '0') }} - + {{ ExpandedForm.piggyBankList('piggy_bank_id',0) }}
    @@ -211,7 +210,6 @@ {% block scripts %} {% endblock %} diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig index 60f0e303fd..7b27187baa 100644 --- a/resources/views/recurring/index.twig +++ b/resources/views/recurring/index.twig @@ -54,12 +54,15 @@
    + {% if rt.active == false %}{% endif %} {{ rt.transaction_type|_ }}: - {{ rt.title }} + {% if rt.active == false %} ({{ 'inactive'|_|lower }}){% endif %} {% if rt.description|length > 0 %}
    {{ rt.description }}
    {% endif %} + +
      diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index 77df4511ab..54936e929d 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -12,10 +12,23 @@

      {{ array.title }} + + ({{ array.transaction_type }}) + + {% if array.active == false %} + ({{ 'inactive'|_|lower }}) + {% endif %}

      {{ array.description }}

      + + {% if array.active == false %} +

      + {{ 'recurrence_is_inactive'|_ }} +

      + {% endif %} +
        {% for rep in array.repetitions %}
      • {{ rep.description }}
      • @@ -35,13 +48,13 @@

        - {{ 'expected_transactions'|_ }} + {{ ('expected_'~array.transaction_type~'s')|_ }}

          - {% for rep in array.repetitions %} + {% for rep in array.recurrence_repetitions %}
        • {{ rep.description }} {% if rep.repetition_skip == 1 %} @@ -128,47 +141,47 @@ {% if array.meta|length > 0 %} -
          -
          -
          -

          - {{ 'meta_data'|_ }} -

          -
          -
          - - - - - - - {% for meta in array.meta %} - - - - - {% endfor %} - +
          +
          +
          +

          + {{ 'meta_data'|_ }} +

          +
          +
          +
          {{ trans('list.field') }}{{ trans('list.value') }}
          {{ trans('firefly.recurring_meta_field_'~meta.name) }} - {% if meta.name == 'tags' %} - {% for tag in meta.tags %} - {{ tag }} - {% endfor %} - {% endif %} - {% if meta.name == 'notes' %} - {{ meta.value|markdown }} - {% endif %} - {% if meta.name == 'bill_id' %} - {{ meta.bill_name }} - {% endif %} - {% if meta.name == 'piggy_bank_id' %} - {{ meta.piggy_bank_name }} - {% endif %} -
          + + + + + + {% for meta in array.meta %} + + + + + {% endfor %} + -
          {{ trans('list.field') }}{{ trans('list.value') }}
          {{ trans('firefly.recurring_meta_field_'~meta.name) }} + {% if meta.name == 'tags' %} + {% for tag in meta.tags %} + {{ tag }} + {% endfor %} + {% endif %} + {% if meta.name == 'notes' %} + {{ meta.value|markdown }} + {% endif %} + {% if meta.name == 'bill_id' %} + {{ meta.bill_name }} + {% endif %} + {% if meta.name == 'piggy_bank_id' %} + {{ meta.piggy_bank_name }} + {% endif %} +
          + +
          -
        {% endif %}
        @@ -177,11 +190,11 @@

        - {{ 'transactions'|_ }} + {{ ('created_'~array.transaction_type~'s')|_ }}

        - Bla bla + List be here.
        From 7c84af2370e5da4ccb62d66d3c0cf10393294289 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 05:41:14 +0200 Subject: [PATCH 047/134] Schedule recurring transactions daily. --- app/Console/Kernel.php | 47 ++++++++++-------------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d919e8e1a4..20350913e4 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -25,12 +25,11 @@ declare(strict_types=1); namespace FireflyIII\Console; use Carbon\Carbon; +use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Jobs\CreateRecurringTransactions; -use FireflyIII\Models\RecurrenceRepetition; -use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\User; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; -use Log; /** * File to make sure commnds work. @@ -65,41 +64,17 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { + // create recurring transactions. + $schedule->job(new CreateRecurringTransactions(new Carbon))->daily(); + + // send test email. $schedule->call( function () { - // run for the entirety of 2018, just to see what happens - $start = new Carbon('2018-01-01'); - $end = new Carbon('2018-12-31'); - while ($start <= $end) { - Log::info(sprintf('Now at %s', $start->format('D Y-m-d'))); - $job = new CreateRecurringTransactions(clone $start); - $job->handle(); - $start->addDay(); - } - + $ipAddress = '127.0.0.1'; + /** @var User $user */ + $user = User::find(1); + event(new AdminRequestedTestMessage($user, $ipAddress)); } - )->everyMinute(); - - //$schedule->job(new CreateRecurringTransactions(new Carbon))->everyMinute(); - - //$schedule->job(new CreateRecurringTransactions(new Carbon))->everyMinute(); - - - // $schedule->call( - // function () { - // // command to do something - // Log::debug('Schedule creation of transactions yaay!'); - // } - // )->daily(); - // - // $schedule->call( - // function () { - // // command to do something - // Log::debug('Every minute!'); - // } - // )->everyMinute() - // ->emailOutputTo('thege'); - - + )->daily(); } } From b95dd5c238609b587cd3a163510c903f2ffb5698 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 06:29:14 +0200 Subject: [PATCH 048/134] Updated language strings [skip ci] --- resources/lang/de_DE/config.php | 41 ++++++---- resources/lang/de_DE/demo.php | 2 + resources/lang/de_DE/firefly.php | 60 +++++++++++++- resources/lang/de_DE/form.php | 25 ++++-- resources/lang/de_DE/list.php | 5 ++ resources/lang/de_DE/validation.php | 1 + resources/lang/en_US/firefly.php | 2 +- resources/lang/es_ES/config.php | 41 ++++++---- resources/lang/es_ES/demo.php | 2 + resources/lang/es_ES/firefly.php | 60 +++++++++++++- resources/lang/es_ES/form.php | 25 ++++-- resources/lang/es_ES/list.php | 5 ++ resources/lang/es_ES/validation.php | 1 + resources/lang/fr_FR/auth.php | 2 +- resources/lang/fr_FR/config.php | 41 ++++++---- resources/lang/fr_FR/demo.php | 4 +- resources/lang/fr_FR/firefly.php | 62 ++++++++++++++- resources/lang/fr_FR/form.php | 43 +++++++---- resources/lang/fr_FR/import.php | 116 ++++++++++++++-------------- resources/lang/fr_FR/intro.php | 6 +- resources/lang/fr_FR/list.php | 27 ++++--- resources/lang/fr_FR/validation.php | 5 +- resources/lang/id_ID/config.php | 41 ++++++---- resources/lang/id_ID/demo.php | 2 + resources/lang/id_ID/firefly.php | 60 +++++++++++++- resources/lang/id_ID/form.php | 25 ++++-- resources/lang/id_ID/list.php | 5 ++ resources/lang/id_ID/validation.php | 1 + resources/lang/it_IT/config.php | 41 ++++++---- resources/lang/it_IT/demo.php | 2 + resources/lang/it_IT/firefly.php | 68 ++++++++++++++-- resources/lang/it_IT/form.php | 25 ++++-- resources/lang/it_IT/import.php | 4 +- resources/lang/it_IT/list.php | 7 +- resources/lang/it_IT/validation.php | 3 +- resources/lang/nl_NL/config.php | 41 ++++++---- resources/lang/nl_NL/demo.php | 2 + resources/lang/nl_NL/firefly.php | 58 +++++++++++++- resources/lang/nl_NL/form.php | 25 ++++-- resources/lang/nl_NL/list.php | 5 ++ resources/lang/nl_NL/validation.php | 1 + resources/lang/pl_PL/config.php | 41 ++++++---- resources/lang/pl_PL/demo.php | 2 + resources/lang/pl_PL/firefly.php | 60 +++++++++++++- resources/lang/pl_PL/form.php | 25 ++++-- resources/lang/pl_PL/list.php | 5 ++ resources/lang/pl_PL/validation.php | 1 + resources/lang/pt_BR/config.php | 41 ++++++---- resources/lang/pt_BR/demo.php | 2 + resources/lang/pt_BR/firefly.php | 60 +++++++++++++- resources/lang/pt_BR/form.php | 25 ++++-- resources/lang/pt_BR/list.php | 5 ++ resources/lang/pt_BR/validation.php | 1 + resources/lang/ru_RU/config.php | 41 ++++++---- resources/lang/ru_RU/demo.php | 2 + resources/lang/ru_RU/firefly.php | 60 +++++++++++++- resources/lang/ru_RU/form.php | 25 ++++-- resources/lang/ru_RU/list.php | 5 ++ resources/lang/ru_RU/validation.php | 1 + resources/lang/tr_TR/config.php | 41 ++++++---- resources/lang/tr_TR/demo.php | 2 + resources/lang/tr_TR/firefly.php | 60 +++++++++++++- resources/lang/tr_TR/form.php | 25 ++++-- resources/lang/tr_TR/list.php | 5 ++ resources/lang/tr_TR/validation.php | 1 + 65 files changed, 1174 insertions(+), 354 deletions(-) diff --git a/resources/lang/de_DE/config.php b/resources/lang/de_DE/config.php index a42da571da..311be92dab 100644 --- a/resources/lang/de_DE/config.php +++ b/resources/lang/de_DE/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'de', - 'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e. %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e. %B %Y', - 'week_in_year' => 'KW %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'Do MMMM YYYY', - 'date_time_js' => 'Do MMMM YYYY um HH:mm:ss', - 'specific_day_js' => 'D. MMMM YYYY', - 'week_in_year_js' => '[Week]. KW, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q. Quartal YYYY', + 'html_language' => 'de', + 'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e. %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e. %B %Y', + 'week_in_year' => 'KW %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY um HH:mm:ss', + 'specific_day_js' => 'D. MMMM YYYY', + 'week_in_year_js' => '[Week]. KW, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q. Quartal YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/de_DE/demo.php b/resources/lang/de_DE/demo.php index 479be2661f..9a3e9f6676 100644 --- a/resources/lang/de_DE/demo.php +++ b/resources/lang/de_DE/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Diese Ausgaben, Einnahmen und Umbuchungen sind nicht besonders einfallsreich. Sie wurden automatisch generiert.', 'piggy-banks-index' => 'Hier wurden bereits drei Sparschweine angelegt. Der Betrag in den Sparschweinen kann über die Plus-/Minus-Buttons angepasst werden. Klicken Sie auf den Namen des Sparschweins um weitere Informationen einzusehen.', 'import-index' => 'Jede CSV-Datei kann in Firefly III importiert werden. Es wird auch der Import von Daten aus Bunq und Spectre unterstützt. Weitere Banken und Finanzaggregatoren werden in Zukunft implementiert. Als Demo-Anwender können Sie jedoch nur einen „Schein”-Anbieter in Aktion erleben. Es werden einige zufällige Transaktionen generiert, um Ihnen zu zeigen, wie der Prozess funktioniert.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 6bbbcaf6de..6cb8150663 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -821,7 +821,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'language' => 'Sprache', 'new_savings_account' => ':bank_name-Sparkonto', 'cash_wallet' => 'Geldbörse', - 'currency_not_present' => 'Wenn die Währung, die Sie normalerweise verwenden, nicht aufgeführt ist, machen Sie sich keine Sorgen. Unter Optionen ➜ Währungen können Sie eigene Währungen anlegen.', + 'currency_not_present' => 'Wenn die Währung, die Sie normalerweise verwenden, nicht aufgeführt ist, machen Sie sich keine Sorgen. Unter Optionen ➜ Währungen können Sie eigene Währungen anlegen.', // home page: 'yourAccounts' => 'Deine Konten', @@ -1063,7 +1063,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'instance_configuration' => 'Konfiguration', 'firefly_instance_configuration' => 'Konfigurationsoptionen für Firefly III', 'setting_single_user_mode' => 'Einzelnutzermodus', - 'setting_single_user_mode_explain' => 'Standardmäßig akzeptiert Firefly III nur eine Registrierung: Sie. Diese Sicherheitsmaßnahme verhindert das Benutzen Ihrer Instanz durch andere, außer Sie erlauben es. Zukünftige Registrierungen sind gesperrt. Wenn Sie dieses Kontrollkästchen deaktivieren können andere ihre Instanz ebenfalls benutzen, vorausgesetzt sie ist erreichbar (mit dem Internet verbunden).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Konfiguration speichern', 'single_user_administration' => 'Benutzerverwaltung für :email', 'edit_user' => 'Benutzer :email bearbeiten', @@ -1156,8 +1156,9 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'cannot_convert_split_journal' => 'Eine Splitbuchung konnte nicht umgesetzt werden', // Import page (general strings only) - 'import_index_title' => 'Daten in Firefly III importieren', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Daten importieren', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Diese Funktion ist nicht verfügbar, wenn Sie Firefly III in einer Sandstorm.io-Umgebung verwenden.', @@ -1207,4 +1208,57 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'no_bills_intro_default' => 'Du hast noch keine Rechnungen. Sie können Rechnungen erstellen, um die laufenden Ausgaben, wie zum Beispiel Ihre Versicherung oder Miete, nachzuverfolgen.', 'no_bills_imperative_default' => 'Haben Sie regelmäßige Rechnungen? Erstellen Sie eine Rechnung und verfolgen Sie Ihre Zahlungen:', 'no_bills_create_default' => 'Eine Rechnung erstellen', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index 3195ec20e9..b5bcbab8b4 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Ländercode', 'provider_code' => 'Bank oder Datenanbieter', - 'due_date' => 'Fälligkeitstermin', - 'payment_date' => 'Zahlungsdatum', - 'invoice_date' => 'Rechnungsdatum', - 'internal_reference' => 'Interner Verweis', - 'inward' => 'Beschreibung der Eingänge', - 'outward' => 'Beschreibung der Ausgänge', - 'rule_group_id' => 'Regelgruppe', + 'due_date' => 'Fälligkeitstermin', + 'payment_date' => 'Zahlungsdatum', + 'invoice_date' => 'Rechnungsdatum', + 'internal_reference' => 'Interner Verweis', + 'inward' => 'Beschreibung der Eingänge', + 'outward' => 'Beschreibung der Ausgänge', + 'rule_group_id' => 'Regelgruppe', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php index f2d030e3f7..805fd8f156 100644 --- a/resources/lang/de_DE/list.php +++ b/resources/lang/de_DE/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Letzte Anmeldung', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq-Zahlungskennung', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 7cc5d3de94..5269f95e73 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'Der Gesamtbetrag darf nicht Null sein', 'unique_piggy_bank_for_user' => 'Der Name des Sparschweins muss eindeutig sein.', 'secure_password' => 'Das ist kein sicheres Passwort. Bitte versuchen Sie es erneut. Weitere Informationen finden Sie unter https://github.com/firefly-iii/help/wiki/Secure-password', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'E-Mail Adresse', 'description' => 'Beschreibung', diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 1bb5af2c78..f64d00cd18 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1212,7 +1212,7 @@ return [ 'recurrences' => 'Recurring transactions', 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', - 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?-icon in the top right corner) before you continue.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', 'no_recurring_create_default' => 'Create a recurring transaction', 'make_new_recurring' => 'Create a recurring transaction', 'recurring_daily' => 'Every day', diff --git a/resources/lang/es_ES/config.php b/resources/lang/es_ES/config.php index d5bebf299c..f4981713fe 100644 --- a/resources/lang/es_ES/config.php +++ b/resources/lang/es_ES/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'es', - 'locale' => 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%B %e, %Y', - 'date_time' => '%B %e, %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Semana %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'es', + 'locale' => 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semana %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index 67a5f2fe85..4588444cea 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Estos gastos, depósitos y transferencias no son particularmente imaginativos. Se han generado automáticamente.', 'piggy-banks-index' => 'Como puede ver, hay tres alcancías. Utilice los botones más y menos para influir en la cantidad de dinero en cada alcancía. Haga clic en el nombre de la alcancía para ver la administración de cada una.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 70fe05fe79..ad4aba617b 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -821,7 +821,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Tus cuentas', @@ -1063,7 +1063,7 @@ return [ 'instance_configuration' => 'Configuracion', 'firefly_instance_configuration' => 'Opciones de configuración de Firefly III', 'setting_single_user_mode' => 'Modo de usuario único', - 'setting_single_user_mode_explain' => 'Por defecto, Firefly III solo acepta un (1) registro: Usted. Esta es una medida de seguridad que impide que otros utilicen su instancia a menos que usted lo permita. Los registros futuros están bloqueados. Cuando usted desbloquee esta casilla, otros pueden usar su instancia también, suponiendo que puedan alcanzarla ( cuando este conectada a Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Configuración de tienda', 'single_user_administration' => 'Administración de usuarios para :email', 'edit_user' => 'Editar usuario :email', @@ -1156,8 +1156,9 @@ return [ 'cannot_convert_split_journal' => 'No se puede convertir una transacción dividida', // Import page (general strings only) - 'import_index_title' => 'Importar datos a Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importar datos', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Esta función no esta disponible cuando usted esta utilizando Firefly III dentro de un ambiente Sandstorm.io.', @@ -1207,4 +1208,57 @@ return [ 'no_bills_intro_default' => 'Usted no tiene facturas aun. Usted puede crear facturas para hacer un seguimiento de los gastos regulares, como su alquiler o el seguro.', 'no_bills_imperative_default' => '¿Tienes facturas periódicas? Crea una factura y haz un seguimiento de tus pagos:', 'no_bills_create_default' => 'Crear una factura', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 11d94d8b7f..a199b95f86 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Código del país', 'provider_code' => 'Banco o proveedor de datos', - 'due_date' => 'Fecha de vencimiento', - 'payment_date' => 'Fecha de pago', - 'invoice_date' => 'Fecha de la factura', - 'internal_reference' => 'Referencia interna', - 'inward' => 'Descripción interna', - 'outward' => 'Descripción externa', - 'rule_group_id' => 'Grupo de reglas', + 'due_date' => 'Fecha de vencimiento', + 'payment_date' => 'Fecha de pago', + 'invoice_date' => 'Fecha de la factura', + 'internal_reference' => 'Referencia interna', + 'inward' => 'Descripción interna', + 'outward' => 'Descripción externa', + 'rule_group_id' => 'Grupo de reglas', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index 2aa21df77b..ea67eabb8a 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 227ebdc572..fc709dd13b 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'La cantidad total no puede ser cero', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'dirección de correo electrónico', 'description' => 'descripcion', diff --git a/resources/lang/fr_FR/auth.php b/resources/lang/fr_FR/auth.php index 689b6966af..974345567e 100644 --- a/resources/lang/fr_FR/auth.php +++ b/resources/lang/fr_FR/auth.php @@ -24,5 +24,5 @@ declare(strict_types=1); return [ 'failed' => 'Ces identifiants n\'ont aucune correspondance.', - 'throttle' => 'Trop de tentatives de connexion. Veuillez essayer à nouveau dans :seconds secondes.', + 'throttle' => 'Trop de tentatives de connexion. Veuillez réessayer dans :seconds secondes.', ]; diff --git a/resources/lang/fr_FR/config.php b/resources/lang/fr_FR/config.php index 763721bdba..05283eb88f 100644 --- a/resources/lang/fr_FR/config.php +++ b/resources/lang/fr_FR/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'fr', - 'locale' => 'fr, French, fr_FR, fr_FR.utf8, fr_FR.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%B %e %Y @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Semaine %W %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'fr', + 'locale' => 'fr, French, fr_FR, fr_FR.utf8, fr_FR.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e %Y @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semaine %W %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/fr_FR/demo.php b/resources/lang/fr_FR/demo.php index 94171d0aa6..6464f29911 100644 --- a/resources/lang/fr_FR/demo.php +++ b/resources/lang/fr_FR/demo.php @@ -33,5 +33,7 @@ return [ 'currencies-index' => 'Firefly III prend en charge plusieurs devises. Bien que l\'Euro soit la devise par défaut, cette dernière peut être changée pour le Dollar américain et de nombreuses autres devises. Comme vous pouvez le remarquer une petite sélection des monnaies a été incluse, mais vous pouvez ajouter vos propres devises si vous le souhaitez. Gardez à l\'esprit que la modification de la devise par défaut ne modifie pas la monnaie des transactions existantes : Firefly III prend en charge l’utilisation de plusieurs devises en même temps.', 'transactions-index' => 'Ces dépenses, dépôts et transferts ne sont pas particulièrement imaginatifs. Ils ont été générés automatiquement.', 'piggy-banks-index' => 'Comme vous pouvez le voir, il y a trois tirelires. Utilisez les boutons plus et moins pour influer sur le montant d’argent dans chaque tirelire. Cliquez sur le nom de la tirelire pour voir l’administration pour chaque tirelire.', - 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'import-index' => 'Tout fichier CSV peut être importé dans Firefly III. L\'importation de données depuis bunq et Specter est également prise en charge. D\'autres banques et agrégateurs financiers seront mis en place dans le futur. En tant qu\'utilisateur du site de démonstration, vous ne pouvez voir que le «faux» service en action. Il va générer des transactions aléatoires pour vous montrer comment se déroule le processus.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 9366f83e3d..b08e26de14 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -820,7 +820,7 @@ return [ 'language' => 'Langage', 'new_savings_account' => ':bank_name compte d\'épargne', 'cash_wallet' => 'Porte-monnaie', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'Si la devise que vous utilisez habituellement n\'est pas répertoriée, ne vous inquiétez pas. Vous pouvez créer vos propres devises dans Options > Devises.', // home page: 'yourAccounts' => 'Vos comptes', @@ -1018,7 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Retirer l’argent de la tirelire ":name"', 'add' => 'Ajouter', 'no_money_for_piggy' => 'Vous n\'avez pas d\'argent à placer dans cette tirelire.', - 'suggested_savings_per_month' => 'Suggested per month', + 'suggested_savings_per_month' => 'Suggéré par mois', 'remove' => 'Enlever', 'max_amount_add' => 'Le montant maximum que vous pouvez ajouter est', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Configuration', 'firefly_instance_configuration' => 'Options de configuration pour Firefly III', 'setting_single_user_mode' => 'Mode utilisateur unique', - 'setting_single_user_mode_explain' => 'Par défaut, Firefly III accepte uniquement un (1) enregistrement : vous. Il s\'agit d\'une mesure de sécurité qui empêche les autres d\'utiliser votre instance, à moins que vous ne les autorisiez. Les enregistrements futurs sont bloqués. Lorsque vous désactivez cette case, d\'autres personnes peuvent utiliser votre instance aussi bien, en supposant qu\'elles puissent l\'atteindre (quand il est connecté à Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Sauvegarder la configuration', 'single_user_administration' => 'Gestion de l\'utilisateur pour :email', 'edit_user' => 'Modifier l\'utilisateur :email', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Vous ne pouvez pas convertir une transaction ventilée', // Import page (general strings only) - 'import_index_title' => 'Importer des données dans Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importer des données', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Cette fonction n\'est pas disponible lorsque vous utilisez Firefly III dans un environnement Sandstorm.io.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'Vous n\'avez pas encore de factures. Vous pouvez créer des factures pour suivre les dépenses ordinaires, comme votre loyer ou l\'assurance.', 'no_bills_imperative_default' => 'Avez-vous des factures régulières ? Créez une facture et suivez vos paiements :', 'no_bills_create_default' => 'Créer une facture', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index 5c57f4717a..22089a8599 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -35,12 +35,12 @@ return [ 'amount_min' => 'Montant minimum', 'amount_max' => 'Montant maximum', 'match' => 'Correspondre à', - 'strict' => 'Strict mode', + 'strict' => 'Mode strict', 'repeat_freq' => 'Répétitions', 'journal_currency_id' => 'Devise', 'currency_id' => 'Devise', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', + 'transaction_currency_id' => 'Devise', + 'external_ip' => 'L\'adresse IP externe de votre serveur', 'attachments' => 'Documents joints', 'journal_amount' => 'Montant', 'journal_source_account_name' => 'Compte de recettes (source)', @@ -95,8 +95,8 @@ return [ 'convert_Transfer' => 'Convertir le transfert', 'amount' => 'Montant', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', + 'foreign_amount' => 'Montant externe', + 'existing_attachments' => 'Pièces jointes existantes', 'date' => 'Date', 'interest_date' => 'Date de l’intérêt', 'book_date' => 'Date de réservation', @@ -186,10 +186,10 @@ return [ 'blocked_code' => 'Raison du blocage', // import - 'apply_rules' => 'Apply rules', - 'artist' => 'Artist', + 'apply_rules' => 'Appliquer les règles', + 'artist' => 'Artiste', 'album' => 'Album', - 'song' => 'Song', + 'song' => 'Titre', // admin @@ -210,17 +210,28 @@ return [ 'client_id' => 'Identifiant', 'service_secret' => 'Secret de service', 'app_secret' => 'Secret d\'application', - 'app_id' => 'App ID', + 'app_id' => 'ID App', 'secret' => 'Secret', 'public_key' => 'Clé publique', 'country_code' => 'Code pays', 'provider_code' => 'Banque ou fournisseur de données', - 'due_date' => 'Échéance', - 'payment_date' => 'Date de paiement', - 'invoice_date' => 'Date de facturation', - 'internal_reference' => 'Référence interne', - 'inward' => 'Description vers l’intérieur', - 'outward' => 'Description de l’extérieur', - 'rule_group_id' => 'Groupe de règles', + 'due_date' => 'Échéance', + 'payment_date' => 'Date de paiement', + 'invoice_date' => 'Date de facturation', + 'internal_reference' => 'Référence interne', + 'inward' => 'Description vers l’intérieur', + 'outward' => 'Description de l’extérieur', + 'rule_group_id' => 'Groupe de règles', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index a111bb834e..c3117e41a3 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -24,33 +24,33 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Import data into Firefly III', - 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', - 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', - 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', - 'job_configuration_breadcrumb' => 'Configuration for ":key"', - 'job_status_breadcrumb' => 'Import status for ":key"', - 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + 'index_breadcrumb' => 'Importer des données dans Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prérequis pour la simulation d\'importation', + 'prerequisites_breadcrumb_spectre' => 'Prérequis pour Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prérequis pour bunq', + 'job_configuration_breadcrumb' => 'Configuration pour ":key"', + 'job_status_breadcrumb' => 'Statut d\'importation pour ":key"', + 'cannot_create_for_provider' => 'Firefly III ne peut pas créer de tâche pour le fournisseur ":provider".', // index page: - 'general_index_title' => 'Import a file', - 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + 'general_index_title' => 'Importer un fichier', + 'general_index_intro' => 'Bienvenue dans la routine d\'importation de Firefly III. Il existe différentes façons d\'importer des données dans Firefly III, affichées ici sous forme de boutons.', // import provider strings (index): - 'button_fake' => 'Fake an import', - 'button_file' => 'Import a file', - 'button_bunq' => 'Import from bunq', - 'button_spectre' => 'Import using Spectre', - 'button_plaid' => 'Import using Plaid', - 'button_yodlee' => 'Import using Yodlee', - 'button_quovo' => 'Import using Quovo', + 'button_fake' => 'Simuler une importation', + 'button_file' => 'Importer un fichier', + 'button_bunq' => 'Importer depuis bunq', + 'button_spectre' => 'Importer en utilisant Spectre', + 'button_plaid' => 'Importer en utilisant Plaid', + 'button_yodlee' => 'Importer en utilisant Yodlee', + 'button_quovo' => 'Importer en utilisant Quovo', // global config box (index) - 'global_config_title' => 'Global import configuration', - 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + 'global_config_title' => 'Configuration d\'importation globale', + 'global_config_text' => 'À l\'avenir, cette boîte contiendra les préférences qui s\'appliquent à TOUTES les sources d\'importation ci-dessus.', // prerequisites box (index) - 'need_prereq_title' => 'Import prerequisites', - 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', - 'do_prereq_fake' => 'Prerequisites for the fake provider', - 'do_prereq_file' => 'Prerequisites for file imports', + 'need_prereq_title' => 'Prérequis d\'importation', + 'need_prereq_intro' => 'Certaines méthodes d\'importation nécessitent votre attention avant de pouvoir être utilisées. Par exemple, elles peuvent nécessiter des clés d\'API spéciales ou des clés secrètes. Vous pouvez les configurer ici. L\'icône indique si ces conditions préalables ont été remplies.', + 'do_prereq_fake' => 'Prérequis pour la simulation', + 'do_prereq_file' => 'Prérequis pour les importations de fichiers', 'do_prereq_bunq' => 'Prerequisites for imports from bunq', 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', @@ -204,61 +204,61 @@ return [ 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'unknown_import_result' => 'Unknown import result', 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', - 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', - 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', + 'result_one_transaction' => 'Une seule transaction a été importée. Elle est stockée sous le tag :tag où vous pouvez l\'afficher en détail.', + 'result_many_transactions' => 'Firefly III a importé :count transactions. Elles sont stockées sous le tag :tag où vous pouvez les afficher en détail.', // general errors and warnings: - 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', + 'bad_job_status' => 'Vous ne pouvez pas accéder à cette page tant que l\'importation a le statut ":status".', // column roles for CSV import: 'column__ignore' => '(ignorer cette colonne)', 'column_account-iban' => 'Compte d’actif (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-id' => 'Compte d\'actif (ID correspondant à FF3)', 'column_account-name' => 'Compte d’actif (nom)', 'column_amount' => 'Montant', - 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_foreign' => 'Montant (en devise étrangère)', 'column_amount_debit' => 'Montant (colonne débit)', 'column_amount_credit' => 'Montant (colonne de crédit)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_amount-comma-separated' => 'Montant (virgule comme séparateur décimal)', + 'column_bill-id' => 'Facture (ID correspondant à FF3)', 'column_bill-name' => 'Nom de la facture', - 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-id' => 'Budget (ID correspondant à FF3)', 'column_budget-name' => 'Nom du budget', - 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-id' => 'Catégorie (ID correspondant à FF3)', 'column_category-name' => 'Nom de catégorie', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_foreign-currency-code' => 'Foreign currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching FF3)', - 'column_currency-name' => 'Currency name (matching FF3)', - 'column_currency-symbol' => 'Currency symbol (matching FF3)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', + 'column_currency-code' => 'Code de la devise (ISO 4217)', + 'column_foreign-currency-code' => 'Code de devise étrangère (ISO 4217)', + 'column_currency-id' => 'Devise (ID correspondant à FF3)', + 'column_currency-name' => 'Nom de la devise (correspondant à FF3)', + 'column_currency-symbol' => 'Symbole de la devise (correspondant à FF3)', + 'column_date-interest' => 'Date de calcul des intérêts', + 'column_date-book' => 'Date d\'enregistrement de la transaction', + 'column_date-process' => 'Date de traitement de la transaction', 'column_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', + 'column_date-due' => 'Date d\'échéance de la transaction', + 'column_date-payment' => 'Date de paiement de la transaction', + 'column_date-invoice' => 'Date de facturation de la transaction', 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_opposing-iban' => 'Compte destinataire (IBAN)', + 'column_opposing-bic' => 'Compte destinataire (BIC)', + 'column_opposing-id' => 'Compte destinataire (ID correspondant à FF3)', 'column_external-id' => 'ID externe', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', - 'column_ing-debit-credit' => 'ING specific debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', + 'column_opposing-name' => 'Compte destinataire (nom)', + 'column_rabo-debit-credit' => 'Indicateur de débit/crédit spécifique à Rabobank', + 'column_ing-debit-credit' => 'Indicateur de débit/crédit spécifique à ING', + 'column_sepa-ct-id' => 'Référence de bout en bout SEPA', + 'column_sepa-ct-op' => 'Référence SEPA du compte destinataire', + 'column_sepa-db' => 'Référence Unique de Mandat SEPA', + 'column_sepa-cc' => 'Code de rapprochement SEPA', + 'column_sepa-ci' => 'Identifiant Créancier SEPA', + 'column_sepa-ep' => 'Objectif externe SEPA', + 'column_sepa-country' => 'Code de pays SEPA', 'column_tags-comma' => 'Tags (séparés par des virgules)', 'column_tags-space' => 'Tags (séparé par un espace)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', + 'column_account-number' => 'Compte d’actif (numéro de compte)', + 'column_opposing-number' => 'Compte destinataire (numéro de compte)', 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', + 'column_internal-reference' => 'Référence interne', ]; diff --git a/resources/lang/fr_FR/intro.php b/resources/lang/fr_FR/intro.php index 103c316b23..cfed1ec1ca 100644 --- a/resources/lang/fr_FR/intro.php +++ b/resources/lang/fr_FR/intro.php @@ -93,7 +93,7 @@ return [ 'piggy-banks_show_piggyEvents' => 'Des ajouts ou suppressions sont également répertoriées ici.', // bill index - 'bills_index_rules' => 'Here you see which rules will check if this bill is hit', + 'bills_index_rules' => 'Ici, vous voyez quelles règles vont s\'appliquer si cette facture est payée', 'bills_index_paid_in_period' => 'Ce champ indique quand la facture a été payée pour la dernière fois.', 'bills_index_expected_in_period' => 'Ce champ indique pour chaque facture si et quand la facture suivante est attendue.', @@ -103,12 +103,12 @@ return [ 'bills_show_billChart' => 'Ce tableau montre les transactions liées à cette facture.', // create bill - 'bills_create_intro' => 'Use bills to track the amount of money you\'re due every period. Think about expenses like rent, insurance or mortgage payments.', + 'bills_create_intro' => 'Utilisez des factures pour suivre les sommes que vous avez à payer à chaque période. Pensez aux dépenses comme le loyer, l\'assurance ou les remboursements d\'emprunts.', 'bills_create_name' => 'Utilisez un nom équivoque tel que "Loyer" ou "Assurance maladie".', //'bills_create_match' => 'To match transactions, use terms from those transactions or the expense account involved. All words must match.', 'bills_create_amount_min_holder' => 'Sélectionnez un montant minimum et maximum pour cette facture.', 'bills_create_repeat_freq_holder' => 'La plupart des factures sont mensuelles, mais vous pouvez définir une autre fréquence ici.', - 'bills_create_skip_holder' => 'If a bill repeats every 2 weeks, the "skip"-field should be set to "1" to skip every other week.', + 'bills_create_skip_holder' => 'Si une facture se répète toutes les 2 semaines, le champ "sauter" doit être réglé sur "1" pour sauter une semaine sur deux.', // rules index 'rules_index_intro' => 'Firefly III vous permet de gérer les règles, qui seront automagiquement appliquées à toute transaction que vous créez ou modifiez.', diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index ccafd1e385..c2d0261328 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -34,7 +34,7 @@ return [ 'name' => 'Nom', 'role' => 'Rôle', 'currentBalance' => 'Solde courant', - 'linked_to_rules' => 'Relevant rules', + 'linked_to_rules' => 'Règles applicables', 'active' => 'Actif ?', 'lastActivity' => 'Activité récente', 'balanceDiff' => 'Différence d\'équilibre', @@ -112,15 +112,20 @@ return [ 'sepa-cc' => 'Code de compensation SEPA', 'sepa-ep' => 'Objectif externe SEPA', 'sepa-ci' => 'Identifiant SEPA Creditor', - 'external_id' => 'External ID', + 'external_id' => 'ID externe', 'account_at_bunq' => 'Compte avec bunq', - 'file_name' => 'File name', - 'file_size' => 'File size', - 'file_type' => 'File type', - 'attached_to' => 'Attached to', - 'file_exists' => 'File exists', - 'spectre_bank' => 'Bank', - 'spectre_last_use' => 'Last login', - 'spectre_status' => 'Status', - 'bunq_payment_id' => 'bunq payment ID', + 'file_name' => 'Nom du fichier', + 'file_size' => 'Taille du fichier', + 'file_type' => 'Type de fichier', + 'attached_to' => 'Attaché à', + 'file_exists' => 'Le fichier existe', + 'spectre_bank' => 'Banque', + 'spectre_last_use' => 'Dernière connexion', + 'spectre_status' => 'Statut', + 'bunq_payment_id' => 'ID de paiement bunq', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index 6ce0d06474..9a34e69703 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -110,8 +110,9 @@ return [ 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', 'present' => 'Le champs :attribute doit être rempli.', 'amount_zero' => 'Le montant total ne peut pas être zéro', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'unique_piggy_bank_for_user' => 'Le nom de la tirelire doit être unique.', + 'secure_password' => 'Ce n’est pas un mot de passe sécurisé. Veuillez essayer à nouveau. Pour plus d’informations, visitez http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'adresse email', 'description' => 'description', diff --git a/resources/lang/id_ID/config.php b/resources/lang/id_ID/config.php index 64659774cd..42f77dcca9 100644 --- a/resources/lang/id_ID/config.php +++ b/resources/lang/id_ID/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'id', - 'locale' => 'id, Bahasa Indonesia, id_ID, id_ID.utf8, id_ID.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Minggu %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'id', + 'locale' => 'id, Bahasa Indonesia, id_ID, id_ID.utf8, id_ID.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Minggu %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/id_ID/demo.php b/resources/lang/id_ID/demo.php index a62c929dc0..0489b67c41 100644 --- a/resources/lang/id_ID/demo.php +++ b/resources/lang/id_ID/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Biaya ini, deposito dan transfer tidak terlalu imajinatif. Mereka telah dihasilkan secara otomatis.', 'piggy-banks-index' => 'Seperti yang bisa Anda lihat, ada tiga celengan. Gunakan tombol plus dan minus untuk mempengaruhi jumlah uang di setiap celengan. Klik nama celengan untuk melihat administrasi masing-masing celengan.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 348ed93b3a..3476f636e4 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -820,7 +820,7 @@ return [ 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Akun anda', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Konfigurasi', 'firefly_instance_configuration' => 'Pilihan konfigurasi untuk Firefly III', 'setting_single_user_mode' => 'Mode pengguna tunggal', - 'setting_single_user_mode_explain' => 'Secara default, Firefly III hanya menerima satu (1) registrasi: anda. Ini adalah tindakan pengamanan, mencegah orang lain menggunakan contoh Anda kecuali jika Anda mengizinkannya melakukannya. Pendaftaran di masa depan diblokir Bila Anda tidak mencentang kotak ini, orang lain dapat menggunakan contoh Anda dengan baik, dengan asumsi mereka dapat mencapainya (bila terhubung ke internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Konfigurasi toko', 'single_user_administration' => 'Administrasi pengguna untuk :email', 'edit_user' => 'Edit pengguna :email', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Tidak dapat mengonversi transaksi split', // Import page (general strings only) - 'import_index_title' => 'Impor data ke Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Impor data', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Fungsi ini tidak tersedia saat Anda menggunakan Firefly III di dalam lingkungan Sandstorm.io.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'Anda belum memiliki tagihan. Anda bisa membuat tagihan untuk mencatat pengeluaran rutin, seperti sewa atau asuransi Anda.', 'no_bills_imperative_default' => 'Apakah Anda memiliki tagihan reguler seperti itu? Buat tagihan dan lacak pembayaran Anda:', 'no_bills_create_default' => 'Buat tagihan', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index 6dd307210f..f0ce510ae0 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Kode negara', 'provider_code' => 'Bank atau penyedia data', - 'due_date' => 'Batas tanggal terakhir', - 'payment_date' => 'Tanggal pembayaran', - 'invoice_date' => 'Tanggal faktur', - 'internal_reference' => 'Referensi internal', - 'inward' => 'Deskripsi dalam', - 'outward' => 'Deskripsi luar', - 'rule_group_id' => 'Kelompok aturan', + 'due_date' => 'Batas tanggal terakhir', + 'payment_date' => 'Tanggal pembayaran', + 'invoice_date' => 'Tanggal faktur', + 'internal_reference' => 'Referensi internal', + 'inward' => 'Deskripsi dalam', + 'outward' => 'Deskripsi luar', + 'rule_group_id' => 'Kelompok aturan', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/id_ID/list.php b/resources/lang/id_ID/list.php index d64698938c..98106680de 100644 --- a/resources/lang/id_ID/list.php +++ b/resources/lang/id_ID/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index 00be6153a5..7b662fd211 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'Jumlah total tidak boleh nol', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'email address', 'description' => 'description', diff --git a/resources/lang/it_IT/config.php b/resources/lang/it_IT/config.php index 667e347c13..69ac82d6e0 100644 --- a/resources/lang/it_IT/config.php +++ b/resources/lang/it_IT/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'it', - 'locale' => 'it, Italiano, it_IT, it_IT.utf8, it_IT.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Settimana %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM AAAA', - 'month_and_day_js' => 'Do MMMM YYYY', - 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', - 'specific_day_js' => 'G MMMM AAAA', - 'week_in_year_js' => '[Week] s, AAAA', - 'year_js' => 'AAAA', - 'half_year_js' => 'T AAAA', + 'html_language' => 'it', + 'locale' => 'it, Italiano, it_IT, it_IT.utf8, it_IT.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Settimana %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM AAAA', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'G MMMM AAAA', + 'week_in_year_js' => '[Week] s, AAAA', + 'year_js' => 'AAAA', + 'half_year_js' => 'T AAAA', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/it_IT/demo.php b/resources/lang/it_IT/demo.php index 38c210d8fb..bf37e30f8f 100644 --- a/resources/lang/it_IT/demo.php +++ b/resources/lang/it_IT/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Queste spese, depositi e trasferimenti non sono particolarmente fantasiosi. Sono stati generati automaticamente.', 'piggy-banks-index' => 'Come puoi vedere, ci sono tre salvadanai. Utilizzare i pulsanti più e meno per influenzare la quantità di denaro in ogni salvadanaio. Fare clic sul nome del salvadanaio per visualizzare la gestione per ciascun salvadanaio.', 'import-index' => 'Qualsiasi file CSV può essere importato in Firefly III. Supporta anche l\'importazione di dati da bunq e Spectre. Altre banche e aggregatori finanziari saranno implementati in futuro. Tuttavia, come utente demo, puoi vedere solo il provider "fittizio" in azione. Genererà alcune transazioni casuali per mostrarti come funziona il processo.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 0532dcd126..7835737f09 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -820,11 +820,11 @@ return [ 'language' => 'Lingua', 'new_savings_account' => 'Conto di risparmio :bank_name', 'cash_wallet' => 'Contanti', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'Se la valuta che usi normalmente non è elencata, non preoccuparti. Puoi creare le tue valute in Opzioni > Valute.', // home page: 'yourAccounts' => 'I tuoi conti', - 'budgetsAndSpending' => 'Bilanci e Spese', + 'budgetsAndSpending' => 'Budget e spese', 'savings' => 'Risparmi', 'newWithdrawal' => 'Nuova uscita', 'newDeposit' => 'Nuovo deposito', @@ -849,7 +849,7 @@ return [ 'Expense account' => 'Conto spese', 'Revenue account' => 'Conto entrate', 'Initial balance account' => 'Saldo iniziale conto', - 'budgets' => 'Bilanci', + 'budgets' => 'Budget', 'tags' => 'Etichette', 'reports' => 'Resoconti', 'transactions' => 'Transazioni', @@ -947,7 +947,7 @@ return [ 'expense_per_account' => 'Spese per conto', 'expense_per_tag' => 'Spese per etichetta', 'income_per_tag' => 'Reddito per etichetta', - 'include_expense_not_in_budget' => 'Spese non incluse nel(nei) bilancio(i) selezionato(i)', + 'include_expense_not_in_budget' => 'Spese non incluse nei budget selezionati', 'include_expense_not_in_account' => 'Spese non incluse nel(nei) conto(i) selezionato(i)', 'include_expense_not_in_category' => 'Spese incluse / non incluse nella(e) categoria(e) selezionata(e)', 'include_income_not_in_category' => 'Entrate non incluse nella(e) categoria(e) selezionata(e)', @@ -1018,7 +1018,7 @@ return [ 'remove_money_from_piggy_title' => 'Rimuovi i soldi dal salvadanaio ":name"', 'add' => 'Aggiungi', 'no_money_for_piggy' => 'non hai soldi da mettere in questo salvadanaio.', - 'suggested_savings_per_month' => 'Suggested per month', + 'suggested_savings_per_month' => 'Suggeriti per mese', 'remove' => 'Rimuovi', 'max_amount_add' => 'L\'importo massimo che puoi aggiungere è', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Configurazione', 'firefly_instance_configuration' => 'Opzioni Configurazione di Firefly III', 'setting_single_user_mode' => 'Modo utente singolo', - 'setting_single_user_mode_explain' => 'Per impostazione predefinita, Firefly III accetta solo una (1) registrazione: tu. Questa è una misura di sicurezza, che impedisce ad altri di usare la tua istanza a meno che tu non le autorizzi. Le future registrazioni sono bloccate. Bene! quando deselezioni questa casella, gli altri possono usare la tua istanza, supponendo che possano raggiungerla (quando è connessa a Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Salva configurazione', 'single_user_administration' => 'Amministrazione utenti per :email', 'edit_user' => 'Modifica utente :email', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Impossibile convertire una transazione divisa', // Import page (general strings only) - 'import_index_title' => 'Importa i dati in Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importa i dati', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Questa funzione non è disponibile quando si utilizza Firefly III in un ambiente Sandstorm.io.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'Non hai ancora nessuna bolletta. Puoi creare bollette per tenere traccia delle spese regolari, come il tuo affitto o l\'assicurazione.', 'no_bills_imperative_default' => 'Hai delle bollette regolari? Crea una bolletta e tieni traccia dei tuoi pagamenti:', 'no_bills_create_default' => 'Crea bolletta', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index 2c47c1601f..c52706c4e7 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Codice Nazione', 'provider_code' => 'Banca o fornitore di dati', - 'due_date' => 'Data scadenza', - 'payment_date' => 'Data pagamento', - 'invoice_date' => 'Data bolletta', - 'internal_reference' => 'Referenze interne', - 'inward' => 'Descrizione interna', - 'outward' => 'Descrizione esterna', - 'rule_group_id' => 'Gruppo regole', + 'due_date' => 'Data scadenza', + 'payment_date' => 'Data pagamento', + 'invoice_date' => 'Data bolletta', + 'internal_reference' => 'Referenze interne', + 'inward' => 'Descrizione interna', + 'outward' => 'Descrizione esterna', + 'rule_group_id' => 'Gruppo regole', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index d91182b40d..44210b0a23 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -131,8 +131,8 @@ return [ 'job_config_bunq_accounts_title' => 'Account bunq', 'job_config_bunq_accounts_text' => 'Questi sono i conti associati al tuo account bunq. Seleziona i conti dai quali vuoi effettuare l\'importazione e in quale conto devono essere importate le transazioni.', 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato nessun account.', - 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', - 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + 'should_download_config' => 'Ti consigliamo di scaricare il file di configurazione per questo lavoro. Ciò renderà le importazioni future più facili.', + 'share_config_file' => 'Se hai importato dati da una banca pubblica, dovresti condividere il tuo file di configurazione così da rendere più facile per gli altri utenti importare i loro dati. La condivisione del file di configurazione non espone i tuoi dettagli finanziari.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/it_IT/list.php b/resources/lang/it_IT/list.php index 4b53c93bf8..dbece38b90 100644 --- a/resources/lang/it_IT/list.php +++ b/resources/lang/it_IT/list.php @@ -122,5 +122,10 @@ return [ 'spectre_bank' => 'Banca', 'spectre_last_use' => 'Ultimo accesso', 'spectre_status' => 'Stato', - 'bunq_payment_id' => 'bunq payment ID', + 'bunq_payment_id' => 'ID pagamento bunq', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index f6c286443a..944289803d 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -110,8 +110,9 @@ return [ 'in_array' => ':attribute il campo non esiste in :other.', 'present' => ':attribute il campo deve essere presente.', 'amount_zero' => 'L\'importo totale non può essere zero', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', + 'unique_piggy_bank_for_user' => 'Il nome del salvadanaio deve essere unico.', 'secure_password' => 'Questa non è una password sicura. Per favore riprova. Per ulteriori informazioni, visitare http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'indirizzo email', 'description' => 'descrizione', diff --git a/resources/lang/nl_NL/config.php b/resources/lang/nl_NL/config.php index d2e66fcf86..aa5530210a 100644 --- a/resources/lang/nl_NL/config.php +++ b/resources/lang/nl_NL/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'nl', - 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8, nl_NL.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'week %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'D MMMM YYYY', - 'date_time_js' => 'D MMMM YYYY @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'nl', + 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8, nl_NL.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'week %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'D MMMM YYYY', + 'date_time_js' => 'D MMMM YYYY @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Maandag', + 'dow_2' => 'Dinsdag', + 'dow_3' => 'Woensdag', + 'dow_4' => 'Donderdag', + 'dow_5' => 'Vrijdag', + 'dow_6' => 'Zaterdag', + 'dow_7' => 'Zondag', ]; diff --git a/resources/lang/nl_NL/demo.php b/resources/lang/nl_NL/demo.php index 5f6ed1f0b2..d8b739c605 100644 --- a/resources/lang/nl_NL/demo.php +++ b/resources/lang/nl_NL/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Deze uitgaven, inkomsten en overschrijvingen zijn niet heel fantasierijk. Ze zijn automatisch gegenereerd.', 'piggy-banks-index' => 'Zoals je kan zien zijn er drie spaarpotjes. Gebruik de plus- en minknoppen om het bedrag in de spaarpotjes te veranderen. Klik op de naam van het spaarpotje om er de geschiedenis van te zien.', 'import-index' => 'Je kan elk CSV bestand importeren met Firefly III. Het is ook mogelijk om transacties te importeren van bunq en Spectre. Andere verzamelbedrijven worden ook geïntegreerd. De demo-gebruiker mag alleen de "nep"-importhulp gebruiken. Deze genereert een paar willekeurige transacties om te laten zien hoe het werkt.', + 'recurring-index' => 'Denk er aan dat deze functie nog ontwikkeld wordt en misschien niet lekker werkt.', + 'recurring-create' => 'Denk er aan dat deze functie nog ontwikkeld wordt en misschien niet lekker werkt.', ]; diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 051852c28d..cc3c55ffdf 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -820,7 +820,7 @@ return [ 'language' => 'Taal', 'new_savings_account' => ':bank_name spaarrekening', 'cash_wallet' => 'Cash-rekening', - 'currency_not_present' => 'Geen zorgen als de valuta die je gewend bent er niet tussen staat. Je kan je eigen valuta maken onder Opties > Valuta.', + 'currency_not_present' => 'Geen zorgen als de valuta die je gewend bent er niet tussen staat. Je kan je eigen valuta maken onder Opties > Valuta.', // home page: 'yourAccounts' => 'Je betaalrekeningen', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Kan geen gesplitste transactie omzetten', // Import page (general strings only) - 'import_index_title' => 'Gegevens importeren in Firefly III', + 'import_index_title' => 'Transacties importeren in Firefly III', 'import_data' => 'Importeer data', + 'import_transactions' => 'Importeer transacties', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Deze functie werkt niet als je Firefly III gebruikt in combinatie met Sandstorm.IO.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'Je hebt nog geen contracten. Je kan contracten gebruiken om terugkerende uitgaven bij te houden, zoals de huur of verzekeringen.', 'no_bills_imperative_default' => 'Heb je zulke uitgaven? Maak dan een contract en houd de betalingen bij:', 'no_bills_create_default' => 'Maak een contract', + + // recurring transactions + 'recurrences' => 'Periodieke transacties', + 'no_recurring_title_default' => 'Maak een periodieke transactie!', + 'no_recurring_intro_default' => 'Je hebt nog geen periodieke transacties. Je kan deze gebruiken om er voor te zorgen dat Firefly III automatisch nieuwe transacties voor je maakt.', + 'no_recurring_imperative_default' => 'Dit is een behoorlijk geadvanceerde functie maar het kan vreselijk handig zijn. Lees ook zeker de documentatie (?)-icoontje rechtsboven) voor je verder gaat.', + 'no_recurring_create_default' => 'Maak een periodieke transactie', + 'make_new_recurring' => 'Maak een periodieke transactie', + 'recurring_daily' => 'Elke dag', + 'recurring_weekly' => 'Elke week op :weekday', + 'recurring_monthly' => 'Elke maand op de :dayOfMonth(e) dag', + 'recurring_ndom' => 'Elke maand op de :dayOfMonth(e) :weekday', + 'recurring_yearly' => 'Elk jaar op :date', + 'overview_for_recurrence' => 'Overzicht voor periodieke transactie ":title"', + 'warning_duplicates_repetitions' => 'Soms zie je hier datums dubbel staan. Dat kan als meerdere herhalingen in elkaars vaarwater zitten. Firefly III maakt altijd maar één transactie per dag.', + 'created_transactions' => 'Gerelateerde transacties', + 'expected_Withdrawals' => 'Verwachte uitgaven', + 'expected_Deposits' => 'Verwachte inkomsten', + 'expected_Transfers' => 'Verwachte overschrijvingen', + 'created_Withdrawals' => 'Gemaakte uitgaven', + 'created_Deposits' => 'Gemaakte inkomsten', + 'created_Transfers' => 'Gemaakte overschrijvingen', + 'created_from_recurrence' => 'Gemaakt door periodieke transactie ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notities', + 'recurring_meta_field_bill_id' => 'Contract', + 'recurring_meta_field_piggy_bank_id' => 'Spaarpotje', + 'create_new_recurrence' => 'Maak een nieuwe periodieke transactie', + 'help_first_date' => 'Geef aan wanneer je de eerste transactie verwacht. Dit moet in de toekomst zijn.', + 'help_first_date_no_past' => 'Geef aan wanneer je de eerste transactie verwacht. Firefly III zal geen transacties in het verleden maken.', + 'no_currency' => '(geen valuta)', + 'mandatory_for_recurring' => 'Verplichte periodieke informatie', + 'mandatory_for_transaction' => 'Verplichte transactieinformatie', + 'optional_for_recurring' => 'Optionele periodieke informatie', + 'optional_for_transaction' => 'Optionele transactieinformatie', + 'change_date_other_options' => 'Wijzig de "eerste datum" om meer opties te zien.', + 'mandatory_fields_for_tranaction' => 'De waarden die je hier invult worden gebruikt om de transactie(s) te maken', + 'click_for_calendar' => 'Hier vind je een kalender die laat zien wanneer de transactie zal herhalen.', + 'repeat_forever' => 'Voor altijd herhalen', + 'repeat_until_date' => 'Herhalen tot een datum', + 'repeat_times' => 'Een aantal maal herhalen', + 'recurring_skips_one' => 'Elke tweede', + 'recurring_skips_more' => 'Slaat :count keer over', + 'store_new_recurrence' => 'Sla periodieke transactie op', + 'stored_new_recurrence' => 'Periodieke transactie ":title" is opgeslagen.', + 'edit_recurrence' => 'Wijzig periodieke transactie ":title"', + 'recurring_repeats_until' => 'Herhaalt tot :date', + 'recurring_repeats_forever' => 'Blijft herhalen', + 'recurring_repeats_x_times' => 'Herhaalt :count keer', + 'update_recurrence' => 'Wijzig periodieke transactie', + 'updated_recurrence' => 'Periodieke transactie ":title" is gewijzigd', + 'recurrence_is_inactive' => 'Deze periodieke transactie is niet actief en maakt geen transacties aan.', ]; diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index 748af68a19..e83fb084f0 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Landcode', 'provider_code' => 'Bank of gegevensprovider', - 'due_date' => 'Vervaldatum', - 'payment_date' => 'Betalingsdatum', - 'invoice_date' => 'Factuurdatum', - 'internal_reference' => 'Interne verwijzing', - 'inward' => 'Binnenwaartse beschrijving', - 'outward' => 'Buitenwaartse beschrijving', - 'rule_group_id' => 'Regelgroep', + 'due_date' => 'Vervaldatum', + 'payment_date' => 'Betalingsdatum', + 'invoice_date' => 'Factuurdatum', + 'internal_reference' => 'Interne verwijzing', + 'inward' => 'Binnenwaartse beschrijving', + 'outward' => 'Buitenwaartse beschrijving', + 'rule_group_id' => 'Regelgroep', + 'transaction_description' => 'Transactiebeschrijving', + 'first_date' => 'Eerste datum', + 'transaction_type' => 'Transactietype', + 'repeat_until' => 'Herhalen tot', + 'recurring_description' => 'Beschrijving van de periodieke transactie', + 'repetition_type' => 'Type herhaling', + 'foreign_currency_id' => 'Vreemde valuta', + 'repetition_end' => 'Stopt met herhalen', + 'repetitions' => 'Herhalingen', + 'calendar' => 'Kalender', + ]; diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php index cd17c28646..db765f8c4f 100644 --- a/resources/lang/nl_NL/list.php +++ b/resources/lang/nl_NL/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Laatst ingelogd', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq betalings-ID', + 'repetitions' => 'Herhalingen', + 'title' => 'Titel', + 'transaction_s' => 'Transactie(s)', + 'field' => 'Veld', + 'value' => 'Waarde', ]; diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index d5d0537e2b..b796f36a22 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'Het totaalbedrag kan niet nul zijn', 'unique_piggy_bank_for_user' => 'De naam van de spaarpot moet uniek zijn.', 'secure_password' => 'Dit is geen sterk wachtwoord. Probeer het nog een keer. Zie ook: http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Dit is geen geldige herhaling voor periodieke transacties', 'attributes' => [ 'email' => 'e-mailadres', 'description' => 'omschrijving', diff --git a/resources/lang/pl_PL/config.php b/resources/lang/pl_PL/config.php index eedc86c9af..f4e8b2660f 100644 --- a/resources/lang/pl_PL/config.php +++ b/resources/lang/pl_PL/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'pl', - 'locale' => 'pl, Polish, polski, pl_PL, pl_PL.utf8, pl_PL.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y o %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Tydzień %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'D MMMM YYYY', - 'date_time_js' => 'D MMMM YYYY [o] HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Tydzień] w. YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'pl', + 'locale' => 'pl, Polish, polski, pl_PL, pl_PL.utf8, pl_PL.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y o %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Tydzień %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'D MMMM YYYY', + 'date_time_js' => 'D MMMM YYYY [o] HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Tydzień] w. YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/pl_PL/demo.php b/resources/lang/pl_PL/demo.php index b4ca24333f..9c1d4678a3 100644 --- a/resources/lang/pl_PL/demo.php +++ b/resources/lang/pl_PL/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Te wydatki, depozyty i transfery nie są szczególnie pomysłowe. Zostały wygenerowane automatycznie.', 'piggy-banks-index' => 'Jak widać, istnieją trzy skarbonki. Użyj przycisków plus i minus, aby wpłynąć na ilość pieniędzy w każdej skarbonce. Kliknij nazwę skarbonki, aby zobaczyć administrację każdej skarbonki.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index c904d80cd6..2b3e7cb7ad 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -820,7 +820,7 @@ return [ 'language' => 'Język', 'new_savings_account' => 'Konto oszczędnościowe :bank_name', 'cash_wallet' => 'Portfel gotówkowy', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Twoje konta', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Konfiguracja', 'firefly_instance_configuration' => 'Opcje konfiguracji dla Firefly III', 'setting_single_user_mode' => 'Tryb pojedynczego użytkownika', - 'setting_single_user_mode_explain' => 'Domyślnie, Firefly III pozwala na jednego (1) użytkownika: Ciebie. Jest to środek bezpieczeństwa uniemożliwiający innym używanie Twojej instalacji, chyba że im pozwolisz. Kolejne rejestracje są zablokowane. Jeżeli odznaczysz to pole, inne osoby będą mogły używać Twojej instalacji Firefly III (zakładając, że jest ona dostępna w Internecie).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Zapisz konfigurację', 'single_user_administration' => 'Administracja użytkownika dla :email', 'edit_user' => 'Modyfikuj użytkownika :email', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Nie można przekonwertować podzielonej transakcji', // Import page (general strings only) - 'import_index_title' => 'Importuj dane do Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importuj dane', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Ta funkcja nie jest dostępna, gdy używasz Firefly III w środowisku Sandstorm.io.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'Nie masz jeszcze żadnych rachunków. Można tworzyć rachunki, aby śledzić regularne wydatki, takie jak czynsz czy ubezpieczenie.', 'no_bills_imperative_default' => 'Czy masz takie regularne rachunki? Utwórz rachunek i śledź swoje płatności:', 'no_bills_create_default' => 'Utwórz rachunek', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index e72c91843f..72e31f8995 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Kod kraju', 'provider_code' => 'Dostawca banku lub danych', - 'due_date' => 'Termin realizacji', - 'payment_date' => 'Data płatności', - 'invoice_date' => 'Data faktury', - 'internal_reference' => 'Wewnętrzny numer', - 'inward' => 'Opis wewnętrzny', - 'outward' => 'Opis zewnętrzny', - 'rule_group_id' => 'Grupa reguł', + 'due_date' => 'Termin realizacji', + 'payment_date' => 'Data płatności', + 'invoice_date' => 'Data faktury', + 'internal_reference' => 'Wewnętrzny numer', + 'inward' => 'Opis wewnętrzny', + 'outward' => 'Opis zewnętrzny', + 'rule_group_id' => 'Grupa reguł', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index a18fe7286e..e8da734c85 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index 0ae9b71c19..c2af23c82d 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'Całkowita kwota nie może być zerem', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'adres e-mail', 'description' => 'opis', diff --git a/resources/lang/pt_BR/config.php b/resources/lang/pt_BR/config.php index ce507e8a05..f0401d93b3 100644 --- a/resources/lang/pt_BR/config.php +++ b/resources/lang/pt_BR/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'pt-br', - 'locale' => 'pt-br, pt_BR, pt_BR.utf8, pt_BR.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e de %B de %Y', - 'date_time' => '%B %e, %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Semana %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'pt-br', + 'locale' => 'pt-br, pt_BR, pt_BR.utf8, pt_BR.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e de %B de %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semana %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/pt_BR/demo.php b/resources/lang/pt_BR/demo.php index b6960bda12..a52519475f 100644 --- a/resources/lang/pt_BR/demo.php +++ b/resources/lang/pt_BR/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Estas despesas, depósitos e transferências não são fantasiosas. Elas foram geradas automaticamente.', 'piggy-banks-index' => 'Como você pode ver, existem três cofrinhos. Use o sinal de mais e menos botões para influenciar a quantidade de dinheiro em cada cofrinho. Clique no nome do cofrinho para ver a administração de cada cofrinho.', 'import-index' => 'Qualquer arquivo CSV pode ser importado para o Firefly III. Importações de dados de bunq e Specter também são suportadas. Outros bancos e agregadores financeiros serão implementados futuramente. Como usuário de demonstração, no entanto, você só pode ver o provedor "falso" em ação. Ele irá gerar transações aleatórias para lhe mostrar como funciona o processo.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 988bf0b85a..b998795ff1 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -820,7 +820,7 @@ return [ 'language' => 'Idioma', 'new_savings_account' => 'Conta de poupança :bank_name', 'cash_wallet' => 'Carteira de dinheiro', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Suas contas', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Configuração', 'firefly_instance_configuration' => 'Opções de configuração para Firefly III', 'setting_single_user_mode' => 'Modo de usuário único', - 'setting_single_user_mode_explain' => 'Por padrão, o Firefly III aceita apenas um (1) registro: você. Esta é uma medida de segurança, impedindo que outros usem sua instância, a menos que você permita. Os registros futuros estão bloqueados. Quando você desmarca essa caixa, outros podem usar sua instância, assumindo que podem alcançá-la (quando conectados à Internet).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Configuração da Loja', 'single_user_administration' => 'Administração de usuários para :email', 'edit_user' => 'Editar usuário :email', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Não é possível converter uma transação dividida', // Import page (general strings only) - 'import_index_title' => 'Importar dados para o Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importar dados', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Esta função não está disponível quando você está usando o Firefly III dentro de um ambiente Sandstorm.io.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'Você ainda não possui faturas. Você pode criar faturas para acompanhar as despesas regulares, como sua renda ou seguro.', 'no_bills_imperative_default' => 'Você tem essas faturas regulares? Crie uma fatura e acompanhe seus pagamentos:', 'no_bills_create_default' => 'Criar uma fatura', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index f31a9d819a..385f8fe893 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Código do país', 'provider_code' => 'Banco ou provedor de dados', - 'due_date' => 'Data de vencimento', - 'payment_date' => 'Data de pagamento', - 'invoice_date' => 'Data da Fatura', - 'internal_reference' => 'Referência interna', - 'inward' => 'Descrição interna', - 'outward' => 'Descrição externa', - 'rule_group_id' => 'Grupo de regras', + 'due_date' => 'Data de vencimento', + 'payment_date' => 'Data de pagamento', + 'invoice_date' => 'Data da Fatura', + 'internal_reference' => 'Referência interna', + 'inward' => 'Descrição interna', + 'outward' => 'Descrição externa', + 'rule_group_id' => 'Grupo de regras', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php index f65a5eee64..5c90f01f42 100644 --- a/resources/lang/pt_BR/list.php +++ b/resources/lang/pt_BR/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 841f7c2d55..ce08655f94 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'A quantidade total não pode ser zero', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'endereço de e-mail', 'description' => 'descrição', diff --git a/resources/lang/ru_RU/config.php b/resources/lang/ru_RU/config.php index a524e3644c..c792be080b 100644 --- a/resources/lang/ru_RU/config.php +++ b/resources/lang/ru_RU/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'ru', - 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => 'Неделя %W, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'Do MMMM YYYY', - 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Неделя] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'ru', + 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Неделя %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'Do MMMM YYYY', + 'date_time_js' => 'Do MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Неделя] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/ru_RU/demo.php b/resources/lang/ru_RU/demo.php index 293ba441f0..d419b6ebe7 100644 --- a/resources/lang/ru_RU/demo.php +++ b/resources/lang/ru_RU/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Эти расходы, доходы и переводы не очень интересны. Они были созданы автоматически.', 'piggy-banks-index' => 'Как вы можете видеть, здесь есть три копилки. Используйте кнопки «плюс» и «минус», чтобы влиять на количество денег в каждой копилке. Нажмите название копилки, чтобы увидеть её настройки.', 'import-index' => 'В Firefly III можно импортировать любой CSV-файл. Также поддерживается импорт данных из bunq и Spectre. Другие банки и финансовые агрегаторы будут реализованы в будущем. Однако, как демо-пользователь, вы можете видеть только как работает «поддельный»-провайдер. Он будет генерировать некоторые случайные транзакции, чтобы показать вам, как работает этот процесс.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index 382410ca5c..2088a1d347 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -820,7 +820,7 @@ return [ 'language' => 'Язык', 'new_savings_account' => 'сберегательный счёт в :bank_name', 'cash_wallet' => 'Кошелёк с наличными', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Ваши счета', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Конфигурация', 'firefly_instance_configuration' => 'Базовая конфигурация Firefly III', 'setting_single_user_mode' => 'Режим одного пользователя', - 'setting_single_user_mode_explain' => 'По умолчанию Firefly III работает только с одним пользователем (это вы). Это сделано с целью обеспечения безопасности, чтобы другие люди не могли использовать ваш Firefly без вашего разрешения. Регистрация других пользователей просто невозможна. Однако, если вы снимите этот флажок, другие смогут использовать ваш Firefly, при условии, что у них есть доступ к нему (например, он доступен через Интернет).', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Сохранить конфигурацию', 'single_user_administration' => 'Управление пользователем :email', 'edit_user' => 'Редактирование пользователя :email', @@ -1155,8 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Невозможно преобразовать раздельную транзакцию', // Import page (general strings only) - 'import_index_title' => 'Импорт данных в Firefly III', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Импорт данных', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Эта функция недоступна, если вы используете Firefly III в среде Sandstorm.io.', @@ -1206,4 +1207,57 @@ return [ 'no_bills_intro_default' => 'У вас пока нет счетов на оплату. Вы можете создавать счета для отслеживания регулярных расходов, таких как арендная плата или страхование.', 'no_bills_imperative_default' => 'У вас есть такие регулярные платежи? Создайте счёт на оплату и отслеживайте свои платежи:', 'no_bills_create_default' => 'Создать счет к оплате', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index a4b4ef54cf..3cfb8a07ea 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Код страны', 'provider_code' => 'Банк или поставщик данных', - 'due_date' => 'Срок', - 'payment_date' => 'Дата платежа', - 'invoice_date' => 'Дата выставления счёта', - 'internal_reference' => 'Внутренняя ссылка', - 'inward' => 'Внутреннее описание', - 'outward' => 'Внешнее описание', - 'rule_group_id' => 'Группа правил', + 'due_date' => 'Срок', + 'payment_date' => 'Дата платежа', + 'invoice_date' => 'Дата выставления счёта', + 'internal_reference' => 'Внутренняя ссылка', + 'inward' => 'Внутреннее описание', + 'outward' => 'Внешнее описание', + 'rule_group_id' => 'Группа правил', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index ad3e77cc87..162f8fe130 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index 9d551d5344..5d706d2353 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'Общее количество не может быть равно нулю', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => '"Адрес электронной почты"', 'description' => '"Описание"', diff --git a/resources/lang/tr_TR/config.php b/resources/lang/tr_TR/config.php index 4bc8b0175d..aaddc7f740 100644 --- a/resources/lang/tr_TR/config.php +++ b/resources/lang/tr_TR/config.php @@ -23,20 +23,29 @@ declare(strict_types=1); return [ - 'html_language' => 'tr', - 'locale' => 'tr, Turkish, tr_TR, tr_TR.utf8, tr_TR.S.UTF-8', - 'month' => '%B %Y', - 'month_and_day' => '%e %B %Y', - 'date_time' => '%e %B %Y, @ %T', - 'specific_day' => '%e %B %Y', - 'week_in_year' => '%W. hafta, %Y', - 'year' => '%Y', - 'half_year' => '%B %Y', - 'month_js' => 'MMMM YYYY', - 'month_and_day_js' => 'MMMM Do, YYYY', - 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', - 'specific_day_js' => 'D MMMM YYYY', - 'week_in_year_js' => '[Week] w, YYYY', - 'year_js' => 'YYYY', - 'half_year_js' => 'Q YYYY', + 'html_language' => 'tr', + 'locale' => 'tr, Turkish, tr_TR, tr_TR.utf8, tr_TR.S.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => '%W. hafta, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Monday', + 'dow_2' => 'Tuesday', + 'dow_3' => 'Wednesday', + 'dow_4' => 'Thursday', + 'dow_5' => 'Friday', + 'dow_6' => 'Saturday', + 'dow_7' => 'Sunday', ]; diff --git a/resources/lang/tr_TR/demo.php b/resources/lang/tr_TR/demo.php index f60723c7a3..d570d7746e 100644 --- a/resources/lang/tr_TR/demo.php +++ b/resources/lang/tr_TR/demo.php @@ -34,4 +34,6 @@ return [ 'transactions-index' => 'Bu masraflar, mevduatlar ve transferler için özellikle yaratıcı değildir. Bunlar otomatik olarak oluşturuldu.', 'piggy-banks-index' => 'Gördüğünüz gibi, üç tane banka var. Her domuzcuk bankasındaki para miktarını değiştirmek için artı ve eksi düğmelerini kullanın. Her domuzcuk bankasının yönetimini görmek için domuzcuk\'un üzerine tıklayın.', 'import-index' => 'Any CSV file can be imported into Firefly III. It also supports importing data from bunq and Spectre. Other banks and financial aggregators will be implemented in the future. As a demo-user however, you can only see the "fake"-provider in action. It will generate some random transactions to show you how the process works.', + 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', ]; diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 32747c7c17..92f9c80f5a 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -821,7 +821,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'language' => 'Language', 'new_savings_account' => ':bank_name savings account', 'cash_wallet' => 'Cash wallet', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', // home page: 'yourAccounts' => 'Hesaplarınız', @@ -1063,7 +1063,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'instance_configuration' => 'Yapılandırma', 'firefly_instance_configuration' => 'Firefly III için yapılandırma seçenekleri', 'setting_single_user_mode' => 'Tek kullanıcı modu', - 'setting_single_user_mode_explain' => 'Varsayılan olarak Firefly III sadece bir kayıt kabul eder: sizi. Bu başkalarının sizin isteklerinizi kontrol etmemesi için bir güvenlik önlemidir. Gelecekteki kayıtlar engellendi. Bu kutunun işaretini kaldırdığınızda erişebildiklerini var sayarsak (internete bağlı olduğunda) başkaları da sizin isteklerinizi kullanabilir.', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', 'store_configuration' => 'Mağaza yapılandırması', 'single_user_administration' => ':email için kullanıcı yönetimi', 'edit_user' => ':email kullanıcısını düzenle', @@ -1156,8 +1156,9 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'cannot_convert_split_journal' => 'Bölünmüş bir İşlemi dönüştüremezsiniz', // Import page (general strings only) - 'import_index_title' => 'Firefly III\'e veri aktarma', + 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Veriyi içe aktar', + 'import_transactions' => 'Import transactions', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Bir Sandstorm.io ortamında Firefly III kullanıyorsanız, bu işlev kullanılamaz.', @@ -1207,4 +1208,57 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'no_bills_intro_default' => 'Henüz bir faturanız yok. Kira veya sigortanız gibi düzenli giderleri takip etmek için faturalar oluşturabilirsiniz.', 'no_bills_imperative_default' => 'Böyle düzenli faturalarınız var mı? Bir fatura oluşturun ve ödemelerinizi takip edin:', 'no_bills_create_default' => 'Fatura oluştur', + + // recurring transactions + 'recurrences' => 'Recurring transactions', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Create a recurring transaction', + 'make_new_recurring' => 'Create a recurring transaction', + 'recurring_daily' => 'Every day', + 'recurring_weekly' => 'Every week on :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Every year on :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Related transactions', + 'expected_Withdrawals' => 'Expected withdrawals', + 'expected_Deposits' => 'Expected deposits', + 'expected_Transfers' => 'Expected transfers', + 'created_Withdrawals' => 'Created withdrawals', + 'created_Deposits' => 'Created deposits', + 'created_Transfers' => 'Created transfers', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + + 'recurring_meta_field_tags' => 'Tags', + 'recurring_meta_field_notes' => 'Notes', + 'recurring_meta_field_bill_id' => 'Bill', + 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(no currency)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Mandatory transaction information', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Optional transaction information', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Repeat forever', + 'repeat_until_date' => 'Repeat until date', + 'repeat_times' => 'Repeat a number of times', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Store recurring transaction', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Repeats until :date', + 'recurring_repeats_forever' => 'Repeats forever', + 'recurring_repeats_x_times' => 'Repeats :count time(s)', + 'update_recurrence' => 'Update recurring transaction', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', ]; diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index ac3f7c5de7..9ec5758658 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -216,11 +216,22 @@ return [ 'country_code' => 'Ülke kodu', 'provider_code' => 'Banka ya da veri sağlayıcı', - 'due_date' => 'Bitiş Tarihi', - 'payment_date' => 'Ödeme Tarihi', - 'invoice_date' => 'Fatura Tarihi', - 'internal_reference' => 'Dahili referans', - 'inward' => 'Dahili açıklama', - 'outward' => 'Harici açıklama', - 'rule_group_id' => 'Kural grubu', + 'due_date' => 'Bitiş Tarihi', + 'payment_date' => 'Ödeme Tarihi', + 'invoice_date' => 'Fatura Tarihi', + 'internal_reference' => 'Dahili referans', + 'inward' => 'Dahili açıklama', + 'outward' => 'Harici açıklama', + 'rule_group_id' => 'Kural grubu', + 'transaction_description' => 'Transaction description', + 'first_date' => 'First date', + 'transaction_type' => 'Transaction type', + 'repeat_until' => 'Repeat until', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Type of repetition', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Repetition ends', + 'repetitions' => 'Repetitions', + 'calendar' => 'Calendar', + ]; diff --git a/resources/lang/tr_TR/list.php b/resources/lang/tr_TR/list.php index 4b2516e5f6..dc23062dd4 100644 --- a/resources/lang/tr_TR/list.php +++ b/resources/lang/tr_TR/list.php @@ -123,4 +123,9 @@ return [ 'spectre_last_use' => 'Last login', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Repetitions', + 'title' => 'Title', + 'transaction_s' => 'Transaction(s)', + 'field' => 'Field', + 'value' => 'Value', ]; diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index 7a4374c4d5..151560afb2 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -112,6 +112,7 @@ return [ 'amount_zero' => 'Toplam tutar sıfır olamaz', 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', 'attributes' => [ 'email' => 'email address', 'description' => 'description', From 19a874b2745cb3b913131c55d3af724b71afda2b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 08:19:29 +0200 Subject: [PATCH 049/134] Ability to delete recurring transactions. --- .../Recurring/DeleteController.php | 70 +++++++ app/Import/Routine/SpectreRoutine.php | 1 + app/Jobs/CreateRecurringTransactions.php | 37 +++- .../Recurring/RecurringRepository.php | 13 ++ .../RecurringRepositoryInterface.php | 26 ++- .../Destroy/RecurrenceDestroyService.php | 48 +++++ resources/lang/en_US/firefly.php | 2 + resources/lang/en_US/form.php | 171 +++++++++--------- resources/views/recurring/delete.twig | 41 +++++ routes/breadcrumbs.php | 8 + routes/web.php | 1 + 11 files changed, 320 insertions(+), 98 deletions(-) create mode 100644 app/Http/Controllers/Recurring/DeleteController.php create mode 100644 app/Services/Internal/Destroy/RecurrenceDestroyService.php create mode 100644 resources/views/recurring/delete.twig diff --git a/app/Http/Controllers/Recurring/DeleteController.php b/app/Http/Controllers/Recurring/DeleteController.php new file mode 100644 index 0000000000..d0d995822d --- /dev/null +++ b/app/Http/Controllers/Recurring/DeleteController.php @@ -0,0 +1,70 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class DeleteController + */ +class DeleteController extends Controller +{ + /** + * @param Recurrence $recurrence + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function delete(Recurrence $recurrence) + { + $subTitle = trans('firefly.delete_recurring', ['title' => $recurrence->title]); + // put previous url in session + $this->rememberPreviousUri('recurrences.delete.uri'); + + // todo actual number. + $journalsCreated = 5; + + return view('recurring.delete', compact('recurrence', 'subTitle','journalsCreated')); + } + + /** + * @param RecurringRepositoryInterface $repository + * @param Request $request + * @param Recurrence $recurrence + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function destroy(RecurringRepositoryInterface $repository, Request $request, Recurrence $recurrence) + { + $repository->destroy($recurrence); + $request->session()->flash('success', (string)trans('firefly.' . 'recurrence_deleted', ['title' => $recurrence->title])); + app('preferences')->mark(); + + return redirect($this->getPreviousUri('recurrences.delete.uri')); + } + +} \ No newline at end of file diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 83aea2664a..41d2a2a80e 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -101,6 +101,7 @@ class SpectreRoutine implements RoutineInterface $handler = app(StageImportDataHandler::class); $handler->setImportJob($this->importJob); $handler->run(); + // todo apply rules. $this->repository->setStatus($this->importJob, 'provider_finished'); $this->repository->setStage($this->importJob, 'final'); } diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index c40ca144b0..af2f210ebe 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -56,6 +56,8 @@ class CreateRecurringTransactions implements ShouldQueue $recurrences = $this->repository->getAll(); Log::debug(sprintf('Count of collection is %d', $recurrences->count())); + $result = []; + /** @var Collection $filtered */ $filtered = $recurrences->filter( function (Recurrence $recurrence) { @@ -66,12 +68,24 @@ class CreateRecurringTransactions implements ShouldQueue Log::debug(sprintf('Left after filtering is %d', $filtered->count())); /** @var Recurrence $recurrence */ foreach ($filtered as $recurrence) { + if (!isset($result[$recurrence->user_id])) { + $result[$recurrence->user_id] = new Collection; + } + $this->repository->setUser($recurrence->user); $this->journalRepository->setUser($recurrence->user); Log::debug(sprintf('Now at recurrence #%d', $recurrence->id)); - $this->handleRepetitions($recurrence); + $created = $this->handleRepetitions($recurrence); Log::debug(sprintf('Done with recurrence #%c', $recurrence->id)); + + $result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($created); } + + // will now send email to users. + foreach($result as $userId => $journals) { + $this->sendReport($userId, $journals); + } + Log::debug('Done with handle()'); } @@ -160,10 +174,12 @@ class CreateRecurringTransactions implements ShouldQueue * @param Recurrence $recurrence * @param array $occurrences * + * @return Collection * @throws \FireflyIII\Exceptions\FireflyException */ - private function handleOccurrences(Recurrence $recurrence, array $occurrences): void + private function handleOccurrences(Recurrence $recurrence, array $occurrences): Collection { + $collection = new Collection; /** @var Carbon $date */ foreach ($occurrences as $date) { Log::debug(sprintf('Now at date %s.', $date->format('Y-m-d'))); @@ -195,21 +211,29 @@ class CreateRecurringTransactions implements ShouldQueue ]; $journal = $this->journalRepository->store($array); Log::info(sprintf('Created new journal #%d', $journal->id)); + // todo fire rules + $collection->push($journal); // update recurring thing: $recurrence->latest_date = $date; $recurrence->save(); } + + return $collection; } /** - * Separate method that will loop all repetitions and do something with it: + * Separate method that will loop all repetitions and do something with it. Will return + * all created transaction journals. * * @param Recurrence $recurrence * + * @return Collection + * * @throws \FireflyIII\Exceptions\FireflyException */ - private function handleRepetitions(Recurrence $recurrence): void + private function handleRepetitions(Recurrence $recurrence): Collection { + $collection = new Collection; /** @var RecurrenceRepetition $repetition */ foreach ($recurrence->recurrenceRepetitions as $repetition) { Log::debug( @@ -227,8 +251,11 @@ class CreateRecurringTransactions implements ShouldQueue ), $this->debugArray($occurrences) ); - $this->handleOccurrences($recurrence, $occurrences); + $result = $this->handleOccurrences($recurrence, $occurrences); + $collection = $collection->merge($result); } + + return $collection; } /** diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 01aa7ca230..18418c9034 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -33,6 +33,7 @@ use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService; use FireflyIII\Services\Internal\Update\RecurrenceUpdateService; use FireflyIII\User; use Illuminate\Support\Collection; @@ -47,6 +48,18 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var User */ private $user; + /** + * Destroy a recurring transaction. + * + * @param Recurrence $recurrence + */ + public function destroy(Recurrence $recurrence): void + { + /** @var RecurrenceDestroyService $service */ + $service = app(RecurrenceDestroyService::class); + $service->destroy($recurrence); + } + /** * Returns all of the user's recurring transactions. * diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index 0f1776a085..6f6963e1b6 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -39,6 +39,13 @@ use Illuminate\Support\Collection; */ interface RecurringRepositoryInterface { + /** + * Destroy a recurring transaction. + * + * @param Recurrence $recurrence + */ + public function destroy(Recurrence $recurrence): void; + /** * Returns all of the user's recurring transactions. * @@ -48,19 +55,11 @@ interface RecurringRepositoryInterface /** * Get ALL recurring transactions. + * * @return Collection */ public function getAll(): Collection; - /** - * Get the category from a recurring transaction transaction. - * - * @param RecurrenceTransaction $recurrenceTransaction - * - * @return null|string - */ - public function getCategory(RecurrenceTransaction $recurrenceTransaction): ?string; - /** * Get the budget ID from a recurring transaction transaction. * @@ -70,6 +69,15 @@ interface RecurringRepositoryInterface */ public function getBudget(RecurrenceTransaction $recurrenceTransaction): ?int; + /** + * Get the category from a recurring transaction transaction. + * + * @param RecurrenceTransaction $recurrenceTransaction + * + * @return null|string + */ + public function getCategory(RecurrenceTransaction $recurrenceTransaction): ?string; + /** * Get the notes. * diff --git a/app/Services/Internal/Destroy/RecurrenceDestroyService.php b/app/Services/Internal/Destroy/RecurrenceDestroyService.php new file mode 100644 index 0000000000..968f249235 --- /dev/null +++ b/app/Services/Internal/Destroy/RecurrenceDestroyService.php @@ -0,0 +1,48 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Destroy; + +use Exception; +use FireflyIII\Models\Recurrence; +use Log; + +/** + * @codeCoverageIgnore + * Class RecurrenceDestroyService + */ +class RecurrenceDestroyService +{ + /** + * @param Recurrence $recurrence + */ + public function destroy(Recurrence $recurrence): void + { + try { + $recurrence->delete(); + } catch (Exception $e) { // @codeCoverageIgnore + Log::error(sprintf('Could not delete recurrence: %s', $e->getMessage())); // @codeCoverageIgnore + } + } + +} diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index f64d00cd18..e3513773a9 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1260,4 +1260,6 @@ return [ 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 9445534e2a..a54447545a 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convert deposit', 'convert_Transfer' => 'Convert transfer', - 'amount' => 'Amount', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'category' => 'Category', - 'tags' => 'Tags', - 'deletePermanently' => 'Delete permanently', - 'cancel' => 'Cancel', - 'targetdate' => 'Target date', - 'startdate' => 'Start date', - 'tag' => 'Tag', - 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'creditCardNumber' => 'Credit card number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', - 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', - 'store_new_withdrawal' => 'Store new withdrawal', - 'store_new_deposit' => 'Store new deposit', - 'store_new_transfer' => 'Store new transfer', - 'add_new_withdrawal' => 'Add a new withdrawal', - 'add_new_deposit' => 'Add a new deposit', - 'add_new_transfer' => 'Add a new transfer', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop processing', - 'start_date' => 'Start of range', - 'end_date' => 'End of range', - 'export_start_range' => 'Start of export range', - 'export_end_range' => 'End of export range', - 'export_format' => 'File format', - 'include_attachments' => 'Include uploaded attachments', - 'include_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', - 'delete_account' => 'Delete account ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'delete_budget' => 'Delete budget ":name"', - 'delete_category' => 'Delete category ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'delete_journal' => 'Delete transaction with description ":description"', - 'delete_attachment' => 'Delete attachment ":name"', - 'delete_rule' => 'Delete rule ":title"', - 'delete_rule_group' => 'Delete rule group ":title"', - 'delete_link_type' => 'Delete link type ":name"', - 'delete_user' => 'Delete user ":email"', - 'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.', - 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', - 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', - 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', - 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', - 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', - 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', - 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', - 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', - 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', - 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', - 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', - 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', - 'journal_link_areYouSure' => 'Are you sure you want to delete the link between :source and :destination?', - 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', - 'delete_all_permanently' => 'Delete selected permanently', - 'update_all_journals' => 'Update these transactions', - 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', - 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', - 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', - 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', - 'check_for_updates' => 'Check for updates', + 'amount' => 'Amount', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', + 'category' => 'Category', + 'tags' => 'Tags', + 'deletePermanently' => 'Delete permanently', + 'cancel' => 'Cancel', + 'targetdate' => 'Target date', + 'startdate' => 'Start date', + 'tag' => 'Tag', + 'under' => 'Under', + 'symbol' => 'Symbol', + 'code' => 'Code', + 'iban' => 'IBAN', + 'accountNumber' => 'Account number', + 'creditCardNumber' => 'Credit card number', + 'has_headers' => 'Headers', + 'date_format' => 'Date format', + 'specifix' => 'Bank- or file specific fixes', + 'attachments[]' => 'Attachments', + 'store_new_withdrawal' => 'Store new withdrawal', + 'store_new_deposit' => 'Store new deposit', + 'store_new_transfer' => 'Store new transfer', + 'add_new_withdrawal' => 'Add a new withdrawal', + 'add_new_deposit' => 'Add a new deposit', + 'add_new_transfer' => 'Add a new transfer', + 'title' => 'Title', + 'notes' => 'Notes', + 'filename' => 'File name', + 'mime' => 'Mime type', + 'size' => 'Size', + 'trigger' => 'Trigger', + 'stop_processing' => 'Stop processing', + 'start_date' => 'Start of range', + 'end_date' => 'End of range', + 'export_start_range' => 'Start of export range', + 'export_end_range' => 'End of export range', + 'export_format' => 'File format', + 'include_attachments' => 'Include uploaded attachments', + 'include_old_uploads' => 'Include imported data', + 'accounts' => 'Export transactions from these accounts', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Delete bill ":name"', + 'delete_budget' => 'Delete budget ":name"', + 'delete_category' => 'Delete category ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'delete_journal' => 'Delete transaction with description ":description"', + 'delete_attachment' => 'Delete attachment ":name"', + 'delete_rule' => 'Delete rule ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'delete_link_type' => 'Delete link type ":name"', + 'delete_user' => 'Delete user ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', + 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'journal_link_areYouSure' => 'Are you sure you want to delete the link between :source and :destination?', + 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', + 'delete_all_permanently' => 'Delete selected permanently', + 'update_all_journals' => 'Update these transactions', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Check for updates', 'email' => 'Email address', 'password' => 'Password', diff --git a/resources/views/recurring/delete.twig b/resources/views/recurring/delete.twig new file mode 100644 index 0000000000..2843a5705e --- /dev/null +++ b/resources/views/recurring/delete.twig @@ -0,0 +1,41 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }} +{% endblock %} + +{% block content %} + +
        + +
        +
        +
        +
        +

        {{ trans('form.delete_recurring', {'title': recurrence.title}) }}

        +
        +
        +

        + {{ trans('form.permDeleteWarning') }} +

        + +

        + {{ trans('form.recurring_areYouSure', {'title': recurrence.title}) }} +

        + +

        + {% if journalsCreated > 0 %} + {{ Lang.choice('form.recurring_keep_transactions', journalsCreated, {count: journalsCreated }) }} + {% endif %} +

        +
        + +
        +
        +
        + +
        +{% endblock %} diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 1ffad9ff16..5a895da18c 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -778,6 +778,14 @@ try { } ); + Breadcrumbs::register( + 'recurring.delete', + function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) { + $breadcrumbs->parent('recurring.index'); + $breadcrumbs->push(trans('firefly.delete_recurring', ['title' => $recurrence->title]), route('recurring.delete', [$recurrence->id])); + } + ); + Breadcrumbs::register( 'recurring.edit', function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) { diff --git a/routes/web.php b/routes/web.php index fc5461766d..84dd57281a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -626,6 +626,7 @@ Route::group( Route::post('store', ['uses' => 'CreateController@store', 'as' => 'store']); Route::post('update/{recurrence}', ['uses' => 'EditController@update', 'as' => 'update']); + Route::post('destroy/{recurrence}', ['uses' => 'DeleteController@destroy', 'as' => 'destroy']); } ); From a1d99c1954e743c4abfda282da23368d8f8ac9dd Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 10:55:26 +0200 Subject: [PATCH 050/134] Bunq and Spectre will ask to apply rules. --- app/Import/Configuration/BunqConfigurator.php | 214 ------------------ .../Configuration/ConfiguratorInterface.php | 80 ------- .../Bunq/ChooseAccountsHandler.php | 12 +- .../Bunq/NewBunqJobHandler.php | 5 - .../Spectre/ChooseAccountsHandler.php | 9 +- resources/lang/en_US/import.php | 5 +- .../views/import/bunq/choose-accounts.twig | 20 ++ resources/views/import/spectre/accounts.twig | 20 ++ 8 files changed, 56 insertions(+), 309 deletions(-) delete mode 100644 app/Import/Configuration/BunqConfigurator.php delete mode 100644 app/Import/Configuration/ConfiguratorInterface.php diff --git a/app/Import/Configuration/BunqConfigurator.php b/app/Import/Configuration/BunqConfigurator.php deleted file mode 100644 index a115f8e933..0000000000 --- a/app/Import/Configuration/BunqConfigurator.php +++ /dev/null @@ -1,214 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Import\Configuration; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\ImportJob; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Support\Import\Configuration\Bunq\HaveAccounts; -use Log; - -/** - * @deprecated - * @codeCoverageIgnore - * Class BunqConfigurator. - */ -class BunqConfigurator implements ConfiguratorInterface -{ - /** @var ImportJob */ - private $job; - - /** @var ImportJobRepositoryInterface */ - private $repository; - - /** @var string */ - private $warning = ''; - - /** - * ConfiguratorInterface constructor. - */ - public function __construct() - { - } - - /** - * Store any data from the $data array into the job. - * - * @param array $data - * - * @return bool - * - * @throws FireflyException - */ - public function configureJob(array $data): bool - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $stage = $this->getConfig()['stage'] ?? 'initial'; - Log::debug(sprintf('in getNextData(), for stage "%s".', $stage)); - - switch ($stage) { - case 'have-accounts': - /** @var HaveAccounts $class */ - $class = app(HaveAccounts::class); - $class->setJob($this->job); - $class->storeConfiguration($data); - - // update job for next step and set to "configured". - $config = $this->getConfig(); - $config['stage'] = 'have-account-mapping'; - $this->repository->setConfiguration($this->job, $config); - - return true; - default: - throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage)); - break; - } - } - - /** - * Return the data required for the next step in the job configuration. - * - * @return array - * - * @throws FireflyException - */ - public function getNextData(): array - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $config = $this->getConfig(); - $stage = $config['stage'] ?? 'initial'; - - Log::debug(sprintf('in getNextData(), for stage "%s".', $stage)); - - switch ($stage) { - case 'have-accounts': - /** @var HaveAccounts $class */ - $class = app(HaveAccounts::class); - $class->setJob($this->job); - - return $class->getData(); - default: - return []; - } - } - - /** - * @return string - * - * @throws FireflyException - */ - public function getNextView(): string - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $stage = $this->getConfig()['stage'] ?? 'initial'; - - Log::debug(sprintf('getNextView: in getNextView(), for stage "%s".', $stage)); - switch ($stage) { - case 'have-accounts': - return 'import.bunq.accounts'; - default: - return ''; - } - } - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string - { - return $this->warning; - } - - /** - * @return bool - * - * @throws FireflyException - */ - public function isJobConfigured(): bool - { - if (null === $this->job) { - throw new FireflyException('Cannot call configureJob() without a job.'); - } - $stage = $this->getConfig()['stage'] ?? 'initial'; - - Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage)); - switch ($stage) { - case 'have-accounts': - Log::debug('isJobConfigured returns false'); - - return false; - default: - Log::debug('isJobConfigured returns true'); - - return true; - } - } - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job): void - { - // make repository - $this->repository = app(ImportJobRepositoryInterface::class); - $this->repository->setUser($job->user); - - // set default config: - $defaultConfig = [ - 'is-redirected' => false, - 'stage' => 'initial', - 'auto-start' => true, - 'apply-rules' => true, - ]; - $currentConfig = $this->repository->getConfiguration($job); - $finalConfig = array_merge($defaultConfig, $currentConfig); - - // set default extended status: - $extendedStatus = $this->repository->getExtendedStatus($job); - $extendedStatus['steps'] = 8; - - // save to job: - $job = $this->repository->setConfiguration($job, $finalConfig); - $job = $this->repository->setExtendedStatus($job, $extendedStatus); - $this->job = $job; - - } - - /** - * Shorthand method. - * - * @return array - */ - private function getConfig(): array - { - return $this->repository->getConfiguration($this->job); - } -} diff --git a/app/Import/Configuration/ConfiguratorInterface.php b/app/Import/Configuration/ConfiguratorInterface.php deleted file mode 100644 index a1e961b0ec..0000000000 --- a/app/Import/Configuration/ConfiguratorInterface.php +++ /dev/null @@ -1,80 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Import\Configuration; - -use FireflyIII\Models\ImportJob; - -/** - * @deprecated - * @codeCoverageIgnore - * Interface ConfiguratorInterface. - */ -interface ConfiguratorInterface -{ - /** - * ConfiguratorInterface constructor. - */ - public function __construct(); - - /** - * Store any data from the $data array into the job. - * - * @param array $data - * - * @return bool - */ - public function configureJob(array $data): bool; - - /** - * Return the data required for the next step in the job configuration. - * - * @return array - */ - public function getNextData(): array; - - /** - * Returns the view of the next step in the job configuration. - * - * @return string - */ - public function getNextView(): string; - - /** - * Return possible warning to user. - * - * @return string - */ - public function getWarningMessage(): string; - - /** - * Returns true when the initial configuration for this job is complete. - * - * @return bool - */ - public function isJobConfigured(): bool; - - /** - * @param ImportJob $job - */ - public function setJob(ImportJob $job); -} diff --git a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php index 247a7a2916..ff8764cb52 100644 --- a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php @@ -76,10 +76,11 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface */ public function configureJob(array $data): MessageBag { - $config = $this->repository->getConfiguration($this->importJob); - $accounts = $config['accounts'] ?? []; - $mapping = $data['account_mapping'] ?? []; - $final = []; + $config = $this->repository->getConfiguration($this->importJob); + $accounts = $config['accounts'] ?? []; + $mapping = $data['account_mapping'] ?? []; + $applyRules = (int)$data['apply_rules'] === 1; + $final = []; if (\count($accounts) === 0) { throw new FireflyException('No bunq accounts found. Import cannot continue.'); // @codeCoverageIgnore } @@ -98,7 +99,8 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface $accountId = $this->validLocalAccount($localId); $final[$bunqId] = $accountId; } - $config['mapping'] = $final; + $config['mapping'] = $final; + $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); return new MessageBag; diff --git a/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php b/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php index 2f3f208c93..e83d4247b5 100644 --- a/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/NewBunqJobHandler.php @@ -45,11 +45,6 @@ class NewBunqJobHandler implements BunqJobConfigurationInterface */ public function configurationComplete(): bool { - // simply set the job configuration "apply-rules" to true. - $config = $this->repository->getConfiguration($this->importJob); - $config['apply-rules'] = true; - $this->repository->setConfiguration($this->importJob, $config); - return true; } diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php index ee85ebfb8d..9797acd428 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php @@ -83,9 +83,10 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface public function configureJob(array $data): MessageBag { Log::debug('Now in ChooseAccountsHandler::configureJob()', $data); - $config = $this->importJob->configuration; - $mapping = $data['account_mapping'] ?? []; - $final = []; + $config = $this->importJob->configuration; + $mapping = $data['account_mapping'] ?? []; + $final = []; + $applyRules = (int)$data['apply_rules'] === 1; foreach ($mapping as $spectreId => $localId) { // validate each $spectreId = $this->validSpectreAccount((int)$spectreId); @@ -96,7 +97,7 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface Log::debug('Final mapping is:', $final); $messages = new MessageBag; $config['account_mapping'] = $final; - + $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); if ($final === [0 => 0] || \count($final) === 0) { $messages->add('count', trans('import.spectre_no_mapping')); diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index feb4f58645..824704689e 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/views/import/bunq/choose-accounts.twig b/resources/views/import/bunq/choose-accounts.twig index f5b4afba79..566837af83 100644 --- a/resources/views/import/bunq/choose-accounts.twig +++ b/resources/views/import/bunq/choose-accounts.twig @@ -7,6 +7,26 @@
        + +
        +
        +
        +

        {{ trans('import.job_config_bunq_apply_rules') }}

        +
        +
        +
        +
        +

        + {{ trans('import.job_config_bunq_apply_rules_text') }} +

        + {{ ExpandedForm.checkbox('apply_rules', 1, true) }} +
        +
        + +
        +
        +
        +
        diff --git a/resources/views/import/spectre/accounts.twig b/resources/views/import/spectre/accounts.twig index c6c0eedf82..4fbf0ba248 100644 --- a/resources/views/import/spectre/accounts.twig +++ b/resources/views/import/spectre/accounts.twig @@ -7,6 +7,26 @@
        + +
        +
        +
        +

        {{ trans('import.job_config_spectre_apply_rules') }}

        +
        +
        +
        +
        +

        + {{ trans('import.job_config_spectre_apply_rules_text') }} +

        + {{ ExpandedForm.checkbox('apply_rules', 1, true) }} +
        +
        + +
        +
        +
        +
        From 3a71bd01fbfe249bf553ead11ddd32441f43737a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 17:40:41 +0200 Subject: [PATCH 051/134] Remove dead code. --- app/Helpers/Collection/BalanceLine.php | 7 ----- app/Helpers/Report/PopupReport.php | 27 ------------------- app/Helpers/Report/PopupReportInterface.php | 8 ------ .../Controllers/Popup/ReportController.php | 5 +--- .../Controllers/Report/BalanceController.php | 2 +- resources/lang/en_US/firefly.php | 1 - 6 files changed, 2 insertions(+), 48 deletions(-) diff --git a/app/Helpers/Collection/BalanceLine.php b/app/Helpers/Collection/BalanceLine.php index a998b4784e..9d62a4aa21 100644 --- a/app/Helpers/Collection/BalanceLine.php +++ b/app/Helpers/Collection/BalanceLine.php @@ -40,10 +40,6 @@ class BalanceLine * */ public const ROLE_TAGROLE = 2; - /** - * - */ - public const ROLE_DIFFROLE = 3; /** @var Collection */ protected $balanceEntries; @@ -167,9 +163,6 @@ class BalanceLine if (self::ROLE_TAGROLE === $this->getRole()) { return (string)trans('firefly.coveredWithTags'); } - if (self::ROLE_DIFFROLE === $this->getRole()) { - return (string)trans('firefly.leftUnbalanced'); - } return ''; } diff --git a/app/Helpers/Report/PopupReport.php b/app/Helpers/Report/PopupReport.php index 73e786e24b..24c1bd2d9e 100644 --- a/app/Helpers/Report/PopupReport.php +++ b/app/Helpers/Report/PopupReport.php @@ -36,33 +36,6 @@ use Illuminate\Support\Collection; */ class PopupReport implements PopupReportInterface { - /** - * @param $account - * @param $attributes - * - * @return Collection - */ - public function balanceDifference($account, $attributes): Collection - { - // row that displays difference - /** @var JournalCollectorInterface $collector */ - $collector = app(JournalCollectorInterface::class); - $collector - ->setAccounts(new Collection([$account])) - ->setTypes([TransactionType::WITHDRAWAL]) - ->setRange($attributes['startDate'], $attributes['endDate']) - ->withoutBudget(); - $journals = $collector->getJournals(); - - return $journals->filter( - function (Transaction $transaction) { - $tags = $transaction->transactionJournal->tags()->where('tagMode', 'balancingAct')->count(); - - return 0 === $tags; - } - ); - } - /** * @param Budget $budget * @param Account $account diff --git a/app/Helpers/Report/PopupReportInterface.php b/app/Helpers/Report/PopupReportInterface.php index 69efef80e6..6345afdcc3 100644 --- a/app/Helpers/Report/PopupReportInterface.php +++ b/app/Helpers/Report/PopupReportInterface.php @@ -32,14 +32,6 @@ use Illuminate\Support\Collection; */ interface PopupReportInterface { - /** - * @param $account - * @param $attributes - * - * @return Collection - */ - public function balanceDifference($account, $attributes): Collection; - /** * @param Budget $budget * @param Account $account diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index ed57022b60..0f30b1c9d7 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -127,6 +127,7 @@ class ReportController extends Controller $budget = $this->budgetRepository->findNull((int)$attributes['budgetId']); $account = $this->accountRepository->findNull((int)$attributes['accountId']); + switch (true) { case BalanceLine::ROLE_DEFAULTROLE === $role && null !== $budget->id: // normal row with a budget: @@ -137,10 +138,6 @@ class ReportController extends Controller $journals = $this->popupHelper->balanceForNoBudget($account, $attributes); $budget->name = (string)trans('firefly.no_budget'); break; - case BalanceLine::ROLE_DIFFROLE === $role: - $journals = $this->popupHelper->balanceDifference($account, $attributes); - $budget->name = (string)trans('firefly.leftUnbalanced'); - break; case BalanceLine::ROLE_TAGROLE === $role: // row with tag info. throw new FireflyException('Firefly cannot handle this type of info-button (BalanceLine::TagRole)'); diff --git a/app/Http/Controllers/Report/BalanceController.php b/app/Http/Controllers/Report/BalanceController.php index 3b31be87de..2945a15b13 100644 --- a/app/Http/Controllers/Report/BalanceController.php +++ b/app/Http/Controllers/Report/BalanceController.php @@ -56,7 +56,7 @@ class BalanceController extends Controller $balance = $helper->getBalanceReport($accounts, $start, $end); - $result = view('reports.partials.balance', compact('balance'))->render(); + $result = view('reports.partials.balance', compact( 'balance'))->render(); $cache->store($result); return $result; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index e3513773a9..48ad084831 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -900,7 +900,6 @@ return [ 'balanceEnd' => 'Balance at end of period', 'splitByAccount' => 'Split by account', 'coveredWithTags' => 'Covered with tags', - 'leftUnbalanced' => 'Left unbalanced', 'leftInBudget' => 'Left in budget', 'sumOfSums' => 'Sum of sums', 'noCategory' => '(no category)', From 793cfcb2c5088dec8d76c1b53d7d98284310120e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 17:59:37 +0200 Subject: [PATCH 052/134] Code for #1503 --- app/Http/Controllers/ProfileController.php | 3 ++- resources/lang/en_US/firefly.php | 1 + resources/views/profile/code.twig | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 581f88b35d..c872ad7805 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -107,9 +107,10 @@ class ProfileController extends Controller $domain = $this->getDomain(); $secret = Google2FA::generateSecretKey(); session()->flash('two-factor-secret', $secret); + $image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200); - return view('profile.code', compact('image')); + return view('profile.code', compact('image','secret')); } /** diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 48ad084831..bb282f79bc 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', 'pref_two_factor_auth_reset_code' => 'Reset verification code', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Save settings', 'saved_preferences' => 'Preferences saved!', 'preferences_general' => 'General', diff --git a/resources/views/profile/code.twig b/resources/views/profile/code.twig index 1313697c72..a1288322bd 100644 --- a/resources/views/profile/code.twig +++ b/resources/views/profile/code.twig @@ -22,6 +22,9 @@
        +

        + {{ trans('firefly.2fa_use_secret_instead', {secret: secret}) }} +

        From 4f2d0a0322dd6b1c6d91caad860765fff04b8f25 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 17:59:57 +0200 Subject: [PATCH 053/134] Sentence patch [skip ci] --- resources/lang/en_US/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index bb282f79bc..72856519f1 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -465,7 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', 'pref_two_factor_auth_reset_code' => 'Reset verification code', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Save settings', 'saved_preferences' => 'Preferences saved!', 'preferences_general' => 'General', From 32b6ded0088fc21a6a58cba08cc8e7cf2c3733a2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 23 Jun 2018 19:56:04 +0200 Subject: [PATCH 054/134] Fix #1505 --- resources/views/bills/show.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index c912bc5583..fe877f39e1 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -66,7 +66,7 @@
        From ad6a9a7df733bea65f126067ac90ca4e4ce421aa Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 06:51:22 +0200 Subject: [PATCH 055/134] Expand API for attachments. --- app/Api/V1/Controllers/AccountController.php | 15 +- .../V1/Controllers/AttachmentController.php | 227 ++++++++++++++++++ app/Api/V1/Requests/AttachmentRequest.php | 92 +++++++ app/Factory/AttachmentFactory.php | 79 ++++++ app/Helpers/Attachments/AttachmentHelper.php | 42 +++- .../Attachments/AttachmentHelperInterface.php | 10 + app/Models/Account.php | 1 + app/Models/Attachment.php | 52 ++-- app/Models/Bill.php | 23 +- app/Models/Note.php | 5 + app/Models/PiggyBank.php | 1 + app/Models/Rule.php | 25 +- app/Models/RuleAction.php | 10 + app/Models/RuleGroup.php | 10 +- app/Models/RuleTrigger.php | 7 + .../Attachment/AttachmentRepository.php | 32 ++- .../AttachmentRepositoryInterface.php | 9 + app/Rules/IsValidAttachmentModel.php | 88 +++++++ app/Transformers/AccountTransformer.php | 2 +- app/Transformers/AttachmentTransformer.php | 25 +- app/Transformers/BillTransformer.php | 50 +++- app/Transformers/NoteTransformer.php | 92 +++++++ app/Transformers/RuleActionTransformer.php | 93 +++++++ app/Transformers/RuleGroupTransformer.php | 110 +++++++++ app/Transformers/RuleTransformer.php | 140 +++++++++++ app/Transformers/RuleTriggerTransformer.php | 92 +++++++ resources/lang/en_US/validation.php | 2 + routes/api.php | 15 ++ 28 files changed, 1290 insertions(+), 59 deletions(-) create mode 100644 app/Api/V1/Controllers/AttachmentController.php create mode 100644 app/Api/V1/Requests/AttachmentRequest.php create mode 100644 app/Factory/AttachmentFactory.php create mode 100644 app/Rules/IsValidAttachmentModel.php create mode 100644 app/Transformers/NoteTransformer.php create mode 100644 app/Transformers/RuleActionTransformer.php create mode 100644 app/Transformers/RuleGroupTransformer.php create mode 100644 app/Transformers/RuleTransformer.php create mode 100644 app/Transformers/RuleTriggerTransformer.php diff --git a/app/Api/V1/Controllers/AccountController.php b/app/Api/V1/Controllers/AccountController.php index d5e0d4d255..f7bd0580f2 100644 --- a/app/Api/V1/Controllers/AccountController.php +++ b/app/Api/V1/Controllers/AccountController.php @@ -30,6 +30,7 @@ use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Transformers\AccountTransformer; +use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; @@ -57,12 +58,14 @@ class AccountController extends Controller parent::__construct(); $this->middleware( function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); // @var AccountRepositoryInterface repository $this->repository = app(AccountRepositoryInterface::class); - $this->repository->setUser(auth()->user()); + $this->repository->setUser($user); $this->currencyRepository = app(CurrencyRepositoryInterface::class); - $this->currencyRepository->setUser(auth()->user()); + $this->currencyRepository->setUser($user); return $next($request); } @@ -93,7 +96,7 @@ class AccountController extends Controller public function index(Request $request): JsonResponse { // create some objects: - $manager = new Manager(); + $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; // read type from URI @@ -129,7 +132,7 @@ class AccountController extends Controller */ public function show(Request $request, Account $account): JsonResponse { - $manager = new Manager(); + $manager = new Manager; // add include parameter: $include = $request->get('include') ?? ''; @@ -156,7 +159,7 @@ class AccountController extends Controller $data['currency_id'] = null === $currency ? 0 : $currency->id; } $account = $this->repository->store($data); - $manager = new Manager(); + $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); @@ -184,7 +187,7 @@ class AccountController extends Controller // set correct type: $data['type'] = config('firefly.shortNamesByFullName.' . $account->accountType->type); $this->repository->update($account, $data); - $manager = new Manager(); + $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); diff --git a/app/Api/V1/Controllers/AttachmentController.php b/app/Api/V1/Controllers/AttachmentController.php new file mode 100644 index 0000000000..5c67f0e387 --- /dev/null +++ b/app/Api/V1/Controllers/AttachmentController.php @@ -0,0 +1,227 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\AttachmentRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; +use FireflyIII\Models\Attachment; +use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; +use FireflyIII\Transformers\AttachmentTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Http\Response as LaravelResponse; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class AttachmentController + */ +class AttachmentController extends Controller +{ + /** @var AttachmentRepositoryInterface */ + private $repository; + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(AttachmentRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * + * @return \Illuminate\Http\Response + */ + public function destroy($id) + { + // + } + + /** + * @param Attachment $attachment + * + * @return LaravelResponse + * @throws FireflyException + */ + public function download(Attachment $attachment): LaravelResponse + { + if ($attachment->uploaded === false) { + throw new FireflyException('No file has been uploaded for this attachment (yet).'); + } + if ($this->repository->exists($attachment)) { + $content = $this->repository->getContent($attachment); + $quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\')); + + /** @var LaravelResponse $response */ + $response = response($content, 200); + $response + ->header('Content-Description', 'File Transfer') + ->header('Content-Type', 'application/octet-stream') + ->header('Content-Disposition', 'attachment; filename=' . $quoted) + ->header('Content-Transfer-Encoding', 'binary') + ->header('Connection', 'Keep-Alive') + ->header('Expires', '0') + ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->header('Pragma', 'public') + ->header('Content-Length', \strlen($content)); + + return $response; + } + throw new FireflyException('Could not find the indicated attachment. The file is no longer there.'); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of accounts. Count it and split it. + $collection = $this->repository->get(); + $count = $collection->count(); + $attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.attachments.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($attachments, new AttachmentTransformer($this->parameters), 'attachments'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Display the specified resource. + * + * @param Request $request + * @param Attachment $attachment + * + * @return JsonResponse + */ + public function show(Request $request, Attachment $attachment): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + * @throws FireflyException + */ + public function store(AttachmentRequest $request): JsonResponse + { + $data = $request->getAll(); + $attachment = $this->repository->store($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Update the specified resource in storage. + * + * @param AttachmentRequest $request + * @param Attachment $attachment + * + * @return JsonResponse + */ + public function update(AttachmentRequest $request, Attachment $attachment): JsonResponse + { + $data = $request->getAll(); + $this->repository->update($attachment, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param Request $request + * @param Attachment $attachment + * + * @return LaravelResponse + */ + public function upload(Request $request, Attachment $attachment): LaravelResponse + { + /** @var AttachmentHelperInterface $helper */ + $helper = app(AttachmentHelperInterface::class); + $body = $request->getContent(); + $helper->saveAttachmentFromApi($attachment, $body); + + return response('', 200); + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/AttachmentRequest.php b/app/Api/V1/Requests/AttachmentRequest.php new file mode 100644 index 0000000000..30da4d2683 --- /dev/null +++ b/app/Api/V1/Requests/AttachmentRequest.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\Bill; +use FireflyIII\Models\ImportJob; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Rules\IsBase64; +use FireflyIII\Rules\IsValidAttachmentModel; + +/** + * Class AttachmentRequest + */ +class AttachmentRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'filename' => $this->string('filename'), + 'title' => $this->string('title'), + 'notes' => $this->string('notes'), + 'model' => $this->string('model'), + 'model_id' => $this->integer('model_id'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $models = implode( + ',', [ + Bill::class, + ImportJob::class, + TransactionJournal::class, + ] + ); + $model = $this->string('model'); + $rules = [ + 'filename' => 'required|between:1,255', + 'title' => 'between:1,255', + 'notes' => 'between:1,65000', + 'model' => sprintf('required|in:%s', $models), + 'model_id' => ['required', 'numeric', new IsValidAttachmentModel($model)], + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + unset($rules['model'], $rules['model_id']); + $rules['filename'] = 'between:1,255'; + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Factory/AttachmentFactory.php b/app/Factory/AttachmentFactory.php new file mode 100644 index 0000000000..b728625a4c --- /dev/null +++ b/app/Factory/AttachmentFactory.php @@ -0,0 +1,79 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Factory; + +use FireflyIII\Models\Attachment; +use FireflyIII\Models\Note; +use FireflyIII\User; + +/** + * Class AttachmentFactory + */ +class AttachmentFactory +{ + /** @var User */ + private $user; + + /** + * @param array $data + * + * @return Attachment|null + */ + public function create(array $data): ?Attachment + { + // create attachment: + $attachment = Attachment::create( + [ + 'user_id' => $this->user->id, + 'attachable_id' => $data['model_id'], + 'attachable_type' => $data['model'], + 'md5' => '', + 'filename' => $data['filename'], + 'title' => '' === $data['title'] ? null : $data['title'], + 'description' => null, + 'mime' => '', + 'size' => 0, + 'uploaded' => 0, + ] + ); + $notes = (string)($data['notes'] ?? ''); + if ('' !== $notes) { + $note = new Note; + $note->noteable()->associate($attachment); + $note->text = $notes; + $note->save(); + } + + return $attachment; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + +} \ No newline at end of file diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 72a00bab0c..b6d950042f 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -122,6 +122,46 @@ class AttachmentHelper implements AttachmentHelperInterface return $this->messages; } + /** + * Uploads a file as a string. + * + * @param Attachment $attachment + * @param string $content + * + * @return bool + */ + public function saveAttachmentFromApi(Attachment $attachment, string $content): bool + { + $resource = tmpfile(); + if (false === $resource) { + Log::error('Cannot create temp-file for file upload.'); + + return false; + } + $path = stream_get_meta_data($resource)['uri']; + fwrite($resource, $content); + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_file($finfo, $path); + $allowedMime = config('firefly.allowedMimes'); + if (!\in_array($mime, $allowedMime, true)) { + Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime)); + + return false; + } + // is allowed? Save the file! + $encrypted = Crypt::encrypt($content); + $this->uploadDisk->put($attachment->fileName(), $encrypted); + + // update attachment. + $attachment->md5 = md5_file($path); + $attachment->mime = $mime; + $attachment->size = \strlen($content); + $attachment->uploaded = 1; + $attachment->save(); + + return true; + } + /** * @param Model $model * @param array|null $files @@ -232,7 +272,7 @@ class AttachmentHelper implements AttachmentHelperInterface Log::debug(sprintf('Name is %s, and mime is %s', $name, $mime)); Log::debug('Valid mimes are', $this->allowedMimes); - if (!\in_array($mime, $this->allowedMimes)) { + if (!\in_array($mime, $this->allowedMimes, true)) { $msg = (string)trans('validation.file_invalid_mime', ['name' => $name, 'mime' => $mime]); $this->errors->add('attachments', $msg); Log::error($msg); diff --git a/app/Helpers/Attachments/AttachmentHelperInterface.php b/app/Helpers/Attachments/AttachmentHelperInterface.php index 03fd095727..8f34041faa 100644 --- a/app/Helpers/Attachments/AttachmentHelperInterface.php +++ b/app/Helpers/Attachments/AttachmentHelperInterface.php @@ -61,6 +61,16 @@ interface AttachmentHelperInterface */ public function getMessages(): MessageBag; + /** + * Uploads a file as a string. + * + * @param Attachment $attachment + * @param string $content + * + * @return bool + */ + public function saveAttachmentFromApi(Attachment $attachment, string $content): bool; + /** * @param Model $model * @param null|array $files diff --git a/app/Models/Account.php b/app/Models/Account.php index c29fe3c988..3c2c4f761f 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -44,6 +44,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property AccountType $accountType * @property bool $active * @property string $virtual_balance + * @property User $user */ class Account extends Model { diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 044283cbdb..c0d71a8fa7 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; @@ -32,6 +33,20 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Attachment. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $attachable_type + * @property string $md5 + * @property string $filename + * @property string $title + * @property string $description + * @property string $notes + * @property string $mime + * @property int $size + * @property User $user + * @property bool $uploaded */ class Attachment extends Model { @@ -100,9 +115,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getDescriptionAttribute($value) + public function getDescriptionAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -116,9 +131,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getFilenameAttribute($value) + public function getFilenameAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -132,9 +147,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getMimeAttribute($value) + public function getMimeAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -148,9 +163,9 @@ class Attachment extends Model * @return null|string * @throws \Illuminate\Contracts\Encryption\DecryptException */ - public function getTitleAttribute($value) + public function getTitleAttribute($value): ?string { - if (null === $value || 0 === \strlen($value)) { + if (null === $value || '' === $value) { return null; } @@ -169,13 +184,14 @@ class Attachment extends Model /** * @codeCoverageIgnore * - * @param string $value - * - * @throws \Illuminate\Contracts\Encryption\EncryptException + * @param string|null $value */ - public function setDescriptionAttribute(string $value) + public function setDescriptionAttribute(string $value = null): void { - $this->attributes['description'] = Crypt::encrypt($value); + if (null !== $value) { + $this->attributes['description'] = Crypt::encrypt($value); + } + } /** @@ -185,7 +201,7 @@ class Attachment extends Model * * @throws \Illuminate\Contracts\Encryption\EncryptException */ - public function setFilenameAttribute(string $value) + public function setFilenameAttribute(string $value): void { $this->attributes['filename'] = Crypt::encrypt($value); } @@ -197,7 +213,7 @@ class Attachment extends Model * * @throws \Illuminate\Contracts\Encryption\EncryptException */ - public function setMimeAttribute(string $value) + public function setMimeAttribute(string $value): void { $this->attributes['mime'] = Crypt::encrypt($value); } @@ -209,9 +225,11 @@ class Attachment extends Model * * @throws \Illuminate\Contracts\Encryption\EncryptException */ - public function setTitleAttribute(string $value) + public function setTitleAttribute(string $value = null): void { - $this->attributes['title'] = Crypt::encrypt($value); + if (null !== $value) { + $this->attributes['title'] = Crypt::encrypt($value); + } } /** diff --git a/app/Models/Bill.php b/app/Models/Bill.php index 3da9e56add..81da6f180d 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -22,23 +22,34 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Bill. * - * @property bool $active - * @property int $transaction_currency_id - * @property string $amount_min - * @property string $amount_max - * @property int $id - * @property string $name + * @property bool $active + * @property int $transaction_currency_id + * @property string $amount_min + * @property string $amount_max + * @property int $id + * @property string $name + * @property Collection $notes + * @property TransactionCurrency $transactionCurrency + * @property Carbon $created_at + * @property Carbon $updated_at + * @property Carbon $date + * @property string $repeat_freq + * @property int $skip + * @property bool $automatch + * @property User $user */ class Bill extends Model { diff --git a/app/Models/Note.php b/app/Models/Note.php index f2cc1ddb20..316b65089e 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -22,12 +22,17 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; /** * Class Note. * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at * @property string $text + * @property string $title */ class Note extends Model { diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 92819a5d28..82721a243a 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -38,6 +38,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $targetamount * @property int $id * @property string $name + * @property Account $account * */ class PiggyBank extends Model diff --git a/app/Models/Rule.php b/app/Models/Rule.php index c3c4acd811..4d368acec2 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -22,19 +22,30 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Rule. * - * @property bool $stop_processing - * @property int $id - * @property \Illuminate\Support\Collection $ruleTriggers - * @property bool $active - * @property bool $strict + * @property bool $stop_processing + * @property int $id + * @property Collection $ruleTriggers + * @property Collection $ruleActions + * @property bool $active + * @property bool $strict + * @property User $user + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $title + * @property string $text + * @property int $order + * @property RuleGroup $ruleGroup */ class Rule extends Model { @@ -78,9 +89,9 @@ class Rule extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function ruleActions() + public function ruleActions(): HasMany { return $this->hasMany(RuleAction::class); } diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index da9e6c048a..7ca38fcc39 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -22,10 +22,20 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; /** * Class RuleAction. + * + * @property string $action_value + * @property string $action_type + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property int $order + * @property bool $active + * @property bool $stop_processing */ class RuleAction extends Model { diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index 149b12fc4d..8bbbc74e8b 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -30,7 +31,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class RuleGroup. * - * @property bool $active + * @property bool $active + * @property User $user + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $title + * @property string $text + * @property int $id + * @property int $order */ class RuleGroup extends Model { diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index 58265d0d1c..b030f9d836 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; /** @@ -29,6 +30,12 @@ use Illuminate\Database\Eloquent\Model; * * @property string $trigger_value * @property string $trigger_type + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property int $order + * @property bool $active + * @property bool $stop_processing */ class RuleTrigger extends Model { diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index 36d38ade9d..d5eac20346 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -25,6 +25,8 @@ namespace FireflyIII\Repositories\Attachment; use Carbon\Carbon; use Crypt; use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\AttachmentFactory; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Models\Attachment; use FireflyIII\Models\Note; @@ -179,11 +181,30 @@ class AttachmentRepository implements AttachmentRepositoryInterface /** * @param User $user */ - public function setUser(User $user) + public function setUser(User $user): void { $this->user = $user; } + /** + * @param array $data + * + * @return Attachment + * @throws FireflyException + */ + public function store(array $data): Attachment + { + /** @var AttachmentFactory $factory */ + $factory = app(AttachmentFactory::class); + $factory->setUser($this->user); + $result = $factory->create($data); + if (null === $result) { + throw new FireflyException('Could not store attachment.'); + } + + return $result; + } + /** * @param Attachment $attachment * @param array $data @@ -193,8 +214,13 @@ class AttachmentRepository implements AttachmentRepositoryInterface public function update(Attachment $attachment, array $data): Attachment { $attachment->title = $data['title']; + + // update filename, if present and different: + if (isset($data['filename']) && '' !== $data['filename'] && $data['filename'] !== $attachment->filename) { + $attachment->filename = $data['filename']; + } $attachment->save(); - $this->updateNote($attachment, $data['notes']); + $this->updateNote($attachment, $data['notes'] ?? ''); return $attachment; } @@ -207,7 +233,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface */ public function updateNote(Attachment $attachment, string $note): bool { - if (0 === \strlen($note)) { + if ('' === $note) { $dbNote = $attachment->notes()->first(); if (null !== $dbNote) { $dbNote->delete(); diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index 0a2b63b537..30caf2c253 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Attachment; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Attachment; use FireflyIII\User; use Illuminate\Support\Collection; @@ -95,6 +96,14 @@ interface AttachmentRepositoryInterface */ public function setUser(User $user); + /** + * @param array $data + * + * @return Attachment + * @throws FireflyException + */ + public function store(array $data): Attachment; + /** * @param Attachment $attachment * @param array $attachmentData diff --git a/app/Rules/IsValidAttachmentModel.php b/app/Rules/IsValidAttachmentModel.php new file mode 100644 index 0000000000..db2959c7c8 --- /dev/null +++ b/app/Rules/IsValidAttachmentModel.php @@ -0,0 +1,88 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Rules; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Contracts\Validation\Rule; + +/** + * Class IsValidAttachmentModel + */ +class IsValidAttachmentModel implements Rule +{ + /** @var string */ + private $model; + + /** + * IsValidAttachmentModel constructor. + * + * @param string $model + */ + public function __construct(string $model) + { + $this->model = $model; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): string + { + return trans('validation.model_id_invalid'); + } + + /** + * Determine if the validation rule passes. + * + * @param string $attribute + * @param mixed $value + * + * @return bool + * @throws FireflyException + */ + public function passes($attribute, $value): bool + { + if (!auth()->check()) { + return false; + } + $user = auth()->user(); + switch ($this->model) { + default: + throw new FireflyException(sprintf('Model "%s" cannot be validated.', $this->model)); + case TransactionJournal::class: + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $repository->setUser($user); + $result = $repository->findNull((int)$value); + + return null !== $result; + break; + } + } +} \ No newline at end of file diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 099398fd8c..c238a28dd8 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -153,7 +153,7 @@ class AccountTransformer extends TransformerAbstract } $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); $currencyCode = null; - $currencySymbol = 'x'; + $currencySymbol = null; $decimalPlaces = 2; if ($currencyId > 0) { $currency = TransactionCurrency::find($currencyId); diff --git a/app/Transformers/AttachmentTransformer.php b/app/Transformers/AttachmentTransformer.php index 0db1993352..81ba473173 100644 --- a/app/Transformers/AttachmentTransformer.php +++ b/app/Transformers/AttachmentTransformer.php @@ -28,6 +28,7 @@ use FireflyIII\Models\Attachment; use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; +use League\Fractal\Resource\Collection as FractalCollection; /** * Class AttachmentTransformer @@ -39,13 +40,13 @@ class AttachmentTransformer extends TransformerAbstract * * @var array */ - protected $availableIncludes = ['user']; + protected $availableIncludes = ['user','notes']; /** * List of resources to automatically include * * @var array */ - protected $defaultIncludes = ['user']; + protected $defaultIncludes = ['user','notes']; /** @var ParameterBag */ protected $parameters; @@ -76,6 +77,20 @@ class AttachmentTransformer extends TransformerAbstract return $this->item($attachment->user, new UserTransformer($this->parameters), 'user'); } + /** + * Attach the notes. + * + * @codeCoverageIgnore + * + * @param Attachment $attachment + * + * @return FractalCollection + */ + public function includeNotes(Attachment $attachment): FractalCollection + { + return $this->collection($attachment->notes, new NoteTransformer($this->parameters), 'notes'); + } + /** * Transform attachment. * @@ -92,11 +107,11 @@ class AttachmentTransformer extends TransformerAbstract 'attachable_type' => $attachment->attachable_type, 'md5' => $attachment->md5, 'filename' => $attachment->filename, + 'download_uri' => route('api.v1.attachments.download', [$attachment->id]), + 'upload_uri' => route('api.v1.attachments.upload', [$attachment->id]), 'title' => $attachment->title, - 'description' => $attachment->description, - 'notes' => $attachment->notes, 'mime' => $attachment->mime, - 'size' => $attachment->size, + 'size' => (int)$attachment->size, 'links' => [ [ 'rel' => 'self', diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index 78a17fe43d..86c7516833 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -26,7 +26,6 @@ namespace FireflyIII\Transformers; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Bill; -use FireflyIII\Models\Note; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use Illuminate\Support\Collection; use League\Fractal\Resource\Collection as FractalCollection; @@ -45,13 +44,13 @@ class BillTransformer extends TransformerAbstract * * @var array */ - protected $availableIncludes = ['attachments', 'transactions', 'user']; + protected $availableIncludes = ['attachments', 'transactions', 'user', 'notes', 'rules']; /** * List of resources to automatically include * * @var array */ - protected $defaultIncludes = []; + protected $defaultIncludes = ['notes', 'rules']; /** @var ParameterBag */ protected $parameters; @@ -83,6 +82,40 @@ class BillTransformer extends TransformerAbstract return $this->collection($attachments, new AttachmentTransformer($this->parameters), 'attachments'); } + /** + * Attach the notes. + * + * @codeCoverageIgnore + * + * @param Bill $bill + * + * @return FractalCollection + */ + public function includeNotes(Bill $bill): FractalCollection + { + return $this->collection($bill->notes, new NoteTransformer($this->parameters), 'notes'); + } + + /** + * Attach the rules. + * + * @codeCoverageIgnore + * + * @param Bill $bill + * + * @return FractalCollection + */ + public function includeRules(Bill $bill): FractalCollection + { + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $repository->setUser($bill->user); + // add info about rules: + $rules = $repository->getRulesForBill($bill); + + return $this->collection($rules, new RuleTransformer($this->parameters), 'rules'); + } + /** * Include any transactions. * @@ -141,19 +174,17 @@ class BillTransformer extends TransformerAbstract 'name' => $bill->name, 'currency_id' => $bill->transaction_currency_id, 'currency_code' => $bill->transactionCurrency->code, - 'match' => explode(',', $bill->match), 'amount_min' => round($bill->amount_min, 2), 'amount_max' => round($bill->amount_max, 2), 'date' => $bill->date->format('Y-m-d'), 'repeat_freq' => $bill->repeat_freq, 'skip' => (int)$bill->skip, - 'automatch' => (int)$bill->automatch === 1, - 'active' => (int)$bill->active === 1, + 'automatch' => $bill->automatch, + 'active' => $bill->active, 'attachments_count' => $bill->attachments()->count(), 'pay_dates' => $payDates, 'paid_dates' => $paidData['paid_dates'], 'next_expected_match' => $paidData['next_expected_match'], - 'notes' => null, 'links' => [ [ 'rel' => 'self', @@ -161,11 +192,6 @@ class BillTransformer extends TransformerAbstract ], ], ]; - /** @var Note $note */ - $note = $bill->notes()->first(); - if (null !== $note) { - $data['notes'] = $note->text; - } return $data; diff --git a/app/Transformers/NoteTransformer.php b/app/Transformers/NoteTransformer.php new file mode 100644 index 0000000000..9cf804c45a --- /dev/null +++ b/app/Transformers/NoteTransformer.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\Note; +use League\CommonMark\CommonMarkConverter; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class NoteTransformer + */ +class NoteTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the note. + * + * @param Note $note + * + * @return array + */ + public function transform(Note $note): array + { + $converter = new CommonMarkConverter; + $data = [ + 'id' => (int)$note->id, + 'updated_at' => $note->updated_at->toAtomString(), + 'created_at' => $note->created_at->toAtomString(), + 'title' => $note->title, + 'text' => $note->text, + 'markdown' => $converter->convertToHtml($note->text), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/notes/' . $note->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/RuleActionTransformer.php b/app/Transformers/RuleActionTransformer.php new file mode 100644 index 0000000000..a0a8401716 --- /dev/null +++ b/app/Transformers/RuleActionTransformer.php @@ -0,0 +1,93 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\RuleAction; +use FireflyIII\Models\RuleTrigger; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleActionTransformer + */ +class RuleActionTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the rule action. + * + * @param RuleAction $ruleAction + * + * @return array + */ + public function transform(RuleAction $ruleAction): array + { + $data = [ + 'id' => (int)$ruleAction->id, + 'updated_at' => $ruleAction->updated_at->toAtomString(), + 'created_at' => $ruleAction->created_at->toAtomString(), + 'action_type' => $ruleAction->action_type, + 'action_value' => $ruleAction->action_value, + 'order' => $ruleAction->order, + 'active' => $ruleAction->active, + 'stop_processing' => $ruleAction->stop_processing, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rule_triggers/' . $ruleAction->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/RuleGroupTransformer.php b/app/Transformers/RuleGroupTransformer.php new file mode 100644 index 0000000000..5cbd579a54 --- /dev/null +++ b/app/Transformers/RuleGroupTransformer.php @@ -0,0 +1,110 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\RuleGroup; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleGroupTransformer + */ +class RuleGroupTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['user']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + + /** + * Include the user. + * + * @param RuleGroup $ruleGroup + * + * @codeCoverageIgnore + * @return Item + */ + public function includeUser(RuleGroup $ruleGroup): Item + { + return $this->item($ruleGroup->user, new UserTransformer($this->parameters), 'users'); + } + + /** + * Transform the rule group + * + * @param RuleGroup $ruleGroup + * + * @return array + */ + public function transform(RuleGroup $ruleGroup): array + { + $data = [ + 'id' => (int)$ruleGroup->id, + 'updated_at' => $ruleGroup->updated_at->toAtomString(), + 'created_at' => $ruleGroup->created_at->toAtomString(), + 'title' => $ruleGroup->title, + 'text' => $ruleGroup->text, + 'order' => $ruleGroup->order, + 'active' => $ruleGroup->active, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rule_groups/' . $ruleGroup->id, + ], + ], + ]; + + return $data; + } + + +} + + diff --git a/app/Transformers/RuleTransformer.php b/app/Transformers/RuleTransformer.php new file mode 100644 index 0000000000..35db378eeb --- /dev/null +++ b/app/Transformers/RuleTransformer.php @@ -0,0 +1,140 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\Rule; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; +use League\Fractal\Resource\Collection as FractalCollection; + +/** + * Class RuleTransformer + */ +class RuleTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['rule_group', 'rule_triggers', 'rule_actions', 'user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['rule_group', 'rule_triggers', 'rule_actions']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param Rule $rule + * + * @return FractalCollection + */ + public function includeRuleTriggers(Rule $rule): FractalCollection { + return $this->collection($rule->ruleTriggers, new RuleTriggerTransformer($this->parameters), 'rule_triggers'); + } + + /** + * @param Rule $rule + * + * @return FractalCollection + */ + public function includeRuleActions(Rule $rule): FractalCollection { + return $this->collection($rule->ruleActions, new RuleActionTransformer($this->parameters), 'rule_actions'); + } + + /** + * Include the user. + * + * @param Rule $rule + * + * @codeCoverageIgnore + * @return Item + */ + public function includeUser(Rule $rule): Item + { + return $this->item($rule->user, new UserTransformer($this->parameters), 'users'); + } + + + /** + * Include the rule group. + * + * @param Rule $rule + * + * @codeCoverageIgnore + * @return Item + */ + public function includeRuleGroup(Rule $rule): Item + { + return $this->item($rule->ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); + } + + /** + * Transform the rule. + * + * @param Rule $rule + * + * @return array + */ + public function transform(Rule $rule): array + { + $data = [ + 'id' => (int)$rule->id, + 'updated_at' => $rule->updated_at->toAtomString(), + 'created_at' => $rule->created_at->toAtomString(), + 'title' => $rule->title, + 'text' => $rule->text, + 'order' => (int)$rule->order, + 'active' => $rule->active, + 'stop_processing' => $rule->stop_processing, + 'strict' => $rule->strict, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rules/' . $rule->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/RuleTriggerTransformer.php b/app/Transformers/RuleTriggerTransformer.php new file mode 100644 index 0000000000..6c51c84bb3 --- /dev/null +++ b/app/Transformers/RuleTriggerTransformer.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\RuleTrigger; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class RuleTriggerTransformer + */ +class RuleTriggerTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the rule trigger. + * + * @param RuleTrigger $ruleTrigger + * + * @return array + */ + public function transform(RuleTrigger $ruleTrigger): array + { + $data = [ + 'id' => (int)$ruleTrigger->id, + 'updated_at' => $ruleTrigger->updated_at->toAtomString(), + 'created_at' => $ruleTrigger->created_at->toAtomString(), + 'trigger_type' => $ruleTrigger->trigger_type, + 'trigger_value' => $ruleTrigger->trigger_value, + 'order' => $ruleTrigger->order, + 'active' => $ruleTrigger->active, + 'stop_processing' => $ruleTrigger->stop_processing, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/rule_triggers/' . $ruleTrigger->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index d8764ef5a9..66df306ae7 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown', 'accepted' => 'The :attribute must be accepted.', 'bic' => 'This is not a valid BIC.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute must be larger than zero.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', diff --git a/routes/api.php b/routes/api.php index eeca33c0f6..6db99723e3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -43,6 +43,21 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'], + function () { + + // Accounts API routes: + Route::get('', ['uses' => 'AttachmentController@index', 'as' => 'index']); + Route::post('', ['uses' => 'AttachmentController@store', 'as' => 'store']); + Route::get('{attachment}', ['uses' => 'AttachmentController@show', 'as' => 'show']); + Route::get('{attachment}/download', ['uses' => 'AttachmentController@download', 'as' => 'download']); + Route::post('{attachment}/upload', ['uses' => 'AttachmentController@upload', 'as' => 'upload']); + Route::put('{attachment}', ['uses' => 'AttachmentController@update', 'as' => 'update']); + Route::delete('{attachment}', ['uses' => 'AttachmentController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'bills', 'as' => 'api.v1.bills.'], function () { From 91701473af51ac4cef742758cc2261a3a3d75526 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 08:33:06 +0200 Subject: [PATCH 056/134] Expand API with available budgets. --- .../V1/Controllers/AttachmentController.php | 10 +- .../Controllers/AvailableBudgetController.php | 183 ++++++++++++++++++ .../V1/Requests/AvailableBudgetRequest.php | 69 +++++++ app/Models/AvailableBudget.php | 32 ++- app/Repositories/Budget/BudgetRepository.php | 66 ++++++- .../Budget/BudgetRepositoryInterface.php | 26 ++- app/Transformers/AccountTransformer.php | 2 +- app/Transformers/AttachmentTransformer.php | 2 +- .../AvailableBudgetTransformer.php | 117 +++++++++++ app/Transformers/RecurrenceTransformer.php | 2 +- config/firefly.php | 23 +-- routes/api.php | 17 +- 12 files changed, 522 insertions(+), 27 deletions(-) create mode 100644 app/Api/V1/Controllers/AvailableBudgetController.php create mode 100644 app/Api/V1/Requests/AvailableBudgetRequest.php create mode 100644 app/Transformers/AvailableBudgetTransformer.php diff --git a/app/Api/V1/Controllers/AttachmentController.php b/app/Api/V1/Controllers/AttachmentController.php index 5c67f0e387..cdc5a8cd75 100644 --- a/app/Api/V1/Controllers/AttachmentController.php +++ b/app/Api/V1/Controllers/AttachmentController.php @@ -69,13 +69,15 @@ class AttachmentController extends Controller /** * Remove the specified resource from storage. * - * @param int $id + * @param Attachment $attachment * - * @return \Illuminate\Http\Response + * @return JsonResponse */ - public function destroy($id) + public function delete(Attachment $attachment): JsonResponse { - // + $this->repository->destroy($attachment); + + return response()->json([], 204); } /** diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php new file mode 100644 index 0000000000..c209802979 --- /dev/null +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -0,0 +1,183 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\AvailableBudgetRequest; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Transformers\AvailableBudgetTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class AvailableBudgetController + */ +class AvailableBudgetController extends Controller +{ + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + /** @var BudgetRepositoryInterface */ + private $repository; + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(BudgetRepositoryInterface::class); + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param AvailableBudget $availableBudget + * + * @return JsonResponse + */ + public function delete(AvailableBudget $availableBudget): JsonResponse + { + $this->repository->destroyAvailableBudget($availableBudget); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of accounts. Count it and split it. + $collection = $this->repository->getAvailableBudgets(); + $count = $collection->count(); + $availableBudgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($availableBudgets, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.available_budgets.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($availableBudgets, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Display the specified resource. + * + * @param Request $request + * @param AvailableBudget $availableBudget + * + * @return JsonResponse + */ + public function show(Request $request, AvailableBudget $availableBudget): JsonResponse + { + + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($availableBudget, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Store a newly created resource in storage. + * + * @param AvailableBudgetRequest $request + * + * @return JsonResponse + */ + public function store(AvailableBudgetRequest $request): JsonResponse + { + $data = $request->getAll(); + $currency = $this->currencyRepository->findNull($data['transaction_currency_id']); + $availableBudget = $this->repository->setAvailableBudget($currency, $data['start_date'], $data['end_date'], $data['amount']); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($availableBudget, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Update the specified resource in storage. + * + * @param AvailableBudgetRequest $request + * @param AvailableBudget $availableBudget + * + * @return JsonResponse + */ + public function update(AvailableBudgetRequest $request, AvailableBudget $availableBudget): JsonResponse + { + $data = $request->getAll(); + $this->repository->updateAvailableBudget($availableBudget, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($availableBudget, new AvailableBudgetTransformer($this->parameters), 'available_budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/AvailableBudgetRequest.php b/app/Api/V1/Requests/AvailableBudgetRequest.php new file mode 100644 index 0000000000..5631f5acc1 --- /dev/null +++ b/app/Api/V1/Requests/AvailableBudgetRequest.php @@ -0,0 +1,69 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +/** + * Class AvailableBudgetRequest + */ +class AvailableBudgetRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'transaction_currency_id' => $this->integer('transaction_currency_id'), + 'amount' => $this->string('amount'), + 'start_date' => $this->date('start_date'), + 'end_date' => $this->date('end_date'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'transaction_currency_id' => 'required|numeric|exists:transaction_currencies,id', + 'amount' => 'required|numeric|more:0', + 'start_date' => 'required|date|before:end_date', + 'end_date' => 'required|date|after:start_date', + ]; + + return $rules; + } + + +} \ No newline at end of file diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 2f023f4ad5..6cfbc53862 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -22,13 +22,25 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class AvailableBudget. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property User $user + * @property TransactionCurrency $transactionCurrency + * @property int $transaction_currency_id + * @property Carbon $start_date + * @property Carbon $end_date + * @property string $amount */ class AvailableBudget extends Model { @@ -49,11 +61,29 @@ class AvailableBudget extends Model /** @var array */ protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; + /** + * @param string $value + * + * @return AvailableBudget + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value): AvailableBudget + { + if (auth()->check()) { + $availableBudgetId = (int)$value; + $availableBudget = auth()->user()->availableBudgets()->find($availableBudgetId); + if (null !== $availableBudget) { + return $availableBudget; + } + } + throw new NotFoundHttpException; + } + /** * @codeCoverageIgnore * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function transactionCurrency() + public function transactionCurrency(): BelongsTo { return $this->belongsTo(TransactionCurrency::class); } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index c0ee968441..8fb69c57c5 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; +use Exception; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\AccountType; use FireflyIII\Models\AvailableBudget; @@ -156,15 +158,30 @@ class BudgetRepository implements BudgetRepositoryInterface * @param Budget $budget * * @return bool - * @throws \Exception */ public function destroy(Budget $budget): bool { - $budget->delete(); + try { + $budget->delete(); + } catch (Exception $e) { + Log::error(sprintf('Could not delete budget: %s', $e->getMessage())); + } return true; } + /** + * @param AvailableBudget $availableBudget + */ + public function destroyAvailableBudget(AvailableBudget $availableBudget): void + { + try { + $availableBudget->delete(); + } catch (Exception $e) { + Log::error(sprintf('Could not delete available budget: %s', $e->getMessage())); + } + } + /** * Filters entries from the result set generated by getBudgetPeriodReport. * @@ -355,6 +372,16 @@ class BudgetRepository implements BudgetRepositoryInterface return $amount; } + /** + * Returns all available budget objects. + * + * @return Collection + */ + public function getAvailableBudgets(): Collection + { + return $this->user->availableBudgets()->get(); + } + /** * @param Budget $budget * @param Carbon $start @@ -527,9 +554,9 @@ class BudgetRepository implements BudgetRepositoryInterface * @param Carbon $end * @param string $amount * - * @return bool + * @return AvailableBudget */ - public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): bool + public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget { $availableBudget = $this->user->availableBudgets() ->where('transaction_currency_id', $currency->id) @@ -545,7 +572,7 @@ class BudgetRepository implements BudgetRepositoryInterface $availableBudget->amount = $amount; $availableBudget->save(); - return true; + return $availableBudget; } /** @@ -652,6 +679,35 @@ class BudgetRepository implements BudgetRepositoryInterface return $budget; } + /** + * @param AvailableBudget $availableBudget + * @param array $data + * + * @return AvailableBudget + * @throws FireflyException + */ + public function updateAvailableBudget(AvailableBudget $availableBudget, array $data): AvailableBudget + { + $existing = $this->user->availableBudgets() + ->where('transaction_currency_id', $data['transaction_currency_id']) + ->where('start_date', $data['start_date']->format('Y-m-d 00:00:00')) + ->where('end_date', $data['end_date']->format('Y-m-d 00:00:00')) + ->where('id', '!=', $availableBudget->id) + ->first(); + + if (null !== $existing) { + throw new FireflyException(sprintf('An entry already exists for these parameters: available budget object with ID #%d', $existing->id)); + } + $availableBudget->transaction_currency_id = $data['transaction_currency_id']; + $availableBudget->start_date = $data['start_date']; + $availableBudget->end_date = $data['end_date']; + $availableBudget->amount = $data['amount']; + $availableBudget->save(); + + return $availableBudget; + + } + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index e2197b76ba..cbc9b224a3 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; +use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\TransactionCurrency; @@ -69,6 +70,11 @@ interface BudgetRepositoryInterface */ public function destroy(Budget $budget): bool; + /** + * @param AvailableBudget $availableBudget + */ + public function destroyAvailableBudget(AvailableBudget $availableBudget): void; + /** * Filters entries from the result set generated by getBudgetPeriodReport. * @@ -141,6 +147,13 @@ interface BudgetRepositoryInterface */ public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string; + /** + * Returns all available budget objects. + * + * @return Collection + */ + public function getAvailableBudgets(): Collection; + /** * @param Budget $budget * @param Carbon $start @@ -194,9 +207,9 @@ interface BudgetRepositoryInterface * @param Carbon $end * @param string $amount * - * @return bool + * @return AvailableBudget */ - public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): bool; + public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget; /** * @param User $user @@ -213,6 +226,7 @@ interface BudgetRepositoryInterface */ public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): string; + /** * @param Collection $accounts * @param Carbon $start @@ -237,6 +251,14 @@ interface BudgetRepositoryInterface */ public function update(Budget $budget, array $data): Budget; + /** + * @param AvailableBudget $availableBudget + * @param array $data + * + * @return AvailableBudget + */ + public function updateAvailableBudget(AvailableBudget $availableBudget, array $data): AvailableBudget; + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index c238a28dd8..64ec626911 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -132,7 +132,7 @@ class AccountTransformer extends TransformerAbstract */ public function includeUser(Account $account): Item { - return $this->item($account->user, new UserTransformer($this->parameters), 'user'); + return $this->item($account->user, new UserTransformer($this->parameters), 'users'); } /** diff --git a/app/Transformers/AttachmentTransformer.php b/app/Transformers/AttachmentTransformer.php index 81ba473173..f1c6ac01bd 100644 --- a/app/Transformers/AttachmentTransformer.php +++ b/app/Transformers/AttachmentTransformer.php @@ -74,7 +74,7 @@ class AttachmentTransformer extends TransformerAbstract */ public function includeUser(Attachment $attachment): Item { - return $this->item($attachment->user, new UserTransformer($this->parameters), 'user'); + return $this->item($attachment->user, new UserTransformer($this->parameters), 'users'); } /** diff --git a/app/Transformers/AvailableBudgetTransformer.php b/app/Transformers/AvailableBudgetTransformer.php new file mode 100644 index 0000000000..0f46939267 --- /dev/null +++ b/app/Transformers/AvailableBudgetTransformer.php @@ -0,0 +1,117 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\AvailableBudget; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class AvailableBudgetTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['transaction_currency', 'user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['transaction_currency']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Attach the currency. + * + * @codeCoverageIgnore + * + * @param AvailableBudget $availableBudget + * + * @return Item + */ + public function includeTransactionCurrency(AvailableBudget $availableBudget): Item + { + return $this->item($availableBudget->transactionCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + /** + * Attach the user. + * + * @codeCoverageIgnore + * + * @param AvailableBudget $availableBudget + * + * @return Item + */ + public function includeUser(AvailableBudget $availableBudget): Item + { + return $this->item($availableBudget->user, new UserTransformer($this->parameters), 'users'); + } + + /** + * Transform the note. + * + * @param AvailableBudget $availableBudget + * + * @return array + */ + public function transform(AvailableBudget $availableBudget): array + { + $data = [ + 'id' => (int)$availableBudget->id, + 'updated_at' => $availableBudget->updated_at->toAtomString(), + 'created_at' => $availableBudget->created_at->toAtomString(), + 'start_date' => $availableBudget->start_date->format('Y-m-d'), + 'end_date' => $availableBudget->end_date->format('Y-m-d'), + 'amount' => round($availableBudget->amount, $availableBudget->transactionCurrency->decimal_places), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/available_budgets/' . $availableBudget->id, + ], + ], + ]; + + return $data; + } + +} \ No newline at end of file diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index 1d60824b8c..8a0bba3a1c 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -84,7 +84,7 @@ class RecurrenceTransformer extends TransformerAbstract */ public function includeUser(Recurrence $recurrence): Item { - return $this->item($recurrence->user, new UserTransformer($this->parameters), 'user'); + return $this->item($recurrence->user, new UserTransformer($this->parameters), 'users'); } /** diff --git a/config/firefly.php b/config/firefly.php index 7721649b9b..0878d882da 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -83,16 +83,16 @@ use FireflyIII\TransactionRules\Triggers\UserAction; */ return [ - 'configuration' => [ + 'configuration' => [ 'single_user_mode' => true, 'is_demo_site' => false, ], - 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, - 'version' => '4.7.4', - 'api_version' => '0.3', - 'db_version' => 4, - 'maxUploadSize' => 15242880, - 'allowedMimes' => [ + 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, + 'version' => '4.7.4', + 'api_version' => '0.3', + 'db_version' => 4, + 'maxUploadSize' => 15242880, + 'allowedMimes' => [ /* plain files */ 'text/plain', @@ -154,8 +154,8 @@ return [ 'application/vnd.oasis.opendocument.database', 'application/vnd.oasis.opendocument.image', ], - 'list_length' => 10, - 'export_formats' => [ + 'list_length' => 10, + 'export_formats' => [ 'csv' => CsvExporter::class, ], 'default_export_format' => 'csv', @@ -260,6 +260,7 @@ return [ // models 'account' => \FireflyIII\Models\Account::class, 'attachment' => \FireflyIII\Models\Attachment::class, + 'availableBudget' => \FireflyIII\Models\AvailableBudget::class, 'bill' => \FireflyIII\Models\Bill::class, 'budget' => \FireflyIII\Models\Budget::class, 'budgetLimit' => \FireflyIII\Models\BudgetLimit::class, @@ -271,7 +272,7 @@ return [ 'piggyBank' => \FireflyIII\Models\PiggyBank::class, 'tj' => \FireflyIII\Models\TransactionJournal::class, 'tag' => \FireflyIII\Models\Tag::class, - 'recurrence' => \FireflyIII\Models\Recurrence::class, + 'recurrence' => \FireflyIII\Models\Recurrence::class, 'rule' => \FireflyIII\Models\Rule::class, 'ruleGroup' => \FireflyIII\Models\RuleGroup::class, 'exportJob' => \FireflyIII\Models\ExportJob::class, @@ -280,7 +281,7 @@ return [ 'user' => \FireflyIII\User::class, // strings - 'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, + 'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, // dates 'start_date' => \FireflyIII\Support\Binder\Date::class, diff --git a/routes/api.php b/routes/api.php index 6db99723e3..d4bc282787 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,7 +47,7 @@ Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'], function () { - // Accounts API routes: + // Attachment API routes: Route::get('', ['uses' => 'AttachmentController@index', 'as' => 'index']); Route::post('', ['uses' => 'AttachmentController@store', 'as' => 'store']); Route::get('{attachment}', ['uses' => 'AttachmentController@show', 'as' => 'show']); @@ -58,6 +58,21 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'available_budgets', 'as' => 'api.v1.available_budgets.'], + function () { + + // Available Budget API routes: + Route::get('', ['uses' => 'AvailableBudgetController@index', 'as' => 'index']); + Route::post('', ['uses' => 'AvailableBudgetController@store', 'as' => 'store']); + Route::get('{availableBudget}', ['uses' => 'AvailableBudgetController@show', 'as' => 'show']); + Route::get('{availableBudget}/download', ['uses' => 'AvailableBudgetController@download', 'as' => 'download']); + Route::post('{availableBudget}/upload', ['uses' => 'AvailableBudgetController@upload', 'as' => 'upload']); + Route::put('{availableBudget}', ['uses' => 'AvailableBudgetController@update', 'as' => 'update']); + Route::delete('{availableBudget}', ['uses' => 'AvailableBudgetController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'bills', 'as' => 'api.v1.bills.'], function () { From 0b9be029ac4e7108cd83ec7a3a9da55918ba3c7b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 13:20:29 +0200 Subject: [PATCH 057/134] Expand API with budget limits --- .../Controllers/AvailableBudgetController.php | 2 +- .../V1/Controllers/BudgetLimitController.php | 230 ++++++++++++++++++ app/Api/V1/Requests/BudgetLimitRequest.php | 77 ++++++ app/Factory/BillFactory.php | 4 +- app/Http/Requests/BillFormRequest.php | 3 - app/Models/BudgetLimit.php | 24 +- app/Repositories/Budget/BudgetRepository.php | 124 +++++++++- .../Budget/BudgetRepositoryInterface.php | 26 +- .../AvailableBudgetTransformer.php | 3 + app/Transformers/BudgetLimitTransformer.php | 103 ++++++++ resources/views/bills/create.twig | 2 - routes/api.php | 15 +- 12 files changed, 587 insertions(+), 26 deletions(-) create mode 100644 app/Api/V1/Controllers/BudgetLimitController.php create mode 100644 app/Api/V1/Requests/BudgetLimitRequest.php create mode 100644 app/Transformers/BudgetLimitTransformer.php diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php index c209802979..ee182bb457 100644 --- a/app/Api/V1/Controllers/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -97,7 +97,7 @@ class AvailableBudgetController extends Controller // types to get, page size: $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; - // get list of accounts. Count it and split it. + // get list of available budgets. Count it and split it. $collection = $this->repository->getAvailableBudgets(); $count = $collection->count(); $availableBudgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php new file mode 100644 index 0000000000..70c452157d --- /dev/null +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -0,0 +1,230 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use Carbon\Carbon; +use FireflyIII\Api\V1\Requests\AvailableBudgetRequest; +use FireflyIII\Api\V1\Requests\BudgetLimitRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Transformers\BudgetLimitTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use InvalidArgumentException; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; +use Log; + +/** + * Class BudgetLimitController + */ +class BudgetLimitController extends Controller +{ + ///** @var CurrencyRepositoryInterface */ + //private $currencyRepository; + /** @var BudgetRepositoryInterface */ + private $repository; + + /** + * AccountController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(BudgetRepositoryInterface::class); + //$this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param BudgetLimit $budgetLimit + * + * @return JsonResponse + */ + public function delete(BudgetLimit $budgetLimit): JsonResponse + { + $this->repository->destroyBudgetLimit($budgetLimit); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // read budget from request + $budgetId = (int)($request->get('budget_id') ?? 0); + $budget = null; + if ($budgetId > 0) { + $budget = $this->repository->findNull($budgetId); + } + // read start date from request + $start = null; + try { + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + $this->parameters->set('start', $start->format('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug(sprintf('Could not parse start date "%s": %s', $request->get('start'), $e->getMessage())); + + } + + // read end date from request + $end = null; + try { + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $this->parameters->set('end', $end->format('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug(sprintf('Could not parse end date "%s": %s', $request->get('end'), $e->getMessage())); + } + $this->parameters->set('budget_id', $budgetId); + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budget limits. Count it and split it. + $collection = new Collection; + if (null === $budget) { + $collection = $this->repository->getAllBudgetLimits($start, $end); + } + if (null !== $budget) { + $collection = $this->repository->getBudgetLimits($budget, $start, $end); + } + + $count = $collection->count(); + $budgetLimits = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.budget_limits.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($budgetLimits, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Display the specified resource. + * + * @param Request $request + * @param BudgetLimit $budgetLimit + * + * @return JsonResponse + */ + public function show(Request $request, BudgetLimit $budgetLimit): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Store a newly created resource in storage. + * + * @param BudgetLimitRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(BudgetLimitRequest $request): JsonResponse + { + $data = $request->getAll(); + $budget = $this->repository->findNull($data['budget_id']); + if (null === $budget) { + throw new FireflyException('Unknown budget.'); + } + $data['budget'] = $budget; + $budgetLimit = $this->repository->storeBudgetLimit($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * Update the specified resource in storage. + * + * @param AvailableBudgetRequest $request + * @param BudgetLimit $budgetLimit + * + * @return JsonResponse + */ + public function update(BudgetLimitRequest $request, BudgetLimit $budgetLimit): JsonResponse + { + $data = $request->getAll(); + $budget = $this->repository->findNull($data['budget_id']); + if (null === $budget) { + $budget = $budgetLimit->budget; + } + $data['budget'] = $budget; + $budgetLimit = $this->repository->updateBudgetLimit($budgetLimit, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/BudgetLimitRequest.php b/app/Api/V1/Requests/BudgetLimitRequest.php new file mode 100644 index 0000000000..8ede47cfce --- /dev/null +++ b/app/Api/V1/Requests/BudgetLimitRequest.php @@ -0,0 +1,77 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + + +/** + * Class BudgetLimitRequest + */ +class BudgetLimitRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'budget_id' => $this->integer('budget_id'), + 'start_date' => $this->date('start_date'), + 'end_date' => $this->date('end_date'), + 'amount' => $this->string('amount'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'budget_id' => 'required|exists:budgets,id|belongsToUser:budgets,id', + 'start_date' => 'required|before:end_date|date', + 'end_date' => 'required|after:start_date|date', + 'amount' => 'required|more:0', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + $rules['budget_id'] = 'required|exists:budgets,id|belongsToUser:budgets,id'; + break; + } + + return $rules; + } + +} \ No newline at end of file diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php index 94327af9ee..101d225452 100644 --- a/app/Factory/BillFactory.php +++ b/app/Factory/BillFactory.php @@ -58,8 +58,8 @@ class BillFactory 'date' => $data['date'], 'repeat_freq' => $data['repeat_freq'], 'skip' => $data['skip'], - 'automatch' => true, - 'active' => $data['active'], + 'automatch' => $data['automatch'] ?? true, + 'active' => $data['active'] ?? true, ] ); diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index 7b0b54083f..82e1581156 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -49,7 +49,6 @@ class BillFormRequest extends Request 'date' => $this->date('date'), 'repeat_freq' => $this->string('repeat_freq'), 'skip' => $this->integer('skip'), - 'active' => $this->boolean('active'), 'notes' => $this->string('notes'), ]; } @@ -73,8 +72,6 @@ class BillFormRequest extends Request 'date' => 'required|date', 'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly', 'skip' => 'required|between:0,31', - 'automatch' => 'in:1', - 'active' => 'in:1', ]; return $rules; diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index 2934a5b6f5..12c7b97612 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -22,11 +22,21 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class BudgetLimit. + * + * @property Budget $budget + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property Carbon $start_date + * @property Carbon $end_date + * @property string $amount */ class BudgetLimit extends Model { @@ -67,20 +77,10 @@ class BudgetLimit extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function budget() + public function budget(): BelongsTo { return $this->belongsTo(Budget::class); } - - /** - * @codeCoverageIgnore - * - * @param $value - */ - public function setAmountAttribute($value) - { - $this->attributes['amount'] = (string)round($value, 12); - } } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 8fb69c57c5..443a117405 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -154,6 +154,20 @@ class BudgetRepository implements BudgetRepositoryInterface return $return; } + /** + * Deletes a budget limit. + * + * @param BudgetLimit $budgetLimit + */ + public function deleteBudgetLimit(BudgetLimit $budgetLimit): void + { + try { + $budgetLimit->delete(); + } catch (Exception $e) { + Log::error(sprintf('Could not delete budget limit: %s', $e->getMessage())); + } + } + /** * @param Budget $budget * @@ -315,8 +329,35 @@ class BudgetRepository implements BudgetRepositoryInterface * * @return Collection */ - public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection + public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection { + // both are NULL: + if (null === $start && null === $end) { + $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->with(['budget']) + ->where('budgets.user_id', $this->user->id) + ->get(['budget_limits.*']); + + return $set; + } + // one of the two is NULL. + if (null === $start xor null === $end) { + $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') + ->with(['budget']) + ->where('budgets.user_id', $this->user->id); + if (null !== $end) { + // end date must be before $end. + $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + if (null !== $start) { + // start date must be after $start. + $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00')); + } + $set = $query->get(['budget_limits.*']); + + return $set; + } + // neither are NULL: $set = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') ->with(['budget']) ->where('budgets.user_id', $this->user->id) @@ -389,8 +430,28 @@ class BudgetRepository implements BudgetRepositoryInterface * * @return Collection */ - public function getBudgetLimits(Budget $budget, Carbon $start, Carbon $end): Collection + public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection { + if (null === $end && null === $start) { + return $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); + } + if (null === $end xor null === $start) { + $query = $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC'); + // one of the two is null + if (null !== $end) { + // end date must be before $end. + $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + if (null !== $start) { + // start date must be after $start. + $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00')); + } + $set = $query->get(['budget_limits.*']); + + return $set; + } + + // when both dates are set: $set = $budget->budgetlimits() ->where( function (Builder $q5) use ($start, $end) { @@ -663,6 +724,42 @@ class BudgetRepository implements BudgetRepositoryInterface return $newBudget; } + /** + * @param array $data + * + * @throws FireflyException + * @return BudgetLimit + */ + public function storeBudgetLimit(array $data): BudgetLimit + { + $this->cleanupBudgets(); + /** @var Budget $budget */ + $budget = $data['budget']; + + // find limit with same date range. + // if it exists, throw error. + $limits = $budget->budgetlimits() + ->where('budget_limits.start_date', $data['start_date']->format('Y-m-d 00:00:00')) + ->where('budget_limits.end_date', $data['end_date']->format('Y-m-d 00:00:00')) + ->get(['budget_limits.*'])->count(); + Log::debug(sprintf('Found %d budget limits.', $limits)); + if ($limits > 0) { + throw new FireflyException('A budget limit for this budget, and this date range already exists. You must update the existing one.'); + } + + 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 = $data['start_date']->format('Y-m-d 00:00:00'); + $limit->end_date = $data['end_date']->format('Y-m-d 00:00:00'); + $limit->amount = $data['amount']; + $limit->save(); + Log::debug(sprintf('Created new budget limit with ID #%d and amount %s', $limit->id, $data['amount'])); + + return $limit; + } + /** * @param Budget $budget * @param array $data @@ -708,6 +805,29 @@ class BudgetRepository implements BudgetRepositoryInterface } + /** + * @param BudgetLimit $budgetLimit + * @param array $data + * + * @return BudgetLimit + * @throws Exception + */ + public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit + { + $this->cleanupBudgets(); + /** @var Budget $budget */ + $budget = $data['budget']; + + $budgetLimit->budget()->associate($budget); + $budgetLimit->start_date = $data['start_date']->format('Y-m-d 00:00:00'); + $budgetLimit->end_date = $data['end_date']->format('Y-m-d 00:00:00'); + $budgetLimit->amount = $data['amount']; + $budgetLimit->save(); + Log::debug(sprintf('Updated budget limit with ID #%d and amount %s', $budgetLimit->id, $data['amount'])); + + return $budgetLimit; + } + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index cbc9b224a3..d08e406a3d 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -63,6 +63,13 @@ interface BudgetRepositoryInterface */ public function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array; + /** + * Deletes a budget limit. + * + * @param BudgetLimit $budgetLimit + */ + public function deleteBudgetLimit(BudgetLimit $budgetLimit): void; + /** * @param Budget $budget * @@ -136,7 +143,7 @@ interface BudgetRepositoryInterface * * @return Collection */ - public function getAllBudgetLimits(Carbon $start, Carbon $end): Collection; + public function getAllBudgetLimits(Carbon $start = null, Carbon $end = null): Collection; /** * @param TransactionCurrency $currency @@ -161,7 +168,7 @@ interface BudgetRepositoryInterface * * @return Collection */ - public function getBudgetLimits(Budget $budget, Carbon $start, Carbon $end): Collection; + public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection; /** * @param Collection $budgets @@ -259,6 +266,21 @@ interface BudgetRepositoryInterface */ public function updateAvailableBudget(AvailableBudget $availableBudget, array $data): AvailableBudget; + /** + * @param BudgetLimit $budgetLimit + * @param array $data + * + * @return BudgetLimit + */ + public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit; + + /** + * @param array $data + * + * @return BudgetLimit + */ + public function storeBudgetLimit(array $data): BudgetLimit; + /** * @param Budget $budget * @param Carbon $start diff --git a/app/Transformers/AvailableBudgetTransformer.php b/app/Transformers/AvailableBudgetTransformer.php index 0f46939267..e50d0a162f 100644 --- a/app/Transformers/AvailableBudgetTransformer.php +++ b/app/Transformers/AvailableBudgetTransformer.php @@ -29,6 +29,9 @@ use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; +/** + * Class AvailableBudgetTransformer + */ class AvailableBudgetTransformer extends TransformerAbstract { /** diff --git a/app/Transformers/BudgetLimitTransformer.php b/app/Transformers/BudgetLimitTransformer.php new file mode 100644 index 0000000000..462290fdb9 --- /dev/null +++ b/app/Transformers/BudgetLimitTransformer.php @@ -0,0 +1,103 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; +use FireflyIII\Models\BudgetLimit; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class BudgetLimitTransformer + */ +class BudgetLimitTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['budget']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['budget']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Attach the budget. + * + * @codeCoverageIgnore + * + * @param BudgetLimit $budgetLimit + * + * @return Item + */ + public function includeBudget(BudgetLimit $budgetLimit): Item + { + return $this->item($budgetLimit->budget, new BudgetTransformer($this->parameters), 'budgets'); + } + + /** + * Transform the note. + * + * @param BudgetLimit $budgetLimit + * + * @return array + */ + public function transform(BudgetLimit $budgetLimit): array + { + $data = [ + 'id' => (int)$budgetLimit->id, + 'updated_at' => $budgetLimit->updated_at->toAtomString(), + 'created_at' => $budgetLimit->created_at->toAtomString(), + 'start_date' => $budgetLimit->start_date->format('Y-m-d'), + 'end_date' => $budgetLimit->end_date->format('Y-m-d'), + 'amount' => $budgetLimit->amount, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/budget_limits/' . $budgetLimit->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/resources/views/bills/create.twig b/resources/views/bills/create.twig index 5e5508d02c..2548c0ed6b 100644 --- a/resources/views/bills/create.twig +++ b/resources/views/bills/create.twig @@ -35,8 +35,6 @@ {{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }} {{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }} {{ ExpandedForm.integer('skip',0) }} - {# only correct way to do active checkbox #} - {{ ExpandedForm.checkbox('active', 1) }}
        diff --git a/routes/api.php b/routes/api.php index d4bc282787..ae67e1232d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -66,13 +66,24 @@ Route::group( Route::get('', ['uses' => 'AvailableBudgetController@index', 'as' => 'index']); Route::post('', ['uses' => 'AvailableBudgetController@store', 'as' => 'store']); Route::get('{availableBudget}', ['uses' => 'AvailableBudgetController@show', 'as' => 'show']); - Route::get('{availableBudget}/download', ['uses' => 'AvailableBudgetController@download', 'as' => 'download']); - Route::post('{availableBudget}/upload', ['uses' => 'AvailableBudgetController@upload', 'as' => 'upload']); Route::put('{availableBudget}', ['uses' => 'AvailableBudgetController@update', 'as' => 'update']); Route::delete('{availableBudget}', ['uses' => 'AvailableBudgetController@delete', 'as' => 'delete']); } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budget_limits', 'as' => 'api.v1.budget_limits.'], + function () { + + // Budget Limit API routes: + Route::get('', ['uses' => 'BudgetLimitController@index', 'as' => 'index']); + Route::post('', ['uses' => 'BudgetLimitController@store', 'as' => 'store']); + Route::get('{budgetLimit}', ['uses' => 'BudgetLimitController@show', 'as' => 'show']); + Route::put('{budgetLimit}', ['uses' => 'BudgetLimitController@update', 'as' => 'update']); + Route::delete('{budgetLimit}', ['uses' => 'BudgetLimitController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'bills', 'as' => 'api.v1.bills.'], function () { From 72cca5ccbf637e438409af39c41de7e577bb118b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 13:23:08 +0200 Subject: [PATCH 058/134] Expand schedule. --- app/Console/Kernel.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 20350913e4..23c1135e92 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -32,7 +32,7 @@ use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; /** - * File to make sure commnds work. + * File to make sure commands work. */ class Kernel extends ConsoleKernel { @@ -48,7 +48,7 @@ class Kernel extends ConsoleKernel /** * Register the commands for the application. */ - protected function commands() + protected function commands(): void { $this->load(__DIR__ . '/Commands'); @@ -59,8 +59,6 @@ class Kernel extends ConsoleKernel * Define the application's command schedule. * * @param \Illuminate\Console\Scheduling\Schedule $schedule - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function schedule(Schedule $schedule): void { @@ -76,5 +74,15 @@ class Kernel extends ConsoleKernel event(new AdminRequestedTestMessage($user, $ipAddress)); } )->daily(); + + // send test email. + $schedule->call( + function () { + $ipAddress = '127.0.0.2'; + /** @var User $user */ + $user = User::find(1); + event(new AdminRequestedTestMessage($user, $ipAddress)); + } + )->hourly(); } } From e05664f34fb23683bdeee6c42a8e6720da88b0a9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 13:46:34 +0200 Subject: [PATCH 059/134] Fix #1509 --- app/Http/Controllers/Transaction/SplitController.php | 2 +- app/Models/TransactionJournal.php | 1 + app/Support/Preferences.php | 5 ++++- app/Support/Twig/Journal.php | 1 + public/js/ff/transactions/split/edit.js | 4 ++-- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index aa25f47fdc..d5276872f5 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -157,7 +157,7 @@ class SplitController extends Controller $type = strtolower($this->repository->getTransactionType($journal)); session()->flash('success', (string)trans('firefly.updated_' . $type, ['description' => $journal->description])); - Preferences::mark(); + app('preferences')->mark(); // @codeCoverageIgnoreStart if (1 === (int)$request->get('return_to_edit')) { diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index e54c69266f..28da385b2f 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -41,6 +41,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * * @property User $user * @property int $bill_id + * @property Collection $categories */ class TransactionJournal extends Model { diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index cc24ef44f1..9dc524af8b 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -163,14 +163,17 @@ class Preferences { $lastActivity = microtime(); $preference = $this->get('lastActivity', microtime()); + if (null !== $preference && null !== $preference->data) { $lastActivity = $preference->data; } if (\is_array($lastActivity)) { $lastActivity = implode(',', $lastActivity); } + $hash = md5($lastActivity); + Log::debug(sprintf('Value of last activity is %s, hash is %s', $lastActivity, $hash)); - return md5($lastActivity); + return $hash; } /** diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index c68a7bedfe..d8613a6e4f 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -198,6 +198,7 @@ class Journal extends Twig_Extension ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') ->where('categories.user_id', $journal->user_id) ->where('transaction_journals.id', $journal->id) + ->whereNull('transactions.deleted_at') ->get(['categories.*']); /** @var Category $category */ foreach ($set as $category) { diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index a99a39f651..a780fe071f 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -188,12 +188,12 @@ function resetDivSplits() { // ends with ][destination_account_name] $.each($('input[name$="][destination_name]"]'), function (i, v) { var input = $(v); - input.attr('name', 'transactions[' + i + '][destination_account_name]'); + input.attr('name', 'transactions[' + i + '][destination_name]'); }); // ends with ][source_account_name] $.each($('input[name$="][source_name]"]'), function (i, v) { var input = $(v); - input.attr('name', 'transactions[' + i + '][source_account_name]'); + input.attr('name', 'transactions[' + i + '][source_name]'); }); // ends with ][amount] $.each($('input[name$="][amount]"]'), function (i, v) { From 96b482dac51e0f4f1c8f38dc2ec3d1f84294332e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 13:59:20 +0200 Subject: [PATCH 060/134] Fix #1510 --- .../Routine/File/ImportableConverter.php | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 6cc4f65141..f4a6870501 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -129,6 +129,28 @@ class ImportableConverter $this->mappedValues = $mappedValues; } + /** + * @param string|null $date + * + * @return string|null + */ + private function convertDateValue(string $date = null): ?string + { + if (null === $date) { + return null; + } + try { + $object = Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $date); + } catch (InvalidDateException|InvalidArgumentException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + + return null; + } + + return $object->format('Y-m-d'); + } + /** * @param ImportTransaction $importable * @@ -224,19 +246,13 @@ class ImportableConverter throw new FireflyException($message); } - // throw error when both are he same - - try { - $date = Carbon::createFromFormat($this->config['date-format'] ?? 'Ymd', $importable->date); - } catch (InvalidDateException|InvalidArgumentException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - $date = new Carbon; + $dateStr = $this->convertDateValue($importable->date); + if (null === $dateStr) { + $date = new Carbon; + $dateStr = $date->format('Y-m-d'); + unset($date); } - - $dateStr = $date->format('Y-m-d'); - return [ 'type' => $transactionType, 'date' => $dateStr, @@ -253,12 +269,12 @@ class ImportableConverter 'sepa-country' => $importable->meta['sepa-countru'] ?? null, 'sepa-ep' => $importable->meta['sepa-ep'] ?? null, 'sepa-ci' => $importable->meta['sepa-ci'] ?? null, - 'interest_date' => $importable->meta['date-interest'] ?? null, - 'book_date' => $importable->meta['date-book'] ?? null, - 'process_date' => $importable->meta['date-process'] ?? null, - 'due_date' => $importable->meta['date-due'] ?? null, - 'payment_date' => $importable->meta['date-payment'] ?? null, - 'invoice_date' => $importable->meta['date-invoice'] ?? null, + 'interest_date' => $this->convertDateValue($importable->meta['date-interest'] ?? null), + 'book_date' => $this->convertDateValue($importable->meta['date-book'] ?? null), + 'process_date' => $this->convertDateValue($importable->meta['date-process'] ?? null), + 'due_date' => $this->convertDateValue($importable->meta['date-due'] ?? null), + 'payment_date' => $this->convertDateValue($importable->meta['date-payment'] ?? null), + 'invoice_date' => $this->convertDateValue($importable->meta['date-invoice'] ?? null), 'external_id' => $importable->externalId, // journal data: From dca2dc4600cf798fd7ed07042b2e71a80343364a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 14:54:06 +0200 Subject: [PATCH 061/134] Add budgets to API. --- app/Api/V1/Controllers/BudgetController.php | 176 ++++++++++++++++++++ app/Api/V1/Requests/BudgetRequest.php | 76 +++++++++ app/Transformers/BudgetTransformer.php | 2 +- routes/api.php | 15 ++ 4 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 app/Api/V1/Controllers/BudgetController.php create mode 100644 app/Api/V1/Requests/BudgetRequest.php diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php new file mode 100644 index 0000000000..96e0c14892 --- /dev/null +++ b/app/Api/V1/Controllers/BudgetController.php @@ -0,0 +1,176 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\BudgetRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Budget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Transformers\BudgetTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class BudgetController + */ +class BudgetController extends Controller +{ + /** @var BudgetRepositoryInterface */ + private $repository; + + /** + * BillController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var BudgetRepositoryInterface repository */ + $this->repository = app(BudgetRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param Budget $budget + * + * @return JsonResponse + */ + public function delete(Budget $budget): JsonResponse + { + $this->repository->destroy($budget); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getBudgets(); + $count = $collection->count(); + $budgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($budgets, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.budgets.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($budgets, new BudgetTransformer($this->parameters), 'budgets'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + + /** + * @param Request $request + * @param Budget $budget + * + * @return JsonResponse + */ + public function show(Request $request, Budget $budget): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param BudgetRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(BudgetRequest $request): JsonResponse + { + $budget = $this->repository->store($request->getAll()); + if (null !== $budget) { + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + throw new FireflyException('Could not store new budget.'); // @codeCoverageIgnore + } + + + /** + * @param BudgetRequest $request + * @param Budget $budget + * + * @return JsonResponse + */ + public function update(BudgetRequest $request, Budget $budget): JsonResponse + { + $data = $request->getAll(); + $bill = $this->repository->update($budget, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($bill, new BudgetTransformer($this->parameters), 'budgets'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/BudgetRequest.php b/app/Api/V1/Requests/BudgetRequest.php new file mode 100644 index 0000000000..e90866d505 --- /dev/null +++ b/app/Api/V1/Requests/BudgetRequest.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\Budget; + +/** + * Class BudgetRequest + */ +class BudgetRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'active' => $this->boolean('active'), + 'order' => 0, + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|between:1,100|uniqueObjectForUser:budgets,name', + 'active' => 'required|boolean', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var Budget $budget */ + $budget = $this->route()->parameter('budget'); + $rules['name'] = sprintf('required|between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id); + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Transformers/BudgetTransformer.php b/app/Transformers/BudgetTransformer.php index 698c3009b8..4b9562292a 100644 --- a/app/Transformers/BudgetTransformer.php +++ b/app/Transformers/BudgetTransformer.php @@ -48,7 +48,7 @@ class BudgetTransformer extends TransformerAbstract * * @var array */ - protected $defaultIncludes = []; + protected $defaultIncludes = ['user']; /** @var ParameterBag */ protected $parameters; diff --git a/routes/api.php b/routes/api.php index ae67e1232d..9ac1b78a0a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -97,6 +97,19 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'budgets', 'as' => 'api.v1.budgets.'], + function () { + + // Budget API routes: + Route::get('', ['uses' => 'BudgetController@index', 'as' => 'index']); + Route::post('', ['uses' => 'BudgetController@store', 'as' => 'store']); + Route::get('{budget}', ['uses' => 'BudgetController@show', 'as' => 'show']); + Route::put('{budget}', ['uses' => 'BudgetController@update', 'as' => 'update']); + Route::delete('{budget}', ['uses' => 'BudgetController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () { @@ -123,6 +136,8 @@ Route::group( } ); + + Route::group( ['middleware' => ['auth:api', 'bindings', \FireflyIII\Http\Middleware\IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users', 'as' => 'api.v1.users.'], function () { From 096af00a7240e94604437a3befbdca834525f717 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 15:05:59 +0200 Subject: [PATCH 062/134] Add Category to API --- app/Api/V1/Controllers/BudgetController.php | 2 +- app/Api/V1/Controllers/CategoryController.php | 176 ++++++++++++++++++ app/Api/V1/Requests/CategoryRequest.php | 75 ++++++++ app/Transformers/CategoryTransformer.php | 2 +- routes/api.php | 39 ++-- 5 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 app/Api/V1/Controllers/CategoryController.php create mode 100644 app/Api/V1/Requests/CategoryRequest.php diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php index 96e0c14892..5b153f5027 100644 --- a/app/Api/V1/Controllers/BudgetController.php +++ b/app/Api/V1/Controllers/BudgetController.php @@ -46,7 +46,7 @@ class BudgetController extends Controller private $repository; /** - * BillController constructor. + * BudgetController constructor. */ public function __construct() { diff --git a/app/Api/V1/Controllers/CategoryController.php b/app/Api/V1/Controllers/CategoryController.php new file mode 100644 index 0000000000..672b044ea0 --- /dev/null +++ b/app/Api/V1/Controllers/CategoryController.php @@ -0,0 +1,176 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\CategoryRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Transformers\CategoryTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * Class CategoryController + */ +class CategoryController extends Controller +{ + /** @var CategoryRepositoryInterface */ + private $repository; + + /** + * CategoryController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var CategoryRepositoryInterface repository */ + $this->repository = app(CategoryRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + * Remove the specified resource from storage. + * + * @param Category $category + * + * @return JsonResponse + */ + public function delete(Category $category): JsonResponse + { + $this->repository->destroy($category); + + return response()->json([], 204); + } + + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return JsonResponse + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getCategories(); + $count = $collection->count(); + $categories = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($categories, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.categories.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($categories, new CategoryTransformer($this->parameters), 'categories'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + + /** + * @param Request $request + * @param Category $category + * + * @return JsonResponse + */ + public function show(Request $request, Category $category): JsonResponse + { + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param CategoryRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(CategoryRequest $request): JsonResponse + { + $category = $this->repository->store($request->getAll()); + if (null !== $category) { + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + throw new FireflyException('Could not store new category.'); // @codeCoverageIgnore + } + + + /** + * @param CategoryRequest $request + * @param Category $category + * + * @return JsonResponse + */ + public function update(CategoryRequest $request, Category $category): JsonResponse + { + $data = $request->getAll(); + $bill = $this->repository->update($category, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($bill, new CategoryTransformer($this->parameters), 'categories'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/CategoryRequest.php b/app/Api/V1/Requests/CategoryRequest.php new file mode 100644 index 0000000000..b910674e54 --- /dev/null +++ b/app/Api/V1/Requests/CategoryRequest.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\Category; + +/** + * Class CategoryRequest + */ +class CategoryRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'active' => $this->boolean('active'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|between:1,100|uniqueObjectForUser:categories,name', + 'active' => 'required|boolean', + ]; + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var Category $category */ + $category = $this->route()->parameter('category'); + $rules['name'] = sprintf('required|between:1,100|uniqueObjectForUser:categories,name,%d', $category->id); + break; + } + + return $rules; + } +} \ No newline at end of file diff --git a/app/Transformers/CategoryTransformer.php b/app/Transformers/CategoryTransformer.php index 3d7de337c9..421946e6b2 100644 --- a/app/Transformers/CategoryTransformer.php +++ b/app/Transformers/CategoryTransformer.php @@ -48,7 +48,7 @@ class CategoryTransformer extends TransformerAbstract * * @var array */ - protected $defaultIncludes = []; + protected $defaultIncludes = ['user']; /** @var ParameterBag */ protected $parameters; diff --git a/routes/api.php b/routes/api.php index 9ac1b78a0a..d04d0029f2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -44,7 +44,7 @@ Route::group( ); Route::group( - ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'], + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'], function () { // Attachment API routes: @@ -59,7 +59,8 @@ Route::group( ); Route::group( - ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'available_budgets', 'as' => 'api.v1.available_budgets.'], + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'available_budgets', + 'as' => 'api.v1.available_budgets.'], function () { // Available Budget API routes: @@ -110,17 +111,31 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'categories', 'as' => 'api.v1.categories.'], + function () { + + // Category API routes: + Route::get('', ['uses' => 'CategoryController@index', 'as' => 'index']); + Route::post('', ['uses' => 'CategoryController@store', 'as' => 'store']); + Route::get('{category}', ['uses' => 'CategoryController@show', 'as' => 'show']); + Route::put('{category}', ['uses' => 'CategoryController@update', 'as' => 'update']); + Route::delete('{category}', ['uses' => 'CategoryController@delete', 'as' => 'delete']); + } +); + Route::group( - ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () { + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], + function () { - // Transaction currency API routes: - Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']); - Route::post('', ['uses' => 'CurrencyController@store', 'as' => 'store']); - Route::get('{currency}', ['uses' => 'CurrencyController@show', 'as' => 'show']); - Route::put('{currency}', ['uses' => 'CurrencyController@update', 'as' => 'update']); - Route::delete('{currency}', ['uses' => 'CurrencyController@delete', 'as' => 'delete']); -} + // Transaction currency API routes: + Route::get('', ['uses' => 'CurrencyController@index', 'as' => 'index']); + Route::post('', ['uses' => 'CurrencyController@store', 'as' => 'store']); + Route::get('{currency}', ['uses' => 'CurrencyController@show', 'as' => 'show']); + Route::put('{currency}', ['uses' => 'CurrencyController@update', 'as' => 'update']); + Route::delete('{currency}', ['uses' => 'CurrencyController@delete', 'as' => 'delete']); + } ); Route::group( @@ -137,9 +152,9 @@ Route::group( ); - Route::group( - ['middleware' => ['auth:api', 'bindings', \FireflyIII\Http\Middleware\IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users', 'as' => 'api.v1.users.'], + ['middleware' => ['auth:api', 'bindings', \FireflyIII\Http\Middleware\IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users', + 'as' => 'api.v1.users.'], function () { // Users API routes: From 5b0e61033cc7006e425975d951b4407ce802724c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 15:33:36 +0200 Subject: [PATCH 063/134] Add configuration to Api. --- .../Controllers/ConfigurationController.php | 104 ++++++++++++++++++ app/Models/Configuration.php | 2 + app/Support/FireflyConfig.php | 2 +- routes/api.php | 9 ++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 app/Api/V1/Controllers/ConfigurationController.php diff --git a/app/Api/V1/Controllers/ConfigurationController.php b/app/Api/V1/Controllers/ConfigurationController.php new file mode 100644 index 0000000000..40c019f943 --- /dev/null +++ b/app/Api/V1/Controllers/ConfigurationController.php @@ -0,0 +1,104 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Configuration; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + +/** + * Class ConfigurationController + */ +class ConfigurationController extends Controller +{ + + /** + * @throws FireflyException + */ + public function index() + { + if (!auth()->user()->hasRole('owner')) { + throw new FireflyException('No access to method.'); // @codeCoverageIgnore + } + $configData = $this->getConfigData(); + + return response()->json(['data' => $configData], 200)->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param Request $request + * + * @throws FireflyException + */ + public function update(Request $request): JsonResponse + { + if (!auth()->user()->hasRole('owner')) { + throw new FireflyException('No access to method.'); // @codeCoverageIgnore + } + $name = $request->get('name'); + $value = $request->get('value'); + $valid = ['is_demo_site', 'permission_update_check', 'single_user_mode']; + if (!\in_array($name, $valid, true)) { + throw new FireflyException('You cannot edit this configuration value.'); + } + $configValue = ''; + switch ($name) { + case 'is_demo_site': + case 'single_user_mode': + $configValue = $value === 'true'; + break; + case 'permission_update_check': + $configValue = (int)$value >= -1 && (int)$value <= 1 ? (int)$value : -1; + break; + } + app('fireflyconfig')->set($name, $configValue); + $configData = $this->getConfigData(); + + return response()->json(['data' => $configData], 200)->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @return array + */ + private function getConfigData(): array + { + /** @var Configuration $isDemoSite */ + $isDemoSite = app('fireflyconfig')->get('is_demo_site'); + /** @var Configuration $updateCheck */ + $updateCheck = app('fireflyconfig')->get('permission_update_check'); + /** @var Configuration $lastCheck */ + $lastCheck = app('fireflyconfig')->get('last_update_check'); + /** @var Configuration $singleUser */ + $singleUser = app('fireflyconfig')->get('single_user_mode'); + $data = [ + 'is_demo_site' => null === $isDemoSite ? null : $isDemoSite->data, + 'permission_update_check' => null === $updateCheck ? null : (int)$updateCheck->data, + 'last_update_check' => null === $lastCheck ? null : (int)$lastCheck->data, + 'single_user_mode' => null === $singleUser ? null : $singleUser->data, + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 6a2b97c9bf..3a0617d92f 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -27,6 +27,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; /** * Class Configuration. + * + * @property string $data */ class Configuration extends Model { diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index 19b8feca8a..0890e601ad 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -55,7 +55,7 @@ class FireflyConfig * * @return \FireflyIII\Models\Configuration|null */ - public function get($name, $default = null) + public function get($name, $default = null): ?Configuration { $fullName = 'ff-config-' . $name; if (Cache::has($fullName)) { diff --git a/routes/api.php b/routes/api.php index d04d0029f2..24a9fab273 100644 --- a/routes/api.php +++ b/routes/api.php @@ -124,6 +124,15 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'configuration', 'as' => 'api.v1.configuration.'], + function () { + + // Configuration API routes: + Route::get('', ['uses' => 'ConfigurationController@index', 'as' => 'index']); + Route::put('', ['uses' => 'ConfigurationController@update', 'as' => 'update']); + } +); Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], From 59f5b38dca226852dfac3cda15471e3dedbcdfbb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 15:43:05 +0200 Subject: [PATCH 064/134] Fix for #1509 --- public/js/ff/transactions/split/edit.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index a780fe071f..61af5fc050 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -100,6 +100,12 @@ function removeDivRow(e) { } var row = $(e.target); var index = row.data('split'); + if (typeof index === 'undefined') { + var parent = row.parent(); + index = parent.data('split'); + console.log('Parent. ' + parent.className); + } + console.log('Split index is "' + index + '"'); $('div.split_row[data-split="' + index + '"]').remove(); From 89f8f9b45be0cd1ceb1d7104d397dab9b8e434fe Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 24 Jun 2018 16:17:42 +0200 Subject: [PATCH 065/134] Fix view of bills. --- app/Console/Kernel.php | 10 ---------- app/Http/Requests/BillFormRequest.php | 2 ++ app/Services/Internal/Update/BillUpdateService.php | 2 +- resources/views/bills/show.twig | 4 ++-- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 23c1135e92..f9fba1df4b 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -74,15 +74,5 @@ class Kernel extends ConsoleKernel event(new AdminRequestedTestMessage($user, $ipAddress)); } )->daily(); - - // send test email. - $schedule->call( - function () { - $ipAddress = '127.0.0.2'; - /** @var User $user */ - $user = User::find(1); - event(new AdminRequestedTestMessage($user, $ipAddress)); - } - )->hourly(); } } diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index 82e1581156..df36e4946e 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -50,6 +50,7 @@ class BillFormRequest extends Request 'repeat_freq' => $this->string('repeat_freq'), 'skip' => $this->integer('skip'), 'notes' => $this->string('notes'), + 'active' => $this->boolean('active'), ]; } @@ -72,6 +73,7 @@ class BillFormRequest extends Request 'date' => 'required|date', 'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly', 'skip' => 'required|between:0,31', + 'active' => 'boolean', ]; return $rules; diff --git a/app/Services/Internal/Update/BillUpdateService.php b/app/Services/Internal/Update/BillUpdateService.php index 97e89b1753..72c811bee2 100644 --- a/app/Services/Internal/Update/BillUpdateService.php +++ b/app/Services/Internal/Update/BillUpdateService.php @@ -51,7 +51,7 @@ class BillUpdateService $bill->repeat_freq = $data['repeat_freq']; $bill->skip = $data['skip']; $bill->automatch = true; - $bill->active = $data['active']; + $bill->active = $data['active']??true; $bill->save(); // update note: diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index fe877f39e1..f22355adb5 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -91,13 +91,13 @@ {{ 'rescan_old'|_ }}
      - {% if object.data.notes != '' %} + {% if object.data.notes.data[0] %}

      {{ 'notes'|_ }}

      - {{ object.data.notes }} + {{ object.data.notes.data[0].markdown|raw }}
      {% endif %} From 7591f3fa2949d3ba7987fc5e03c12638cdd12a7a Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 25 Jun 2018 16:01:45 +0200 Subject: [PATCH 066/134] Send mail message when cron has hit. --- app/Events/RequestedReportOnJournals.php | 45 ++++++++++++ app/Handlers/Events/AutomationHandler.php | 71 +++++++++++++++++++ app/Handlers/Events/UserEventHandler.php | 2 +- app/Jobs/CreateRecurringTransactions.php | 16 +++-- app/Mail/ReportNewJournalsMail.php | 71 +++++++++++++++++++ app/Providers/EventServiceProvider.php | 4 ++ .../emails/report-new-journals-html.twig | 32 +++++++++ .../emails/report-new-journals-text.twig | 25 +++++++ 8 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 app/Events/RequestedReportOnJournals.php create mode 100644 app/Handlers/Events/AutomationHandler.php create mode 100644 app/Mail/ReportNewJournalsMail.php create mode 100644 resources/views/emails/report-new-journals-html.twig create mode 100644 resources/views/emails/report-new-journals-text.twig diff --git a/app/Events/RequestedReportOnJournals.php b/app/Events/RequestedReportOnJournals.php new file mode 100644 index 0000000000..9956ee62fd --- /dev/null +++ b/app/Events/RequestedReportOnJournals.php @@ -0,0 +1,45 @@ +userId = $userId; + $this->journals = $journals; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Handlers/Events/AutomationHandler.php b/app/Handlers/Events/AutomationHandler.php new file mode 100644 index 0000000000..697371afcf --- /dev/null +++ b/app/Handlers/Events/AutomationHandler.php @@ -0,0 +1,71 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Handlers\Events; + +use Exception; +use FireflyIII\Events\RequestedReportOnJournals; +use FireflyIII\Mail\ReportNewJournalsMail; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Log; +use Mail; + +/** + * Class AutomationHandler + */ +class AutomationHandler +{ + + /** + * @param RequestedReportOnJournals $event + * + * @return bool + */ + public function reportJournals(RequestedReportOnJournals $event): bool + { + Log::debug('In reportJournals.'); + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + $user = $repository->findNull($event->userId); + if (null === $user) { + Log::debug('User is NULL'); + return true; + } + if ($event->journals->count() === 0) { + Log::debug('No journals.'); + return true; + } + + try { + Log::debug('Trying to mail...'); + Mail::to($user->email)->send(new ReportNewJournalsMail($user->email, '127.0.0.1', $event->journals)); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::error($e->getMessage()); + } + Log::debug('Done!'); + + // @codeCoverageIgnoreEnd + return true; + } +} \ No newline at end of file diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index c0b6acaa5d..64ef4a83fc 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -113,7 +113,7 @@ class UserEventHandler * * @return bool */ - function demoUserBackToEnglish(Login $event): bool + public function demoUserBackToEnglish(Login $event): bool { /** @var UserRepositoryInterface $repository */ $repository = app(UserRepositoryInterface::class); diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index af2f210ebe..45cfe72936 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -3,11 +3,14 @@ namespace FireflyIII\Jobs; use Carbon\Carbon; +use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -29,6 +32,8 @@ class CreateRecurringTransactions implements ShouldQueue private $journalRepository; /** @var RecurringRepositoryInterface */ private $repository; + /** @var UserRepositoryInterface */ + private $userRepository; /** * Create a new job instance. @@ -41,6 +46,7 @@ class CreateRecurringTransactions implements ShouldQueue $this->date = $date; $this->repository = app(RecurringRepositoryInterface::class); $this->journalRepository = app(JournalRepositoryInterface::class); + $this->userRepository = app(UserRepositoryInterface::class); } /** @@ -76,14 +82,16 @@ class CreateRecurringTransactions implements ShouldQueue $this->journalRepository->setUser($recurrence->user); Log::debug(sprintf('Now at recurrence #%d', $recurrence->id)); $created = $this->handleRepetitions($recurrence); - Log::debug(sprintf('Done with recurrence #%c', $recurrence->id)); - + Log::debug(sprintf('Done with recurrence #%d', $recurrence->id)); $result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($created); } + Log::debug('Now running report thing.'); // will now send email to users. - foreach($result as $userId => $journals) { - $this->sendReport($userId, $journals); + foreach ($result as $userId => $journals) { + // random bunch to make mail. + $journals = TransactionJournal::where('user_id', $userId)->inRandomOrder()->take(1)->get(); + event(new RequestedReportOnJournals($userId, $journals)); } Log::debug('Done with handle()'); diff --git a/app/Mail/ReportNewJournalsMail.php b/app/Mail/ReportNewJournalsMail.php new file mode 100644 index 0000000000..d1f1d2445e --- /dev/null +++ b/app/Mail/ReportNewJournalsMail.php @@ -0,0 +1,71 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Mail; + +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Collection; + +/** + * Class ReportNewJournalsMail. + * + * Sends a list of newly created journals to the user. + */ +class ReportNewJournalsMail extends Mailable +{ + use Queueable, SerializesModels; + + /** @var string Email address of the user */ + public $email; + /** @var string IP address of user (if known) */ + public $ipAddress; + + /** @var Collection A collection of journals */ + public $journals; + + /** + * ConfirmEmailChangeMail constructor. + * + * @param string $email + * @param string $ipAddress + * @param Collection $journals + */ + public function __construct(string $email, string $ipAddress, Collection $journals) + { + $this->email = $email; + $this->ipAddress = $ipAddress; + $this->journals = $journals; + } + + /** + * Build the message. + * + * @return $this + */ + public function build(): self + { + return $this->view('emails.report-new-journals-html')->text('emails.report-new-journals-text') + ->subject('Firefly III has created new transactions'); + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 9e0a82dcc5..ec5891f6f3 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -26,6 +26,7 @@ use Exception; use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; +use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Events\UpdatedTransactionJournal; @@ -70,6 +71,9 @@ class EventServiceProvider extends ServiceProvider RequestedVersionCheckStatus::class => [ 'FireflyIII\Handlers\Events\VersionCheckEventHandler@checkForUpdates', ], + RequestedReportOnJournals::class => [ + 'FireflyIII\Handlers\Events\AutomationHandler@reportJournals', + ], // is a User related event. RequestedNewPassword::class => [ diff --git a/resources/views/emails/report-new-journals-html.twig b/resources/views/emails/report-new-journals-html.twig new file mode 100644 index 0000000000..c25899b2d7 --- /dev/null +++ b/resources/views/emails/report-new-journals-html.twig @@ -0,0 +1,32 @@ +{% include 'emails.header-html' %} +

      + {% if journals.count == 1 %} + Firefly III has created a transaction for you. + {% endif %} + {% if journals.count > 1 %} + Firefly III has created {{ journals.count }} transactions for you. + {% endif %} +

      + + +{% if journals.count == 1 %} +

      + You can find in in your Firefly III installation: + {% for journal in journals %} + {{ journal.description }} + {% endfor %} +

      +{% endif %} + +{% if journals.count > 1 %} +

      + You can find them in your Firefly III installation: +

      + +{% endif %} + +{% include 'emails.footer-html' %} diff --git a/resources/views/emails/report-new-journals-text.twig b/resources/views/emails/report-new-journals-text.twig new file mode 100644 index 0000000000..780e62133a --- /dev/null +++ b/resources/views/emails/report-new-journals-text.twig @@ -0,0 +1,25 @@ +{% include 'emails.header-text' %} +{% if journals.count == 1 %} +Firefly III has created a transaction for you. + +{% endif %} +{% if journals.count > 1 %} +Firefly III has created {{ journals.count }} transactions for you. +{% endif %} +{% if journals.count == 1 %} +You can find in in your Firefly III installation: + +{% for journal in journals %} +{{ journal.description }}: {{ route('transactions.show', journal.id) }} +{% endfor %} +{% endif %} + +{% if journals.count > 1 %} +You can find them in your Firefly III installation: + +{% for journal in journals %} +- {{ journal.description }}: {{ route('transactions.show', journal.id) }} +{% endfor %} +{% endif %} + +{% include 'emails.footer-text' %} From 5d01955133eb44693479f8b9d6b082b98e241dc6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 26 Jun 2018 18:49:33 +0200 Subject: [PATCH 067/134] Various extensions to recurring transactions. --- app/Console/Kernel.php | 10 ----- .../Recurring/CreateController.php | 14 ++++++- .../Controllers/Recurring/IndexController.php | 6 ++- app/Jobs/CreateRecurringTransactions.php | 2 +- app/Models/RecurrenceRepetition.php | 13 +++++- .../Recurring/RecurringRepository.php | 41 +++++++++++++++++++ .../2018_06_08_200526_changes_for_v475.php | 1 + public/js/ff/recurring/create.js | 1 + resources/lang/en_US/firefly.php | 6 +++ resources/lang/en_US/form.php | 1 + resources/views/partials/control-bar.twig | 9 ++++ resources/views/recurring/create.twig | 1 + 12 files changed, 88 insertions(+), 17 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index f9fba1df4b..a9d5b137c3 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -64,15 +64,5 @@ class Kernel extends ConsoleKernel { // create recurring transactions. $schedule->job(new CreateRecurringTransactions(new Carbon))->daily(); - - // send test email. - $schedule->call( - function () { - $ipAddress = '127.0.0.1'; - /** @var User $user */ - $user = User::find(1); - event(new AdminRequestedTestMessage($user, $ipAddress)); - } - )->daily(); } } diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php index 3a75c998fc..817f6f73dd 100644 --- a/app/Http/Controllers/Recurring/CreateController.php +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Http\Controllers\Recurring; use Carbon\Carbon; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\RecurrenceFormRequest; +use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use Illuminate\Http\Request; @@ -89,10 +90,17 @@ class CreateController extends Controller 'until_date' => trans('firefly.repeat_until_date'), 'times' => trans('firefly.repeat_times'), ]; + // what to do in the weekend? + $weekendResponses = [ + RecurrenceRepetition::WEEKEND_DO_NOTHING => trans('firefly.do_nothing'), + RecurrenceRepetition::WEEKEND_SKIP_CREATION => trans('firefly.skip_transaction'), + RecurrenceRepetition::WEEKEND_TO_FRIDAY => trans('firefly.jump_to_friday'), + RecurrenceRepetition::WEEKEND_TO_MONDAY => trans('firefly.jump_to_monday'), + ]; // flash some data: $hasOldInput = null !== $request->old('_token'); - $preFilled = [ + $preFilled = [ 'first_date' => $tomorrow->format('Y-m-d'), 'transaction_type' => $hasOldInput ? $request->old('transaction_type') : 'withdrawal', 'active' => $hasOldInput ? (bool)$request->old('active') : true, @@ -100,7 +108,9 @@ class CreateController extends Controller ]; $request->session()->flash('preFilled', $preFilled); - return view('recurring.create', compact('tomorrow', 'oldRepetitionType', 'preFilled', 'repetitionEnds', 'defaultCurrency', 'budgets')); + return view( + 'recurring.create', compact('tomorrow', 'oldRepetitionType', 'weekendResponses', 'preFilled', 'repetitionEnds', 'defaultCurrency', 'budgets') + ); } /** diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index b7e2884a58..69f5847d95 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -110,6 +110,7 @@ class IndexController extends Controller $repetition->repetition_type = $repetitionType; $repetition->repetition_moment = $repetitionMoment; $repetition->repetition_skip = (int)$request->get('skip'); + $repetition->weekend = (int)$request->get('weekend'); $actualEnd = clone $end; switch ($endsAt) { @@ -222,8 +223,9 @@ class IndexController extends Controller 'daily' => ['label' => trans('firefly.recurring_daily'), 'selected' => 0 === strpos($preSelected, 'daily')], $weekly => ['label' => trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), 'selected' => 0 === strpos($preSelected, 'weekly')], $monthly => ['label' => trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), 'selected' => 0 === strpos($preSelected, 'monthly')], - $ndom => ['label' => trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]),'selected' => 0 === strpos($preSelected, 'ndom')], - $yearly => ['label' => trans('firefly.recurring_yearly', ['date' => $yearlyDate]),'selected' => 0 === strpos($preSelected, 'yearly')], + $ndom => ['label' => trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), + 'selected' => 0 === strpos($preSelected, 'ndom')], + $yearly => ['label' => trans('firefly.recurring_yearly', ['date' => $yearlyDate]), 'selected' => 0 === strpos($preSelected, 'yearly')], ]; } diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 45cfe72936..5c378811d4 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -89,7 +89,7 @@ class CreateRecurringTransactions implements ShouldQueue Log::debug('Now running report thing.'); // will now send email to users. foreach ($result as $userId => $journals) { - // random bunch to make mail. + //// random bunch to make mail. $journals = TransactionJournal::where('user_id', $userId)->inRandomOrder()->take(1)->get(); event(new RequestedReportOnJournals($userId, $journals)); } diff --git a/app/Models/RecurrenceRepetition.php b/app/Models/RecurrenceRepetition.php index 48f3eb93ff..6164e38b99 100644 --- a/app/Models/RecurrenceRepetition.php +++ b/app/Models/RecurrenceRepetition.php @@ -34,6 +34,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string $repetition_type * @property string $repetition_moment * @property int $repetition_skip + * @property int $weekend * @property \Carbon\Carbon $created_at * @property \Carbon\Carbon $deleted_at * @property \Carbon\Carbon $updated_at @@ -41,10 +42,18 @@ use Illuminate\Database\Eloquent\SoftDeletes; */ class RecurrenceRepetition extends Model { + /** @var int */ + public const WEEKEND_DO_NOTHING = 1; + /** @var int */ + public const WEEKEND_SKIP_CREATION = 2; + /** @var int */ + public const WEEKEND_TO_FRIDAY = 3; + /** @var int */ + public const WEEKEND_TO_MONDAY = 4; use SoftDeletes; /** @var array */ protected $casts - = [ + = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', @@ -52,7 +61,7 @@ class RecurrenceRepetition extends Model 'repetition_moment' => 'string', 'repetition_skip' => 'int', ]; - protected $fillable = ['recurrence_id', 'repetition_type', 'repetition_moment', 'repetition_skip']; + protected $fillable = ['recurrence_id', 'weekend', 'repetition_type', 'repetition_moment', 'repetition_skip']; /** @var string */ protected $table = 'recurrences_repetitions'; diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 18418c9034..024f3fb14a 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -262,6 +262,8 @@ class RecurringRepository implements RecurringRepositoryInterface } break; } + // filter out all the weekend days: + $return = $this->filterWeekends($repetition, $return); return $return; } @@ -492,4 +494,43 @@ class RecurringRepository implements RecurringRepositoryInterface return $service->update($recurrence, $data); } + + /** + * Filters out all weekend entries, if necessary. + * + * @param RecurrenceRepetition $repetition + * @param array $dates + * + * @return array + */ + protected function filterWeekends(RecurrenceRepetition $repetition, array $dates): array + { + if ($repetition->weekend === RecurrenceRepetition::WEEKEND_DO_NOTHING) { + return $dates; + } + $return = []; + /** @var Carbon $date */ + foreach ($dates as $date) { + $isWeekend = $date->isWeekend(); + + // set back to Friday? + if ($isWeekend && $repetition->weekend === RecurrenceRepetition::WEEKEND_TO_FRIDAY) { + $clone = clone $date; + $clone->subDays(7 - $date->dayOfWeekIso); + $return[] = $clone; + } + + // postpone to Monday? + if ($isWeekend && $repetition->weekend === RecurrenceRepetition::WEEKEND_TO_MONDAY) { + $clone = clone $date; + $clone->addDays(8 - $date->dayOfWeekIso); + $return[] = $clone; + } + // otherwise, ignore the date! + } + + // filter unique dates? + + return $return; + } } \ No newline at end of file diff --git a/database/migrations/2018_06_08_200526_changes_for_v475.php b/database/migrations/2018_06_08_200526_changes_for_v475.php index db5cfbebe7..366ea260f9 100644 --- a/database/migrations/2018_06_08_200526_changes_for_v475.php +++ b/database/migrations/2018_06_08_200526_changes_for_v475.php @@ -92,6 +92,7 @@ class ChangesForV475 extends Migration $table->string('repetition_type', 50); $table->string('repetition_moment', 50); $table->smallInteger('repetition_skip', false, true); + $table->smallInteger('weekend', false, true); $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); } diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index d8cbe8912f..7e1deebfff 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -67,6 +67,7 @@ function showRepCalendar() { newEventsUri += '&end_date=' + $('#ffInput_repeat_until').val(); newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); newEventsUri += '&first_date=' + $('#ffInput_first_date').val(); + newEventsUri += '&weekend=' + $('#ffInput_weekend').val(); // remove all event sources from calendar: calendar.fullCalendar('removeEventSources'); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 72856519f1..cbe8f38983 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1261,5 +1261,11 @@ return [ 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index a54447545a..773f713fa0 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -236,5 +236,6 @@ return [ 'repetition_end' => 'Repetition ends', 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', + 'weekend' => 'Weekend', ]; diff --git a/resources/views/partials/control-bar.twig b/resources/views/partials/control-bar.twig index aa96baa72b..e1d19a1beb 100644 --- a/resources/views/partials/control-bar.twig +++ b/resources/views/partials/control-bar.twig @@ -105,6 +105,15 @@
    +
  • + + + + + +
  • diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index d9f4e3572f..c165ad7dc7 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -21,6 +21,7 @@ {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} {{ ExpandedForm.number('skip', 0) }} {{ ExpandedForm.select('repetition_end', repetitionEnds) }} + {{ ExpandedForm.select('weekend', weekendResponses, null, {helpText: trans('firefly.help_weekend')}) }} {{ ExpandedForm.date('repeat_until',null) }} {{ ExpandedForm.number('repetitions',null) }} From 49de4f2200ac436e13588069eafa6db459b95744 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 26 Jun 2018 19:26:10 +0200 Subject: [PATCH 068/134] Expand report email. --- app/Jobs/CreateRecurringTransactions.php | 2 +- app/Models/Transaction.php | 1 + .../Twig/Extension/TransactionJournal.php | 52 +++++++++++++++---- app/Support/Twig/Journal.php | 1 + .../emails/report-new-journals-html.twig | 8 +-- .../emails/report-new-journals-text.twig | 4 +- 6 files changed, 53 insertions(+), 15 deletions(-) diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 5c378811d4..65a42a6ea9 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -90,7 +90,7 @@ class CreateRecurringTransactions implements ShouldQueue // will now send email to users. foreach ($result as $userId => $journals) { //// random bunch to make mail. - $journals = TransactionJournal::where('user_id', $userId)->inRandomOrder()->take(1)->get(); + //$journals = TransactionJournal::where('user_id', $userId)->inRandomOrder()->take(1)->get(); event(new RequestedReportOnJournals($userId, $journals)); } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 2a9ffa0a69..4742467e29 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -73,6 +73,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool $is_split * @property int $attachmentCount * @property int $transaction_currency_id + * @property int $foreign_currency_id */ class Transaction extends Model { diff --git a/app/Support/Twig/Extension/TransactionJournal.php b/app/Support/Twig/Extension/TransactionJournal.php index c9b56a3d8b..66f05708d3 100644 --- a/app/Support/Twig/Extension/TransactionJournal.php +++ b/app/Support/Twig/Extension/TransactionJournal.php @@ -96,10 +96,51 @@ class TransactionJournal extends Twig_Extension * @return string */ public function totalAmount(JournalModel $journal): string + { + $type = $journal->transactionType->type; + $totals = $this->getTotalAmount($journal); + $array = []; + foreach ($totals as $total) { + if (TransactionType::WITHDRAWAL === $type) { + $total['amount'] = bcmul($total['amount'], '-1'); + } + $array[] = app('amount')->formatAnything($total['currency'], $total['amount']); + } + + return implode(' / ', $array); + } + + /** + * @param JournalModel $journal + * + * @return string + */ + public function totalAmountPlain(JournalModel $journal): string + { + $type = $journal->transactionType->type; + $totals = $this->getTotalAmount($journal); + $array = []; + + foreach ($totals as $total) { + if (TransactionType::WITHDRAWAL === $type) { + $total['amount'] = bcmul($total['amount'], '-1'); + } + $array[] = app('amount')->formatAnything($total['currency'], $total['amount'], false); + } + + return implode(' / ', $array); + } + + /** + * @param JournalModel $journal + * + * @return string + */ + private function getTotalAmount(JournalModel $journal): array { $transactions = $journal->transactions()->where('amount', '>', 0)->get(); $totals = []; - $type = $journal->transactionType->type; + /** @var TransactionModel $transaction */ foreach ($transactions as $transaction) { $currencyId = $transaction->transaction_currency_id; @@ -128,14 +169,7 @@ class TransactionJournal extends Twig_Extension ); } } - $array = []; - foreach ($totals as $total) { - if (TransactionType::WITHDRAWAL === $type) { - $total['amount'] = bcmul($total['amount'], '-1'); - } - $array[] = app('amount')->formatAnything($total['currency'], $total['amount']); - } - return implode(' / ', $array); + return $totals; } } diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php index d8613a6e4f..c6502a8f72 100644 --- a/app/Support/Twig/Journal.php +++ b/app/Support/Twig/Journal.php @@ -79,6 +79,7 @@ class Journal extends Twig_Extension { $filters = [ new Twig_SimpleFilter('journalTotalAmount', [TransactionJournalExtension::class, 'totalAmount'], ['is_safe' => ['html']]), + new Twig_SimpleFilter('journalTotalAmountPlain', [TransactionJournalExtension::class, 'totalAmountPlain'], ['is_safe' => ['html']]), ]; return $filters; diff --git a/resources/views/emails/report-new-journals-html.twig b/resources/views/emails/report-new-journals-html.twig index c25899b2d7..a3022d0775 100644 --- a/resources/views/emails/report-new-journals-html.twig +++ b/resources/views/emails/report-new-journals-html.twig @@ -11,9 +11,9 @@ {% if journals.count == 1 %}

    - You can find in in your Firefly III installation: + You can find it in your Firefly III installation:
    {% for journal in journals %} - {{ journal.description }} + {{ journal.description }} ({{ journal|journalTotalAmount }}) {% endfor %}

    {% endif %} @@ -24,7 +24,9 @@

    {% endif %} diff --git a/resources/views/emails/report-new-journals-text.twig b/resources/views/emails/report-new-journals-text.twig index 780e62133a..81faa232d5 100644 --- a/resources/views/emails/report-new-journals-text.twig +++ b/resources/views/emails/report-new-journals-text.twig @@ -10,7 +10,7 @@ Firefly III has created {{ journals.count }} transactions for you. You can find in in your Firefly III installation: {% for journal in journals %} -{{ journal.description }}: {{ route('transactions.show', journal.id) }} +{{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }}) {% endfor %} {% endif %} @@ -18,7 +18,7 @@ You can find in in your Firefly III installation: You can find them in your Firefly III installation: {% for journal in journals %} -- {{ journal.description }}: {{ route('transactions.show', journal.id) }} +- {{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }}) {% endfor %} {% endif %} From 7ba11a57a8d7dbeb83f4b21241101c8d17fc068e Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 26 Jun 2018 21:17:50 +0200 Subject: [PATCH 069/134] make sure recurrence can skip weekends. --- app/Jobs/CreateRecurringTransactions.php | 24 ++++++---- app/Models/Recurrence.php | 1 + .../Recurring/RecurringRepository.php | 47 +++++++++++++++++-- .../RecurringRepositoryInterface.php | 11 +++++ app/Transformers/RecurrenceTransformer.php | 1 + public/js/ff/recurring/edit.js | 1 + resources/views/recurring/create.twig | 2 +- 7 files changed, 73 insertions(+), 14 deletions(-) diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 65a42a6ea9..b358e9bc39 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -7,10 +7,8 @@ use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; -use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; -use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -32,8 +30,6 @@ class CreateRecurringTransactions implements ShouldQueue private $journalRepository; /** @var RecurringRepositoryInterface */ private $repository; - /** @var UserRepositoryInterface */ - private $userRepository; /** * Create a new job instance. @@ -46,14 +42,11 @@ class CreateRecurringTransactions implements ShouldQueue $this->date = $date; $this->repository = app(RecurringRepositoryInterface::class); $this->journalRepository = app(JournalRepositoryInterface::class); - $this->userRepository = app(UserRepositoryInterface::class); } /** * Execute the job. * - * TODO check number of repetitions. - * * @throws \FireflyIII\Exceptions\FireflyException */ public function handle(): void @@ -198,6 +191,13 @@ class CreateRecurringTransactions implements ShouldQueue } Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); + // count created journals on THIS day. + $created = $this->repository->getJournals($recurrence, $date, $date); + if ($created->count() > 0) { + Log::info(sprintf('Already created %d journal(s) for date %s', $created->count(), $date->format('Y-m-d'))); + continue; + } + // create transaction array and send to factory. $array = [ 'type' => $recurrence->transactionType->type, @@ -212,7 +212,7 @@ class CreateRecurringTransactions implements ShouldQueue 'piggy_bank_name' => null, 'bill_id' => null, 'bill_name' => null, - 'recurrence_id' => $recurrence->id, + 'recurrence_id' => (int)$recurrence->id, // transaction data: 'transactions' => $this->getTransactionData($recurrence), @@ -315,6 +315,14 @@ class CreateRecurringTransactions implements ShouldQueue return false; } + // has repeated X times. + $journals = $this->repository->getJournals($recurrence, null, null); + if ($recurrence->repetitions !== 0 && $journals->count() >= $recurrence->repetitions) { + Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions)); + + return false; + } + // is no longer running if ($this->repeatUntilHasPassed($recurrence)) { Log::info( diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php index f168f34afb..bc60c3bad2 100644 --- a/app/Models/Recurrence.php +++ b/app/Models/Recurrence.php @@ -72,6 +72,7 @@ class Recurrence extends Model 'updated_at' => 'datetime', 'deleted_at' => 'datetime', 'title' => 'string', + 'id' => 'int', 'description' => 'string', 'first_date' => 'date', 'repeat_until' => 'date', diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 024f3fb14a..0ae942e8f0 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -33,6 +33,7 @@ use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService; use FireflyIII\Services\Internal\Update\RecurrenceUpdateService; use FireflyIII\User; @@ -129,6 +130,34 @@ class RecurringRepository implements RecurringRepositoryInterface return $return === '' ? null : $return; } + /** + * Returns the journals created for this recurrence, possibly limited by time. + * + * @param Recurrence $recurrence + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return Collection + */ + public function getJournals(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): Collection + { + $query = TransactionJournal + ::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transaction_journals.user_id', $recurrence->user_id) + ->whereNull('transaction_journals.deleted_at') + ->where('journal_meta.name', 'recurrence_id') + ->where('journal_meta.data', '"' . $recurrence->id . '"'); + if (null !== $start) { + $query->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')); + } + if (null !== $end) { + $query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')); + } + $result = $query->get(['transaction_journals.*']); + + return $result; + } + /** * Get the notes. * @@ -398,6 +427,8 @@ class RecurringRepository implements RecurringRepositoryInterface } break; } + // filter out all the weekend days: + $return = $this->filterWeekends($repetition, $return); return $return; } @@ -512,12 +543,16 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var Carbon $date */ foreach ($dates as $date) { $isWeekend = $date->isWeekend(); + if (!$isWeekend) { + $return[] = clone $date; + continue; + } - // set back to Friday? + // is weekend and must set back to Friday? if ($isWeekend && $repetition->weekend === RecurrenceRepetition::WEEKEND_TO_FRIDAY) { $clone = clone $date; - $clone->subDays(7 - $date->dayOfWeekIso); - $return[] = $clone; + $clone->addDays(5 - $date->dayOfWeekIso); + $return[] = clone $clone; } // postpone to Monday? @@ -526,10 +561,12 @@ class RecurringRepository implements RecurringRepositoryInterface $clone->addDays(8 - $date->dayOfWeekIso); $return[] = $clone; } - // otherwise, ignore the date! } - // filter unique dates? + // filter unique dates + $collection = new Collection($return); + $filtered = $collection->unique(); + $return = $filtered->toArray(); return $return; } diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index 6f6963e1b6..98751b3f39 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -78,6 +78,17 @@ interface RecurringRepositoryInterface */ public function getCategory(RecurrenceTransaction $recurrenceTransaction): ?string; + /** + * Returns the journals created for this recurrence, possibly limited by time. + * + * @param Recurrence $recurrence + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return Collection + */ + public function getJournals(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): Collection; + /** * Get the notes. * diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index 8a0bba3a1c..0c23487c08 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -137,6 +137,7 @@ class RecurrenceTransformer extends TransformerAbstract 'repetition_type' => $repetition->repetition_type, 'repetition_moment' => $repetition->repetition_moment, 'repetition_skip' => (int)$repetition->repetition_skip, + 'weekend' => (int)$repetition->weekend, 'description' => $this->repository->repetitionDescription($repetition), 'occurrences' => [], ]; diff --git a/public/js/ff/recurring/edit.js b/public/js/ff/recurring/edit.js index a2aad13572..f499280b57 100644 --- a/public/js/ff/recurring/edit.js +++ b/public/js/ff/recurring/edit.js @@ -67,6 +67,7 @@ function showRepCalendar() { newEventsUri += '&end_date=' + $('#ffInput_repeat_until').val(); newEventsUri += '&reps=' + $('#ffInput_repetitions').val(); newEventsUri += '&first_date=' + $('#ffInput_first_date').val(); + newEventsUri += '&weekend=' + $('#ffInput_weekend').val(); // remove all event sources from calendar: calendar.fullCalendar('removeEventSources'); diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index c165ad7dc7..7af6a7082f 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -20,8 +20,8 @@ {{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }} {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} {{ ExpandedForm.number('skip', 0) }} - {{ ExpandedForm.select('repetition_end', repetitionEnds) }} {{ ExpandedForm.select('weekend', weekendResponses, null, {helpText: trans('firefly.help_weekend')}) }} + {{ ExpandedForm.select('repetition_end', repetitionEnds) }} {{ ExpandedForm.date('repeat_until',null) }} {{ ExpandedForm.number('repetitions',null) }} From 20aa6e429b0dd2b5cb2ceb809732302d73188439 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 27 Jun 2018 05:37:56 +0200 Subject: [PATCH 070/134] Expand support for weekend and add some logging. --- app/Http/Requests/RecurrenceFormRequest.php | 9 +++++---- app/Repositories/Recurring/RecurringRepository.php | 7 ++++++- .../Internal/Support/RecurringTransactionTrait.php | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index ce6a2afe22..c05c6ea0a8 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -79,14 +79,15 @@ class RecurrenceFormRequest extends Request ], 'meta' => [ // tags and piggy bank ID. - 'tags' => '' !== $this->string('tags') ? explode(',', $this->string('tags')): [], + 'tags' => '' !== $this->string('tags') ? explode(',', $this->string('tags')) : [], 'piggy_bank_id' => $this->integer('piggy_bank_id'), ], 'repetitions' => [ [ - 'type' => $repetitionData['type'], - 'moment' => $repetitionData['moment'], - 'skip' => $this->integer('skip'), + 'type' => $repetitionData['type'], + 'moment' => $repetitionData['moment'], + 'skip' => $this->integer('skip'), + 'weekend' => $this->integer('weekend'), ], ], diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 0ae942e8f0..7d8e4b7b48 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -195,14 +195,18 @@ class RecurringRepository implements RecurringRepositoryInterface $skipMod = $repetition->repetition_skip + 1; $attempts = 0; Log::debug(sprintf('Calculating occurrences for rep type "%s"', $repetition->repetition_type)); + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); switch ($repetition->repetition_type) { default: throw new FireflyException( sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type) ); case 'daily': + Log::debug('Rep is daily. Start of loop.'); while ($mutator <= $end) { + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); if ($attempts % $skipMod === 0) { + Log::debug(sprintf('Attempts modulo skipmod is zero, include %s', $mutator->format('Y-m-d'))); $return[] = clone $mutator; } $mutator->addDay(); @@ -210,9 +214,10 @@ class RecurringRepository implements RecurringRepositoryInterface } break; case 'weekly': + Log::debug('Rep is weekly.'); // monday = 1 // sunday = 7 - $mutator->addDay(); // always assume today has passed. + $mutator->addDay(); // always assume today has passed. TODO why? $dayOfWeek = (int)$repetition->repetition_moment; if ($mutator->dayOfWeekIso > $dayOfWeek) { // day has already passed this week, add one week: diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index 6495c35577..4de55141ce 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -56,6 +56,7 @@ trait RecurringTransactionTrait 'repetition_type' => $array['type'], 'repetition_moment' => $array['moment'], 'repetition_skip' => $array['skip'], + 'weekend' => $array['weekend'] ?? 1, ] ); From 7b46339a5d9c0e83be8e3600fd209eb89c2b85ae Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 27 Jun 2018 05:40:14 +0200 Subject: [PATCH 071/134] Add some logging to weekly recurring. --- app/Repositories/Recurring/RecurringRepository.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 7d8e4b7b48..6e6c628ef2 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -218,10 +218,13 @@ class RecurringRepository implements RecurringRepositoryInterface // monday = 1 // sunday = 7 $mutator->addDay(); // always assume today has passed. TODO why? + Log::debug(sprintf('Add a day (not sure why), so mutator is now: %s', $mutator->format('Y-m-d'))); $dayOfWeek = (int)$repetition->repetition_moment; + Log::debug(sprintf('DoW in repetition is %d, in mutator is %d', $dayOfWeek, $mutator->dayOfWeekIso)); if ($mutator->dayOfWeekIso > $dayOfWeek) { // day has already passed this week, add one week: $mutator->addWeek(); + Log::debug(sprintf('Jump to next week, so mutator is now: %s', $mutator->format('Y-m-d'))); } // today is wednesday (3), expected is friday (5): add two days. // today is friday (5), expected is monday (1), subtract four days. From a8e666db342a93c8a17c65b0754dcf083acaeba1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 27 Jun 2018 18:30:53 +0200 Subject: [PATCH 072/134] Expand view to include weekend responses. --- .../Controllers/Recurring/EditController.php | 18 +++++++++++---- app/Jobs/CreateRecurringTransactions.php | 2 -- .../Recurring/RecurringRepository.php | 6 +++-- resources/lang/en_US/firefly.php | 3 +++ resources/views/recurring/edit.twig | 1 + resources/views/recurring/index.twig | 22 +++++++++++++------ 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php index 1107e93505..e78b8a820f 100644 --- a/app/Http/Controllers/Recurring/EditController.php +++ b/app/Http/Controllers/Recurring/EditController.php @@ -88,7 +88,6 @@ class EditController extends Controller // todo handle old repetition type as well. - /** @var RecurrenceRepetition $repetition */ $repetition = $recurrence->recurrenceRepetitions()->first(); $currentRepetitionType = $repetition->repetition_type; @@ -117,16 +116,27 @@ class EditController extends Controller $repetitionEnd = 'times'; } + // what to do in the weekend? + $weekendResponses = [ + RecurrenceRepetition::WEEKEND_DO_NOTHING => trans('firefly.do_nothing'), + RecurrenceRepetition::WEEKEND_SKIP_CREATION => trans('firefly.skip_transaction'), + RecurrenceRepetition::WEEKEND_TO_FRIDAY => trans('firefly.jump_to_friday'), + RecurrenceRepetition::WEEKEND_TO_MONDAY => trans('firefly.jump_to_monday'), + ]; + // code to handle active-checkboxes $hasOldInput = null !== $request->old('_token'); - // $hasOldInput = false; - $preFilled = [ + // $hasOldInput = false; + $preFilled = [ 'transaction_type' => strtolower($recurrence->transactionType->type), 'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active, 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules, ]; - return view('recurring.edit', compact('recurrence', 'array', 'budgets', 'preFilled', 'currentRepetitionType', 'repetitionEnd', 'repetitionEnds')); + return view( + 'recurring.edit', + compact('recurrence', 'array', 'weekendResponses', 'budgets', 'preFilled', 'currentRepetitionType', 'repetitionEnd', 'repetitionEnds') + ); } /** diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index b358e9bc39..37319dc958 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -82,8 +82,6 @@ class CreateRecurringTransactions implements ShouldQueue Log::debug('Now running report thing.'); // will now send email to users. foreach ($result as $userId => $journals) { - //// random bunch to make mail. - //$journals = TransactionJournal::where('user_id', $userId)->inRandomOrder()->take(1)->get(); event(new RequestedReportOnJournals($userId, $journals)); } diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 6e6c628ef2..8776c8d2de 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -217,8 +217,6 @@ class RecurringRepository implements RecurringRepositoryInterface Log::debug('Rep is weekly.'); // monday = 1 // sunday = 7 - $mutator->addDay(); // always assume today has passed. TODO why? - Log::debug(sprintf('Add a day (not sure why), so mutator is now: %s', $mutator->format('Y-m-d'))); $dayOfWeek = (int)$repetition->repetition_moment; Log::debug(sprintf('DoW in repetition is %d, in mutator is %d', $dayOfWeek, $mutator->dayOfWeekIso)); if ($mutator->dayOfWeekIso > $dayOfWeek) { @@ -228,14 +226,18 @@ class RecurringRepository implements RecurringRepositoryInterface } // today is wednesday (3), expected is friday (5): add two days. // today is friday (5), expected is monday (1), subtract four days. + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); $dayDifference = $dayOfWeek - $mutator->dayOfWeekIso; $mutator->addDays($dayDifference); + Log::debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d'))); while ($mutator <= $end) { if ($attempts % $skipMod === 0 && $start->lte($mutator) && $end->gte($mutator)) { + Log::debug('Date is in range of start+end, add to set.'); $return[] = clone $mutator; } $attempts++; $mutator->addWeek(); + Log::debug(sprintf('Mutator is now (end of loop): %s', $mutator->format('Y-m-d'))); } break; case 'monthly': diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index cbe8f38983..2fae93b868 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1267,5 +1267,8 @@ return [ 'skip_transaction' => 'Skip the occurence', 'jump_to_friday' => 'Create the transaction on the previous Friday instead', 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/views/recurring/edit.twig b/resources/views/recurring/edit.twig index 06267cdafd..cba7f8e113 100644 --- a/resources/views/recurring/edit.twig +++ b/resources/views/recurring/edit.twig @@ -21,6 +21,7 @@ {{ ExpandedForm.date('first_date',array.first_date, {helpText: trans('firefly.help_first_date_no_past')}) }} {{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }} {{ ExpandedForm.number('skip', array.recurrence_repetitions[0].repetition_skip) }} + {{ ExpandedForm.select('weekend', weekendResponses, array.recurrence_repetitions[0].weekend, {helpText: trans('firefly.help_weekend')}) }} {{ ExpandedForm.select('repetition_end', repetitionEnds, repetitionEnd) }} {{ ExpandedForm.date('repeat_until',array.repeat_until) }} {{ ExpandedForm.number('repetitions', array.repetitions) }} diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig index 7b27187baa..a4f39bd1d7 100644 --- a/resources/views/recurring/index.twig +++ b/resources/views/recurring/index.twig @@ -87,26 +87,34 @@ {% for rep in rt.recurrence_repetitions %}
  • {{ rep.description }} {% if rep.repetition_skip == 1 %} - ({{ trans('firefly.recurring_skips_one')|lower }}) + ({{ trans('firefly.recurring_skips_one')|lower }}). {% endif %} {% if rep.repetition_skip > 1 %} - ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}). {% endif %} - - + {% if rep.weekend == 3 %} + {{ 'will_jump_friday'|_ }} + {% endif %} + {% if rep.weekend == 4 %} + {{ 'will_jump_monday'|_ }} + {% endif %} + {% if rep.weekend == 2 %} + {{ 'except_weekends'|_ }} + {% endif %}
  • {% endfor %}

    {% if null == rt.repeat_until and rt.repetitions == 0 %} - {{ 'recurring_repeats_forever'|_ }} + {{ 'recurring_repeats_forever'|_ }}. {% endif %} {% if null != rt.repeat_until and rt.repetitions == 0 %} - {{ trans('firefly.recurring_repeats_until', {date: rt.repeat_until.formatLocalized(monthAndDayFormat)}) }} + {{ trans('firefly.recurring_repeats_until', {date: rt.repeat_until.formatLocalized(monthAndDayFormat)}) }}. {% endif %} {% if null == rt.repeat_until and rt.repetitions != 0 %} - {{ trans('firefly.recurring_repeats_x_times', {count: rt.repetitions}) }} + {{ trans('firefly.recurring_repeats_x_times', {count: rt.repetitions}) }}. {% endif %} +

    From d378e7e897e5821420ae49a74d3bba6d4fc8782b Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 27 Jun 2018 21:37:16 +0200 Subject: [PATCH 073/134] New translations [skip ci] --- resources/lang/de_DE/config.php | 16 +- resources/lang/de_DE/demo.php | 4 +- resources/lang/de_DE/firefly.php | 119 +++--- resources/lang/de_DE/form.php | 192 ++++----- resources/lang/de_DE/import.php | 5 +- resources/lang/de_DE/list.php | 10 +- resources/lang/de_DE/validation.php | 4 +- resources/lang/es_ES/firefly.php | 13 +- resources/lang/es_ES/form.php | 172 ++++---- resources/lang/es_ES/import.php | 5 +- resources/lang/es_ES/validation.php | 2 + resources/lang/fr_FR/firefly.php | 13 +- resources/lang/fr_FR/form.php | 172 ++++---- resources/lang/fr_FR/import.php | 5 +- resources/lang/fr_FR/validation.php | 2 + resources/lang/id_ID/firefly.php | 13 +- resources/lang/id_ID/form.php | 172 ++++---- resources/lang/id_ID/import.php | 5 +- resources/lang/id_ID/validation.php | 2 + resources/lang/it_IT/breadcrumbs.php | 4 +- resources/lang/it_IT/config.php | 16 +- resources/lang/it_IT/demo.php | 8 +- resources/lang/it_IT/firefly.php | 595 ++++++++++++++------------- resources/lang/it_IT/form.php | 226 +++++----- resources/lang/it_IT/import.php | 55 +-- resources/lang/it_IT/intro.php | 16 +- resources/lang/it_IT/list.php | 60 +-- resources/lang/it_IT/passwords.php | 4 +- resources/lang/it_IT/validation.php | 26 +- resources/lang/nl_NL/config.php | 2 +- resources/lang/nl_NL/firefly.php | 13 +- resources/lang/nl_NL/form.php | 172 ++++---- resources/lang/nl_NL/import.php | 5 +- resources/lang/nl_NL/validation.php | 2 + resources/lang/pl_PL/config.php | 14 +- resources/lang/pl_PL/firefly.php | 63 +-- resources/lang/pl_PL/form.php | 198 ++++----- resources/lang/pl_PL/import.php | 81 ++-- resources/lang/pl_PL/list.php | 6 +- resources/lang/pl_PL/validation.php | 2 + resources/lang/pt_BR/firefly.php | 13 +- resources/lang/pt_BR/form.php | 172 ++++---- resources/lang/pt_BR/import.php | 5 +- resources/lang/pt_BR/validation.php | 2 + resources/lang/ru_RU/config.php | 16 +- resources/lang/ru_RU/demo.php | 4 +- resources/lang/ru_RU/firefly.php | 113 ++--- resources/lang/ru_RU/form.php | 192 ++++----- resources/lang/ru_RU/import.php | 23 +- resources/lang/ru_RU/intro.php | 2 +- resources/lang/ru_RU/list.php | 18 +- resources/lang/ru_RU/validation.php | 8 +- resources/lang/tr_TR/firefly.php | 13 +- resources/lang/tr_TR/form.php | 172 ++++---- resources/lang/tr_TR/import.php | 5 +- resources/lang/tr_TR/validation.php | 2 + 56 files changed, 1727 insertions(+), 1527 deletions(-) diff --git a/resources/lang/de_DE/config.php b/resources/lang/de_DE/config.php index 311be92dab..7ddcf49b6e 100644 --- a/resources/lang/de_DE/config.php +++ b/resources/lang/de_DE/config.php @@ -27,7 +27,7 @@ return [ 'locale' => 'de, Deutsch, de_DE, de_DE.utf8, de_DE.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e. %B %Y', - 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_date_day' => '%A, %B %e. %Y', 'month_and_day_no_year' => '%B %e', 'date_time' => '%e %B %Y, @ %T', 'specific_day' => '%e. %B %Y', @@ -41,11 +41,11 @@ return [ 'week_in_year_js' => '[Week]. KW, YYYY', 'year_js' => 'YYYY', 'half_year_js' => 'Q. Quartal YYYY', - 'dow_1' => 'Monday', - 'dow_2' => 'Tuesday', - 'dow_3' => 'Wednesday', - 'dow_4' => 'Thursday', - 'dow_5' => 'Friday', - 'dow_6' => 'Saturday', - 'dow_7' => 'Sunday', + 'dow_1' => 'Montag', + 'dow_2' => 'Dienstag', + 'dow_3' => 'Mittwoch', + 'dow_4' => 'Donnerstag', + 'dow_5' => 'Freitag', + 'dow_6' => 'Samstag', + 'dow_7' => 'Sonntag', ]; diff --git a/resources/lang/de_DE/demo.php b/resources/lang/de_DE/demo.php index 9a3e9f6676..8a65f1adcf 100644 --- a/resources/lang/de_DE/demo.php +++ b/resources/lang/de_DE/demo.php @@ -34,6 +34,6 @@ return [ 'transactions-index' => 'Diese Ausgaben, Einnahmen und Umbuchungen sind nicht besonders einfallsreich. Sie wurden automatisch generiert.', 'piggy-banks-index' => 'Hier wurden bereits drei Sparschweine angelegt. Der Betrag in den Sparschweinen kann über die Plus-/Minus-Buttons angepasst werden. Klicken Sie auf den Namen des Sparschweins um weitere Informationen einzusehen.', 'import-index' => 'Jede CSV-Datei kann in Firefly III importiert werden. Es wird auch der Import von Daten aus Bunq und Spectre unterstützt. Weitere Banken und Finanzaggregatoren werden in Zukunft implementiert. Als Demo-Anwender können Sie jedoch nur einen „Schein”-Anbieter in Aktion erleben. Es werden einige zufällige Transaktionen generiert, um Ihnen zu zeigen, wie der Prozess funktioniert.', - 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', - 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-index' => 'Bitte beachten Sie, dass sich diese Funktion in der aktiven Entwicklung befindet und möglicherweise nicht wie erwartet funktioniert.', + 'recurring-create' => 'Bitte beachten Sie, dass sich diese Funktion in der aktiven Entwicklung befindet und möglicherweise nicht wie erwartet funktioniert.', ]; diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 6cb8150663..7f1bec46e4 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scannen Sie den QR-Code mit einer Anwendung wie Authy oder Google Authenticator auf ihrem Handy und geben Sie den generierten Code ein.', 'pref_two_factor_auth_reset_code' => 'Verifizierungscode zurücksetzen', 'pref_two_factor_auth_disable_2fa' => '2FA deaktivieren', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Einstellungen speichern', 'saved_preferences' => 'Einstellungen gespeichert!', 'preferences_general' => 'Allgemein', @@ -901,7 +902,6 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'balanceEnd' => 'Bilanz zum Ende der Periode', 'splitByAccount' => 'Nach Konto aufteilen', 'coveredWithTags' => 'Mit Schlagwörtern versehen', - 'leftUnbalanced' => 'Unausgeglichen belassen', 'leftInBudget' => 'Verblieben im Kostenrahmen', 'sumOfSums' => 'Summe der Summen', 'noCategory' => '(keine Kategorie)', @@ -1063,7 +1063,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'instance_configuration' => 'Konfiguration', 'firefly_instance_configuration' => 'Konfigurationsoptionen für Firefly III', 'setting_single_user_mode' => 'Einzelnutzermodus', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', + 'setting_single_user_mode_explain' => 'Dies ist eine sehr fortschrittliche Funktion, welche aber sehr nützlich sein kann. Stellen Sie sicher, dass Sie die Dokumentation (❓-Symbol in der oberen rechten Ecke) lesen, bevor Sie fortfahren.', 'store_configuration' => 'Konfiguration speichern', 'single_user_administration' => 'Benutzerverwaltung für :email', 'edit_user' => 'Benutzer :email bearbeiten', @@ -1156,9 +1156,9 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'cannot_convert_split_journal' => 'Eine Splitbuchung konnte nicht umgesetzt werden', // Import page (general strings only) - 'import_index_title' => 'Import transactions into Firefly III', + 'import_index_title' => 'Buchungen in Firefly III importieren', 'import_data' => 'Daten importieren', - 'import_transactions' => 'Import transactions', + 'import_transactions' => 'Buchungen importieren', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Diese Funktion ist nicht verfügbar, wenn Sie Firefly III in einer Sandstorm.io-Umgebung verwenden.', @@ -1210,55 +1210,66 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'no_bills_create_default' => 'Eine Rechnung erstellen', // recurring transactions - 'recurrences' => 'Recurring transactions', - 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', - 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', - 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', - 'no_recurring_create_default' => 'Create a recurring transaction', - 'make_new_recurring' => 'Create a recurring transaction', - 'recurring_daily' => 'Every day', - 'recurring_weekly' => 'Every week on :weekday', - 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', - 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', - 'recurring_yearly' => 'Every year on :date', - 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', - 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', - 'created_transactions' => 'Related transactions', - 'expected_Withdrawals' => 'Expected withdrawals', - 'expected_Deposits' => 'Expected deposits', - 'expected_Transfers' => 'Expected transfers', - 'created_Withdrawals' => 'Created withdrawals', - 'created_Deposits' => 'Created deposits', - 'created_Transfers' => 'Created transfers', - 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurrences' => 'Regelmäßige Buchungen', + 'no_recurring_title_default' => 'Lassen Sie uns eine regelmäßige Buchung erstellen!', + 'no_recurring_intro_default' => 'Sie verfügen noch über keine regelmäßigen Buchungen. Mit diesen können Sie Firefly III dazu einsetzen, automatisch Buchungen für Sie zu erstellen.', + 'no_recurring_imperative_default' => 'Dies ist eine sehr fortschrittliche Funktion, welche aber sehr nützlich sein kann. Stellen Sie sicher, dass Sie die Dokumentation (❓-Symbol in der oberen rechten Ecke) lesen, bevor Sie fortfahren.', + 'no_recurring_create_default' => 'Regelmäßige Buchung erstellen', + 'make_new_recurring' => 'Regelmäßige Buchung erstellen', + 'recurring_daily' => 'Täglich', + 'recurring_weekly' => 'Wöchentlich am :weekday', + 'recurring_monthly' => 'An jedem :dayOfMonth. Tag des Monats', + 'recurring_ndom' => 'An jedem :dayOfMonth. :weekday', + 'recurring_yearly' => 'Jährlich am :date', + 'overview_for_recurrence' => 'Übersicht der regelmäßigen Buchungen „:title”', + 'warning_duplicates_repetitions' => 'In seltenen Fällen werden die Daten zweimal in dieser Liste angezeigt. Dies kann passieren, wenn mehrere Wiederholungen aufeinandertreffen. Firefly III erzeugt immer eine Transaktion pro Tag.', + 'created_transactions' => 'Ähnliche Buchungen', + 'expected_Withdrawals' => 'Erwartete Abzüge', + 'expected_Deposits' => 'Erwartete Einzahlungen', + 'expected_Transfers' => 'Erwartete Überweisungen', + 'created_Withdrawals' => 'Erstellte Abzüge', + 'created_Deposits' => 'Erstellte Einzahlungen', + 'created_Transfers' => 'Erstellte Überweisungen', + 'created_from_recurrence' => 'Erstellt aus Dauerauftrag „:title” (#:id)', - 'recurring_meta_field_tags' => 'Tags', - 'recurring_meta_field_notes' => 'Notes', - 'recurring_meta_field_bill_id' => 'Bill', - 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', - 'create_new_recurrence' => 'Create new recurring transaction', - 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', - 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', - 'no_currency' => '(no currency)', - 'mandatory_for_recurring' => 'Mandatory recurrence information', - 'mandatory_for_transaction' => 'Mandatory transaction information', - 'optional_for_recurring' => 'Optional recurrence information', - 'optional_for_transaction' => 'Optional transaction information', - 'change_date_other_options' => 'Change the "first date" to see more options.', - 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', - 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', - 'repeat_forever' => 'Repeat forever', - 'repeat_until_date' => 'Repeat until date', - 'repeat_times' => 'Repeat a number of times', - 'recurring_skips_one' => 'Every other', - 'recurring_skips_more' => 'Skips :count occurrences', - 'store_new_recurrence' => 'Store recurring transaction', - 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', - 'edit_recurrence' => 'Edit recurring transaction ":title"', - 'recurring_repeats_until' => 'Repeats until :date', - 'recurring_repeats_forever' => 'Repeats forever', - 'recurring_repeats_x_times' => 'Repeats :count time(s)', - 'update_recurrence' => 'Update recurring transaction', - 'updated_recurrence' => 'Updated recurring transaction ":title"', - 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'recurring_meta_field_tags' => 'Schlagwörter', + 'recurring_meta_field_notes' => 'Anmerkungen', + 'recurring_meta_field_bill_id' => 'Rechnung', + 'recurring_meta_field_piggy_bank_id' => 'Sparschwein', + 'create_new_recurrence' => 'Neuen Dauerauftrag erstellen', + 'help_first_date' => 'Geben Sie die erste erwartete Wiederholung an. Zeitpunkt muss in der Zukunft liegen.', + 'help_first_date_no_past' => 'Geben Sie die erste erwartete Wiederholung an. Firefly III erzeugt keine Buchungen die in der Vergangenheit liegen.', + 'no_currency' => '(ohne Währung)', + 'mandatory_for_recurring' => 'Erforderliche Wiederholungsinformationen', + 'mandatory_for_transaction' => 'Erforderliche Buchungsinformationen', + 'optional_for_recurring' => 'Optionale Wiederholungsinformationen', + 'optional_for_transaction' => 'Optionale Buchungsinformationen', + 'change_date_other_options' => 'Ändern Sie das „erste Datum”, um weitere Optionen anzuzeigen.', + 'mandatory_fields_for_tranaction' => 'Diese Werte enden in der/den zu erstellenden Buchung(en)', + 'click_for_calendar' => 'Klicken Sie hier für einen Kalender, der Ihnen anzeigt, wann sich die Buchung wiederholen würde.', + 'repeat_forever' => 'Wiederholt sich für immer', + 'repeat_until_date' => 'Wiederholen bis Datum', + 'repeat_times' => 'Wiederholen Sie mehrmals', + 'recurring_skips_one' => 'Alle anderen', + 'recurring_skips_more' => 'Überspringt :count Vorgänge', + 'store_new_recurrence' => 'Dauerauftrag speichern', + 'stored_new_recurrence' => 'Dauerauftrag „:title” erfolgreich gespeichert.', + 'edit_recurrence' => 'Dauerauftrag „:title” bearbeiten', + 'recurring_repeats_until' => 'Wiederholt sich bis :date', + 'recurring_repeats_forever' => 'Wiederholt sich für immer', + 'recurring_repeats_x_times' => 'Wiederholt sich :count mal', + 'update_recurrence' => 'Dauerauftrag aktualisieren', + 'updated_recurrence' => 'Dauerauftrag ":title" aktualisiert', + 'recurrence_is_inactive' => 'Dieser Dauerauftrag ist nicht aktiv und erzeugt keine neuen Buchungen.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index b5bcbab8b4..b7e269b13e 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Ändere zu Einzahlung', 'convert_Transfer' => 'In Umbuchung umwandeln', - 'amount' => 'Betrag', - 'foreign_amount' => 'Ausländischer Betrag', - 'existing_attachments' => 'Bestehende Anhänge', - 'date' => 'Datum', - 'interest_date' => 'Zinstermin', - 'book_date' => 'Buchungsdatum', - 'process_date' => 'Bearbeitungsdatum', - 'category' => 'Kategorie', - 'tags' => 'Schlagwörter', - 'deletePermanently' => 'Dauerhaft löschen', - 'cancel' => 'Abbrechen', - 'targetdate' => 'Zieldatum', - 'startdate' => 'Startdatum', - 'tag' => 'Schlagwort', - 'under' => 'Unter', - 'symbol' => 'Zeichen', - 'code' => 'Schlüssel', - 'iban' => 'IBAN', - 'accountNumber' => 'Kontonummer', - 'creditCardNumber' => 'Kreditkartennummer', - 'has_headers' => 'Kopfzeilen', - 'date_format' => 'Datumsformat', - 'specifix' => 'Bank- oder Dateispezifischer Korrekturen', - 'attachments[]' => 'Anhänge', - 'store_new_withdrawal' => 'Speichere neue Ausgabe', - 'store_new_deposit' => 'Speichere neue Einnahme', - 'store_new_transfer' => 'Neue Umbuchung speichern', - 'add_new_withdrawal' => 'Fügen Sie eine neue Ausgabe hinzu', - 'add_new_deposit' => 'Fügen Sie eine neue Einnahme hinzu', - 'add_new_transfer' => 'Neue Umbuchung anlegen', - 'title' => 'Titel', - 'notes' => 'Notizen', - 'filename' => 'Dateiname', - 'mime' => 'MIME-Typ', - 'size' => 'Größe', - 'trigger' => 'Auslöser', - 'stop_processing' => 'Verarbeitung beenden', - 'start_date' => 'Anfang des Bereichs', - 'end_date' => 'Ende des Bereichs', - 'export_start_range' => 'Beginn des Exportbereichs', - 'export_end_range' => 'Ende des Exportbereichs', - 'export_format' => 'Dateiformat', - 'include_attachments' => 'Hochgeladene Anhänge hinzufügen', - 'include_old_uploads' => 'Importierte Daten hinzufügen', - 'accounts' => 'Exportiere die Überweisungen von diesem Konto', - 'delete_account' => 'Konto „:name” löschen', - 'delete_bill' => 'Rechnung „:name” löschen', - 'delete_budget' => 'Kostenrahmen „:name” löschen', - 'delete_category' => 'Kategorie „:name” löschen', - 'delete_currency' => 'Währung „:name” löschen', - 'delete_journal' => 'Lösche Überweisung mit Beschreibung ":description"', - 'delete_attachment' => 'Anhang „:name” löschen', - 'delete_rule' => 'Lösche Regel ":title"', - 'delete_rule_group' => 'Lösche Regelgruppe ":title"', - 'delete_link_type' => 'Verknüpfungstyp „:name” löschen', - 'delete_user' => 'Benutzer ":email" löschen', - 'user_areYouSure' => 'Wenn Sie den Benutzer ":email" löschen, ist alles weg. Es gibt keine Sicherung, Wiederherstellung oder ähnliches. Wenn Sie sich selbst löschen, verlieren Sie den Zugriff auf diese Instanz von Firefly III.', - 'attachment_areYouSure' => 'Möchten Sie den Anhang „:name” wirklich löschen?', - 'account_areYouSure' => 'Möchten Sie das Konto „:name” wirklich löschen?', - 'bill_areYouSure' => 'Möchten Sie die Rechnung „:name” wirklich löschen?', - 'rule_areYouSure' => 'Sind Sie sicher, dass Sie die Regel mit dem Titel ":title" löschen möchten?', - 'ruleGroup_areYouSure' => 'Sind Sie sicher, dass sie die Regelgruppe ":title" löschen möchten?', - 'budget_areYouSure' => 'Möchten Sie den Kostenrahmen „:name” wirklich löschen?', - 'category_areYouSure' => 'Möchten Sie die Kategorie „:name” wirklich löschen?', - 'currency_areYouSure' => 'Möchten Sie die Währung „:name” wirklich löschen?', - 'piggyBank_areYouSure' => 'Möchten Sie das Sparschwein „:name” wirklich löschen?', - 'journal_areYouSure' => 'Sind Sie sicher, dass Sie die Überweisung mit dem Namen ":description" löschen möchten?', - 'mass_journal_are_you_sure' => 'Sind Sie sicher, dass Sie diese Überweisung löschen möchten?', - 'tag_areYouSure' => 'Möchten Sie das Schlagwort „:tag” wirklich löschen?', - 'journal_link_areYouSure' => 'Sind Sie sicher, dass Sie die Verknüpfung zwischen :source und :destination löschen möchten?', - 'linkType_areYouSure' => 'Möchten Sie den Verknüpfungstyp „:name” („:inward”/„:outward”) wirklich löschen?', - 'permDeleteWarning' => 'Das Löschen von Dingen in Firefly III ist dauerhaft und kann nicht rückgängig gemacht werden.', - 'mass_make_selection' => 'Sie können das Löschen von Elementen verhindern, indem Sie die Checkbox entfernen.', - 'delete_all_permanently' => 'Ausgewähltes dauerhaft löschen', - 'update_all_journals' => 'Diese Transaktionen aktualisieren', - 'also_delete_transactions' => 'Die einzige Überweisung, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Überweisungen, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'also_delete_connections' => 'Die einzige Transaktion, die mit diesem Verknüpfungstyp verknüpft ist, verliert diese Verbindung. • Alle :count Buchungen, die mit diesem Verknüpfungstyp verknüpft sind, verlieren ihre Verbindung.', - 'also_delete_rules' => 'Die einzige Regel, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Regeln, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'also_delete_piggyBanks' => 'Das einzige Sparschwein, das mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Sparschweine, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'bill_keep_transactions' => 'Die einzige Überweisung, die mit dieser Rechnung verknüpft ist, wird nicht gelöscht. | Keine der :count Überweisungen, die mit dieser Rechnung verknüpft sind, werden gelöscht.', - 'budget_keep_transactions' => 'Die einzige Buchung, die mit dieser Rechnung verknüpft ist, wird nicht gelöscht. | Keine der :count Buchungen, die mit dieser Rechnung verknüpft sind, werden gelöscht.', - 'category_keep_transactions' => 'Die eine Überweisungen, die mit dieser Kategorie verknüpft ist, wird nicht gelöscht. | Keine der :count Kategorien, die mit dieser Rechnung verknüpft sind, werden gelöscht.', - 'tag_keep_transactions' => 'Die einzige Buchung, die mit diesem Schlagwort verbunden ist, wird nicht gelöscht. • Alle :count Vorgänge, die mit diesem Schlagwort verbunden sind, werden nicht gelöscht.', - 'check_for_updates' => 'Nach Updates suchen', + 'amount' => 'Betrag', + 'foreign_amount' => 'Ausländischer Betrag', + 'existing_attachments' => 'Bestehende Anhänge', + 'date' => 'Datum', + 'interest_date' => 'Zinstermin', + 'book_date' => 'Buchungsdatum', + 'process_date' => 'Bearbeitungsdatum', + 'category' => 'Kategorie', + 'tags' => 'Schlagwörter', + 'deletePermanently' => 'Dauerhaft löschen', + 'cancel' => 'Abbrechen', + 'targetdate' => 'Zieldatum', + 'startdate' => 'Startdatum', + 'tag' => 'Schlagwort', + 'under' => 'Unter', + 'symbol' => 'Zeichen', + 'code' => 'Schlüssel', + 'iban' => 'IBAN', + 'accountNumber' => 'Kontonummer', + 'creditCardNumber' => 'Kreditkartennummer', + 'has_headers' => 'Kopfzeilen', + 'date_format' => 'Datumsformat', + 'specifix' => 'Bank- oder Dateispezifischer Korrekturen', + 'attachments[]' => 'Anhänge', + 'store_new_withdrawal' => 'Speichere neue Ausgabe', + 'store_new_deposit' => 'Speichere neue Einnahme', + 'store_new_transfer' => 'Neue Umbuchung speichern', + 'add_new_withdrawal' => 'Fügen Sie eine neue Ausgabe hinzu', + 'add_new_deposit' => 'Fügen Sie eine neue Einnahme hinzu', + 'add_new_transfer' => 'Neue Umbuchung anlegen', + 'title' => 'Titel', + 'notes' => 'Notizen', + 'filename' => 'Dateiname', + 'mime' => 'MIME-Typ', + 'size' => 'Größe', + 'trigger' => 'Auslöser', + 'stop_processing' => 'Verarbeitung beenden', + 'start_date' => 'Anfang des Bereichs', + 'end_date' => 'Ende des Bereichs', + 'export_start_range' => 'Beginn des Exportbereichs', + 'export_end_range' => 'Ende des Exportbereichs', + 'export_format' => 'Dateiformat', + 'include_attachments' => 'Hochgeladene Anhänge hinzufügen', + 'include_old_uploads' => 'Importierte Daten hinzufügen', + 'accounts' => 'Exportiere die Überweisungen von diesem Konto', + 'delete_account' => 'Konto „:name” löschen', + 'delete_bill' => 'Rechnung „:name” löschen', + 'delete_budget' => 'Kostenrahmen „:name” löschen', + 'delete_category' => 'Kategorie „:name” löschen', + 'delete_currency' => 'Währung „:name” löschen', + 'delete_journal' => 'Lösche Überweisung mit Beschreibung ":description"', + 'delete_attachment' => 'Anhang „:name” löschen', + 'delete_rule' => 'Lösche Regel ":title"', + 'delete_rule_group' => 'Lösche Regelgruppe ":title"', + 'delete_link_type' => 'Verknüpfungstyp „:name” löschen', + 'delete_user' => 'Benutzer ":email" löschen', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Wenn Sie den Benutzer ":email" löschen, ist alles weg. Es gibt keine Sicherung, Wiederherstellung oder ähnliches. Wenn Sie sich selbst löschen, verlieren Sie den Zugriff auf diese Instanz von Firefly III.', + 'attachment_areYouSure' => 'Möchten Sie den Anhang „:name” wirklich löschen?', + 'account_areYouSure' => 'Möchten Sie das Konto „:name” wirklich löschen?', + 'bill_areYouSure' => 'Möchten Sie die Rechnung „:name” wirklich löschen?', + 'rule_areYouSure' => 'Sind Sie sicher, dass Sie die Regel mit dem Titel ":title" löschen möchten?', + 'ruleGroup_areYouSure' => 'Sind Sie sicher, dass sie die Regelgruppe ":title" löschen möchten?', + 'budget_areYouSure' => 'Möchten Sie den Kostenrahmen „:name” wirklich löschen?', + 'category_areYouSure' => 'Möchten Sie die Kategorie „:name” wirklich löschen?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Möchten Sie die Währung „:name” wirklich löschen?', + 'piggyBank_areYouSure' => 'Möchten Sie das Sparschwein „:name” wirklich löschen?', + 'journal_areYouSure' => 'Sind Sie sicher, dass Sie die Überweisung mit dem Namen ":description" löschen möchten?', + 'mass_journal_are_you_sure' => 'Sind Sie sicher, dass Sie diese Überweisung löschen möchten?', + 'tag_areYouSure' => 'Möchten Sie das Schlagwort „:tag” wirklich löschen?', + 'journal_link_areYouSure' => 'Sind Sie sicher, dass Sie die Verknüpfung zwischen :source und :destination löschen möchten?', + 'linkType_areYouSure' => 'Möchten Sie den Verknüpfungstyp „:name” („:inward”/„:outward”) wirklich löschen?', + 'permDeleteWarning' => 'Das Löschen von Dingen in Firefly III ist dauerhaft und kann nicht rückgängig gemacht werden.', + 'mass_make_selection' => 'Sie können das Löschen von Elementen verhindern, indem Sie die Checkbox entfernen.', + 'delete_all_permanently' => 'Ausgewähltes dauerhaft löschen', + 'update_all_journals' => 'Diese Transaktionen aktualisieren', + 'also_delete_transactions' => 'Die einzige Überweisung, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Überweisungen, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', + 'also_delete_connections' => 'Die einzige Transaktion, die mit diesem Verknüpfungstyp verknüpft ist, verliert diese Verbindung. • Alle :count Buchungen, die mit diesem Verknüpfungstyp verknüpft sind, verlieren ihre Verbindung.', + 'also_delete_rules' => 'Die einzige Regel, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Regeln, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', + 'also_delete_piggyBanks' => 'Das einzige Sparschwein, das mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Sparschweine, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Nach Updates suchen', 'email' => 'E-Mail Adresse', 'password' => 'Passwort', @@ -223,15 +226,16 @@ return [ 'inward' => 'Beschreibung der Eingänge', 'outward' => 'Beschreibung der Ausgänge', 'rule_group_id' => 'Regelgruppe', - 'transaction_description' => 'Transaction description', - 'first_date' => 'First date', - 'transaction_type' => 'Transaction type', - 'repeat_until' => 'Repeat until', - 'recurring_description' => 'Recurring transaction description', - 'repetition_type' => 'Type of repetition', - 'foreign_currency_id' => 'Foreign currency', - 'repetition_end' => 'Repetition ends', - 'repetitions' => 'Repetitions', - 'calendar' => 'Calendar', + 'transaction_description' => 'Beschreibung der Buchung', + 'first_date' => 'Erstes Datum', + 'transaction_type' => 'Art der Buchung', + 'repeat_until' => 'Wiederholen bis', + 'recurring_description' => 'Beschreibung des Dauerauftrags', + 'repetition_type' => 'Art der Wiederholung', + 'foreign_currency_id' => 'Fremdwährung', + 'repetition_end' => 'Wiederholung endet', + 'repetitions' => 'Wiederholungen', + 'calendar' => 'Kalender', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index 2e2c30e8e5..c4a3c14ceb 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'Es scheint, dass Sie keine Konten zum Importieren ausgewählt haben.', 'imported_from_account' => 'Von „:account” importiert', 'spectre_account_with_number' => 'Konto :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq-Konten', 'job_config_bunq_accounts_text' => 'Dies sind jene Konten, die mit Ihrem „bunq”-Konto verknüpft sind. Bitte wählen Sie die Konten aus, von denen Sie importieren möchten, und in welches Konto die Buchungen importiert werden sollen.', 'bunq_no_mapping' => 'Es scheint, dass Sie keine Konten ausgewählt haben.', 'should_download_config' => 'Sie sollten die Konfigurationsdatei für diesen Aufgabe herunterladen. Dies wird zukünftige Importe erheblich erleichtern.', 'share_config_file' => 'Wenn Sie Daten von einer öffentlichen Bank importiert haben, sollten Sie Ihre Konfigurationsdatei freigeben, damit es für andere Benutzer einfach ist, ihre Daten zu importieren. Wenn Sie Ihre Konfigurationsdatei freigeben, werden Ihre finanziellen Daten nicht preisgegeben.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT Code', diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php index 805fd8f156..fe4b250ed6 100644 --- a/resources/lang/de_DE/list.php +++ b/resources/lang/de_DE/list.php @@ -123,9 +123,9 @@ return [ 'spectre_last_use' => 'Letzte Anmeldung', 'spectre_status' => 'Status', 'bunq_payment_id' => 'bunq-Zahlungskennung', - 'repetitions' => 'Repetitions', - 'title' => 'Title', - 'transaction_s' => 'Transaction(s)', - 'field' => 'Field', - 'value' => 'Value', + 'repetitions' => 'Wiederholungen', + 'title' => 'Titel', + 'transaction_s' => 'Buchung(en)', + 'field' => 'Feld', + 'value' => 'Wert', ]; diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 5269f95e73..c2d2d34059 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'Der Wert von :attribute ist nicht bekannt', 'accepted' => ':attribute muss akzeptiert werden.', 'bic' => 'Dies ist kein gültiger BIC.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute muss größer als Null sein.', 'active_url' => ':attribute ist keine gültige URL.', 'after' => ':attribute muss ein Datum nach :date sein.', @@ -112,7 +114,7 @@ return [ 'amount_zero' => 'Der Gesamtbetrag darf nicht Null sein', 'unique_piggy_bank_for_user' => 'Der Name des Sparschweins muss eindeutig sein.', 'secure_password' => 'Das ist kein sicheres Passwort. Bitte versuchen Sie es erneut. Weitere Informationen finden Sie unter https://github.com/firefly-iii/help/wiki/Secure-password', - 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', + 'valid_recurrence_rep_type' => 'Ungültige Wiederholungsart für Daueraufträge', 'attributes' => [ 'email' => 'E-Mail Adresse', 'description' => 'Beschreibung', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index ad4aba617b..20dc9fa099 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Escanee el código QR con una aplicación en su teléfono como Authy o Google autenticator y ingrese el código generado.', 'pref_two_factor_auth_reset_code' => 'Reiniciar código de verificación', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Guardar la configuración', 'saved_preferences' => '¡Preferencias guardadas!', 'preferences_general' => 'General', @@ -901,7 +902,6 @@ return [ 'balanceEnd' => 'Balance al final de un periodo', 'splitByAccount' => 'Separada por cuenta', 'coveredWithTags' => 'Cubierta con etiquetas', - 'leftUnbalanced' => 'Izquierda desbalanceada', 'leftInBudget' => 'Dejado en el presupuesto', 'sumOfSums' => 'Suma de sumas', 'noCategory' => '(sin categoría)', @@ -1261,4 +1261,15 @@ return [ 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index a199b95f86..3f87b4a1c1 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convertir depósito', 'convert_Transfer' => 'Convertir transferencia', - 'amount' => 'Importe', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Fecha', - 'interest_date' => 'Fecha de interés', - 'book_date' => 'Fecha de registro', - 'process_date' => 'Fecha de procesamiento', - 'category' => 'Categoría', - 'tags' => 'Etiquetas', - 'deletePermanently' => 'Borrar permanentemente', - 'cancel' => 'Cancelar', - 'targetdate' => 'Fecha tope', - 'startdate' => 'Fecha de inicio', - 'tag' => 'Etiqueta', - 'under' => 'Debajo', - 'symbol' => 'Símbolo', - 'code' => 'Código', - 'iban' => 'IBAN', - 'accountNumber' => 'Número de cuenta', - 'creditCardNumber' => 'Número de la tarjeta de crédito', - 'has_headers' => 'Encabezados', - 'date_format' => 'Formato de fecha', - 'specifix' => 'Banco- o archivo de soluciones especificas', - 'attachments[]' => 'Adjuntos', - 'store_new_withdrawal' => 'Guardar rueva retirada de efectivo', - 'store_new_deposit' => 'Guardar nuevo depósito', - 'store_new_transfer' => 'Guardar nueva transferencia', - 'add_new_withdrawal' => 'Añadir rueva retirada de efectivo', - 'add_new_deposit' => 'Añadir nuevo depósito', - 'add_new_transfer' => 'Añadir nueva transferencia', - 'title' => 'Título', - 'notes' => 'Notas', - 'filename' => 'Nombre de fichero', - 'mime' => 'Tipo Mime', - 'size' => 'Tamaño', - 'trigger' => 'Disparador', - 'stop_processing' => 'Detener el procesamiento', - 'start_date' => 'Inicio del rango', - 'end_date' => 'Final del rango', - 'export_start_range' => 'Inicio del rango de exportación', - 'export_end_range' => 'Fin del rango de exportación', - 'export_format' => 'Formato del archivo', - 'include_attachments' => 'Incluir archivos adjuntos subidos', - 'include_old_uploads' => 'Incluir datos importados', - 'accounts' => 'Exportar transacciones de estas cuentas', - 'delete_account' => 'Borrar cuenta ":name"', - 'delete_bill' => 'Eliminar factura ":name"', - 'delete_budget' => 'Eliminar presupuesto ":name"', - 'delete_category' => 'Eliminar categoría ":name"', - 'delete_currency' => 'Eliminar divisa ":name"', - 'delete_journal' => 'Eliminar la transacción con descripción ":description"', - 'delete_attachment' => 'Eliminar adjunto ":name"', - 'delete_rule' => 'Eliminar regla ":title"', - 'delete_rule_group' => 'Eliminar grupo de reglas ":title"', - 'delete_link_type' => 'Eliminar tipo de enlace ":name"', - 'delete_user' => 'Eliminar usuario ":email"', - 'user_areYouSure' => 'Si elimina usuario ":email", todo desaparecerá. No hay deshacer, recuperar ni nada. Si te eliminas, perderás el acceso a esta instancia de Firefly III.', - 'attachment_areYouSure' => '¿Seguro que quieres eliminar el archivo adjunto llamado "name"?', - 'account_areYouSure' => '¿Seguro que quieres eliminar la cuenta llamada ":name"?', - 'bill_areYouSure' => '¿Seguro que quieres eliminar la factura llamada ":name"?', - 'rule_areYouSure' => '¿Seguro que quieres eliminar la regla titulada ":title"?', - 'ruleGroup_areYouSure' => '¿Seguro que quieres eliminar el grupo de reglas titulado ":title"?', - 'budget_areYouSure' => '¿Seguro que quieres eliminar el presupuesto llamado ":name"?', - 'category_areYouSure' => '¿Seguro que quieres eliminar la categoría llamada ":name"?', - 'currency_areYouSure' => '¿Está seguro que desea eliminar la moneda denominada ":name"?', - 'piggyBank_areYouSure' => '¿Está seguro que desea eliminar la hucha llamada ":name"?', - 'journal_areYouSure' => '¿Estás seguro de que deseas eliminar la transacción descrita ":description"?', - 'mass_journal_are_you_sure' => '¿Usted esta seguro de querer eliminar estas transacciones?', - 'tag_areYouSure' => '¿Seguro que quieres eliminar la etiqueta ":tag"?', - 'journal_link_areYouSure' => '¿Seguro que quieres eliminar el vínculo entre :source y :destination?', - 'linkType_areYouSure' => '¿Estás seguro de que deseas eliminar el tipo de vínculo ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'Aún puede evitar que se eliminen elementos quitando la casilla de verificación.', - 'delete_all_permanently' => 'Eliminar selección permanentemente', - 'update_all_journals' => 'Actualiza estas transacciones', - 'also_delete_transactions' => 'La única transacción conectada a esta cuenta también se eliminará. | Todas las :count transacciones conectadas a esta cuenta también se eliminarán.', - 'also_delete_connections' => 'La única transacción vinculada con este tipo de enlace perderá esta conexión. | Todas las :count transacciones vinculadas con este tipo de enlace perderán su conexión.', - 'also_delete_rules' => 'La única regla conectada a este grupo de reglas también se eliminará. | Todas las :count reglas conectadas a este grupo de reglas también se eliminarán.', - 'also_delete_piggyBanks' => 'La única alcancía conectada a esta cuenta también se eliminará. | Todas las :count alcancías conectadas a esta cuenta también se eliminará.', - 'bill_keep_transactions' => 'La única transacción conectada a esta factura no se eliminará. | Todas las :count transacciones conectadas a esta factura evitarán la eliminación.', - 'budget_keep_transactions' => 'La única transacción conectada a este presupuesto no se eliminará. | Todas las :count transacciones conectadas a este presupuesto evitarán la eliminación.', - 'category_keep_transactions' => 'La única transacción conectada a esta categoría no se eliminará. | Todas las :count transacciones conectadas a esta categoría evitarán la eliminación.', - 'tag_keep_transactions' => 'La única transacción conectada a esta etiqueta no se eliminará. | Todas las :count transacciones conectadas a esta etiqueta evitarán la eliminación.', - 'check_for_updates' => 'Ver actualizaciones', + 'amount' => 'Importe', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Fecha', + 'interest_date' => 'Fecha de interés', + 'book_date' => 'Fecha de registro', + 'process_date' => 'Fecha de procesamiento', + 'category' => 'Categoría', + 'tags' => 'Etiquetas', + 'deletePermanently' => 'Borrar permanentemente', + 'cancel' => 'Cancelar', + 'targetdate' => 'Fecha tope', + 'startdate' => 'Fecha de inicio', + 'tag' => 'Etiqueta', + 'under' => 'Debajo', + 'symbol' => 'Símbolo', + 'code' => 'Código', + 'iban' => 'IBAN', + 'accountNumber' => 'Número de cuenta', + 'creditCardNumber' => 'Número de la tarjeta de crédito', + 'has_headers' => 'Encabezados', + 'date_format' => 'Formato de fecha', + 'specifix' => 'Banco- o archivo de soluciones especificas', + 'attachments[]' => 'Adjuntos', + 'store_new_withdrawal' => 'Guardar rueva retirada de efectivo', + 'store_new_deposit' => 'Guardar nuevo depósito', + 'store_new_transfer' => 'Guardar nueva transferencia', + 'add_new_withdrawal' => 'Añadir rueva retirada de efectivo', + 'add_new_deposit' => 'Añadir nuevo depósito', + 'add_new_transfer' => 'Añadir nueva transferencia', + 'title' => 'Título', + 'notes' => 'Notas', + 'filename' => 'Nombre de fichero', + 'mime' => 'Tipo Mime', + 'size' => 'Tamaño', + 'trigger' => 'Disparador', + 'stop_processing' => 'Detener el procesamiento', + 'start_date' => 'Inicio del rango', + 'end_date' => 'Final del rango', + 'export_start_range' => 'Inicio del rango de exportación', + 'export_end_range' => 'Fin del rango de exportación', + 'export_format' => 'Formato del archivo', + 'include_attachments' => 'Incluir archivos adjuntos subidos', + 'include_old_uploads' => 'Incluir datos importados', + 'accounts' => 'Exportar transacciones de estas cuentas', + 'delete_account' => 'Borrar cuenta ":name"', + 'delete_bill' => 'Eliminar factura ":name"', + 'delete_budget' => 'Eliminar presupuesto ":name"', + 'delete_category' => 'Eliminar categoría ":name"', + 'delete_currency' => 'Eliminar divisa ":name"', + 'delete_journal' => 'Eliminar la transacción con descripción ":description"', + 'delete_attachment' => 'Eliminar adjunto ":name"', + 'delete_rule' => 'Eliminar regla ":title"', + 'delete_rule_group' => 'Eliminar grupo de reglas ":title"', + 'delete_link_type' => 'Eliminar tipo de enlace ":name"', + 'delete_user' => 'Eliminar usuario ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Si elimina usuario ":email", todo desaparecerá. No hay deshacer, recuperar ni nada. Si te eliminas, perderás el acceso a esta instancia de Firefly III.', + 'attachment_areYouSure' => '¿Seguro que quieres eliminar el archivo adjunto llamado "name"?', + 'account_areYouSure' => '¿Seguro que quieres eliminar la cuenta llamada ":name"?', + 'bill_areYouSure' => '¿Seguro que quieres eliminar la factura llamada ":name"?', + 'rule_areYouSure' => '¿Seguro que quieres eliminar la regla titulada ":title"?', + 'ruleGroup_areYouSure' => '¿Seguro que quieres eliminar el grupo de reglas titulado ":title"?', + 'budget_areYouSure' => '¿Seguro que quieres eliminar el presupuesto llamado ":name"?', + 'category_areYouSure' => '¿Seguro que quieres eliminar la categoría llamada ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => '¿Está seguro que desea eliminar la moneda denominada ":name"?', + 'piggyBank_areYouSure' => '¿Está seguro que desea eliminar la hucha llamada ":name"?', + 'journal_areYouSure' => '¿Estás seguro de que deseas eliminar la transacción descrita ":description"?', + 'mass_journal_are_you_sure' => '¿Usted esta seguro de querer eliminar estas transacciones?', + 'tag_areYouSure' => '¿Seguro que quieres eliminar la etiqueta ":tag"?', + 'journal_link_areYouSure' => '¿Seguro que quieres eliminar el vínculo entre :source y :destination?', + 'linkType_areYouSure' => '¿Estás seguro de que deseas eliminar el tipo de vínculo ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'Aún puede evitar que se eliminen elementos quitando la casilla de verificación.', + 'delete_all_permanently' => 'Eliminar selección permanentemente', + 'update_all_journals' => 'Actualiza estas transacciones', + 'also_delete_transactions' => 'La única transacción conectada a esta cuenta también se eliminará. | Todas las :count transacciones conectadas a esta cuenta también se eliminarán.', + 'also_delete_connections' => 'La única transacción vinculada con este tipo de enlace perderá esta conexión. | Todas las :count transacciones vinculadas con este tipo de enlace perderán su conexión.', + 'also_delete_rules' => 'La única regla conectada a este grupo de reglas también se eliminará. | Todas las :count reglas conectadas a este grupo de reglas también se eliminarán.', + 'also_delete_piggyBanks' => 'La única alcancía conectada a esta cuenta también se eliminará. | Todas las :count alcancías conectadas a esta cuenta también se eliminará.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Ver actualizaciones', 'email' => 'Correo electrónico', 'password' => 'Contraseña', @@ -233,5 +236,6 @@ return [ 'repetition_end' => 'Repetition ends', 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/es_ES/import.php b/resources/lang/es_ES/import.php index 8a812e4689..3676742295 100644 --- a/resources/lang/es_ES/import.php +++ b/resources/lang/es_ES/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index fc709dd13b..555dd9563d 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'El valor de :attribute es desconocido', 'accepted' => 'El :attribute debe ser aceptado.', 'bic' => 'Esto no es un BIC válido.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute debe ser mayor que cero.', 'active_url' => 'El campo :attribute no es una URL válida.', 'after' => 'El campo :attribute debe ser una fecha posterior a :date.', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index b08e26de14..7cd9b4d937 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scanner le code QR avec une application sur votre téléphone comme Authy ou Google Authenticator et entrez le code généré.', 'pref_two_factor_auth_reset_code' => 'Réinitialiser le code de vérification', 'pref_two_factor_auth_disable_2fa' => 'Désactiver l\'authentification en deux étapes', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Enregistrer les paramètres', 'saved_preferences' => 'Préférences enregistrées!', 'preferences_general' => 'Général', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Solde à la fin de la période', 'splitByAccount' => 'Divisé par compte', 'coveredWithTags' => 'Recouvert de tags', - 'leftUnbalanced' => 'Restant déséquilibré', 'leftInBudget' => 'Budget restant', 'sumOfSums' => 'Montant des sommes', 'noCategory' => '(aucune catégorie)', @@ -1260,4 +1260,15 @@ return [ 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index 22089a8599..df70869cf9 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convertir le dépôt', 'convert_Transfer' => 'Convertir le transfert', - 'amount' => 'Montant', - 'foreign_amount' => 'Montant externe', - 'existing_attachments' => 'Pièces jointes existantes', - 'date' => 'Date', - 'interest_date' => 'Date de l’intérêt', - 'book_date' => 'Date de réservation', - 'process_date' => 'Date de traitement', - 'category' => 'Catégorie', - 'tags' => 'Mots-clés', - 'deletePermanently' => 'Supprimer définitivement', - 'cancel' => 'Annuler', - 'targetdate' => 'Date cible', - 'startdate' => 'Date de début', - 'tag' => 'Mot-clé', - 'under' => 'En dessous de', - 'symbol' => 'Symbole', - 'code' => 'Code', - 'iban' => 'Numéro IBAN', - 'accountNumber' => 'N° de compte', - 'creditCardNumber' => 'Numéro de carte de crédit', - 'has_headers' => 'Entêtes ', - 'date_format' => 'Format de la date', - 'specifix' => 'Banque - ou déposer des corrections spécifiques', - 'attachments[]' => 'Pièces jointes', - 'store_new_withdrawal' => 'Enregistrer un nouveau retrait', - 'store_new_deposit' => 'Enregistrer un nouveau dépôt', - 'store_new_transfer' => 'Enregistrer un nouveau transfert', - 'add_new_withdrawal' => 'Ajouter un nouveau retrait', - 'add_new_deposit' => 'Ajouter un nouveau dépôt', - 'add_new_transfer' => 'Ajouter un nouveau transfert', - 'title' => 'Titre', - 'notes' => 'Notes', - 'filename' => 'Nom du fichier', - 'mime' => 'Type Mime', - 'size' => 'Taille', - 'trigger' => 'Déclencheur', - 'stop_processing' => 'Arrêter le traitement', - 'start_date' => 'Début de l\'étendue', - 'end_date' => 'Fin de l\'étendue', - 'export_start_range' => 'Début de l’étendue d’exportation', - 'export_end_range' => 'Fin de l’étendue d\'exportation', - 'export_format' => 'Format de fichier', - 'include_attachments' => 'Inclure des pièces jointes téléchargées', - 'include_old_uploads' => 'Inclure les données importées', - 'accounts' => 'Exporter les opérations depuis ces comptes', - 'delete_account' => 'Supprimer le compte ":name"', - 'delete_bill' => 'Supprimer la facture ":name"', - 'delete_budget' => 'Supprimer le budget ":name"', - 'delete_category' => 'Supprimer la catégorie ":name"', - 'delete_currency' => 'Supprimer la devise ":name"', - 'delete_journal' => 'Supprimer l\'opération ayant comme description ":description"', - 'delete_attachment' => 'Supprimer la pièce jointe ":name"', - 'delete_rule' => 'Supprimer la règle ":title"', - 'delete_rule_group' => 'Supprimer le groupe de filtres ":title"', - 'delete_link_type' => 'Supprimer le type de lien ":name"', - 'delete_user' => 'Supprimer l\'utilisateur ":email"', - 'user_areYouSure' => 'Si vous supprimez l\'utilisateur ":email", tout disparaitra. Il n\'y a pas d\'annulation, de "dé-suppression" ou quoi que ce soit de la sorte. Si vous supprimez votre propre compte, vous n\'aurez plus accès à cette instance de Firefly III.', - 'attachment_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la pièce jointe nommée ":name" ?', - 'account_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le compte nommé ":name" ?', - 'bill_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la facture nommée ":name" ?', - 'rule_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la règle intitulée ":title" ?', - 'ruleGroup_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le groupe de règles intitulé ":title" ?', - 'budget_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le budget nommé ":name" ?', - 'category_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la catégorie nommée ":name" ?', - 'currency_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la devise nommée ":name" ?', - 'piggyBank_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la tirelire nommée ":name" ?', - 'journal_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la description de l\'opération ":description" ?', - 'mass_journal_are_you_sure' => 'Êtes-vous sûr de que vouloir supprimer ces opérations ?', - 'tag_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le tag ":tag" ?', - 'journal_link_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le lien entre :source et :destination?', - 'linkType_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le type de lien ":name" (":inward" / ":outward") ?', - 'permDeleteWarning' => 'Supprimer quelque chose dans Firefly est permanent et ne peut pas être annulé.', - 'mass_make_selection' => 'Vous pouvez toujours empêcher des éléments d’être supprimés en décochant la case à cocher.', - 'delete_all_permanently' => 'Supprimer la selection définitivement', - 'update_all_journals' => 'Mettre à jour ces opérations', - 'also_delete_transactions' => 'La seule opération liée à ce compte sera aussi supprimée.|Les :count opérations liées à ce compte seront aussi supprimées.', - 'also_delete_connections' => 'La seule transaction liée à ce type de lien perdra cette connexion. | Toutes les transactions :count liées à ce type de lien perdront leur connexion.', - 'also_delete_rules' => 'La seule règle liée à ce groupe de règles sera aussi supprimée.|Les :count règles liées à ce groupe de règles seront aussi supprimées.', - 'also_delete_piggyBanks' => 'La seule tirelire liée à ce compte sera aussi supprimée.|Les :count tirelires liées à ce compte seront aussi supprimées.', - 'bill_keep_transactions' => 'La seule opération liée à cette facture ne sera pas supprimée.|Les :count opérations liées à cette facture ne seront pas supprimées.', - 'budget_keep_transactions' => 'La seule opération liée à ce budget ne sera pas supprimée.|Les :count opérations liées à ce budget ne seront pas supprimées.', - 'category_keep_transactions' => 'La seule opération liée à cette catégorie ne sera pas supprimée.|Les :count opérations liées à cette catégorie ne seront pas supprimées.', - 'tag_keep_transactions' => 'La seule opération liée à ce tag ne sera pas supprimée.|Les :count opérations liées à ce tag ne seront pas supprimées.', - 'check_for_updates' => 'Vérifier les mises à jour', + 'amount' => 'Montant', + 'foreign_amount' => 'Montant externe', + 'existing_attachments' => 'Pièces jointes existantes', + 'date' => 'Date', + 'interest_date' => 'Date de l’intérêt', + 'book_date' => 'Date de réservation', + 'process_date' => 'Date de traitement', + 'category' => 'Catégorie', + 'tags' => 'Mots-clés', + 'deletePermanently' => 'Supprimer définitivement', + 'cancel' => 'Annuler', + 'targetdate' => 'Date cible', + 'startdate' => 'Date de début', + 'tag' => 'Mot-clé', + 'under' => 'En dessous de', + 'symbol' => 'Symbole', + 'code' => 'Code', + 'iban' => 'Numéro IBAN', + 'accountNumber' => 'N° de compte', + 'creditCardNumber' => 'Numéro de carte de crédit', + 'has_headers' => 'Entêtes ', + 'date_format' => 'Format de la date', + 'specifix' => 'Banque - ou déposer des corrections spécifiques', + 'attachments[]' => 'Pièces jointes', + 'store_new_withdrawal' => 'Enregistrer un nouveau retrait', + 'store_new_deposit' => 'Enregistrer un nouveau dépôt', + 'store_new_transfer' => 'Enregistrer un nouveau transfert', + 'add_new_withdrawal' => 'Ajouter un nouveau retrait', + 'add_new_deposit' => 'Ajouter un nouveau dépôt', + 'add_new_transfer' => 'Ajouter un nouveau transfert', + 'title' => 'Titre', + 'notes' => 'Notes', + 'filename' => 'Nom du fichier', + 'mime' => 'Type Mime', + 'size' => 'Taille', + 'trigger' => 'Déclencheur', + 'stop_processing' => 'Arrêter le traitement', + 'start_date' => 'Début de l\'étendue', + 'end_date' => 'Fin de l\'étendue', + 'export_start_range' => 'Début de l’étendue d’exportation', + 'export_end_range' => 'Fin de l’étendue d\'exportation', + 'export_format' => 'Format de fichier', + 'include_attachments' => 'Inclure des pièces jointes téléchargées', + 'include_old_uploads' => 'Inclure les données importées', + 'accounts' => 'Exporter les opérations depuis ces comptes', + 'delete_account' => 'Supprimer le compte ":name"', + 'delete_bill' => 'Supprimer la facture ":name"', + 'delete_budget' => 'Supprimer le budget ":name"', + 'delete_category' => 'Supprimer la catégorie ":name"', + 'delete_currency' => 'Supprimer la devise ":name"', + 'delete_journal' => 'Supprimer l\'opération ayant comme description ":description"', + 'delete_attachment' => 'Supprimer la pièce jointe ":name"', + 'delete_rule' => 'Supprimer la règle ":title"', + 'delete_rule_group' => 'Supprimer le groupe de filtres ":title"', + 'delete_link_type' => 'Supprimer le type de lien ":name"', + 'delete_user' => 'Supprimer l\'utilisateur ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Si vous supprimez l\'utilisateur ":email", tout disparaitra. Il n\'y a pas d\'annulation, de "dé-suppression" ou quoi que ce soit de la sorte. Si vous supprimez votre propre compte, vous n\'aurez plus accès à cette instance de Firefly III.', + 'attachment_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la pièce jointe nommée ":name" ?', + 'account_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le compte nommé ":name" ?', + 'bill_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la facture nommée ":name" ?', + 'rule_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la règle intitulée ":title" ?', + 'ruleGroup_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le groupe de règles intitulé ":title" ?', + 'budget_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le budget nommé ":name" ?', + 'category_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la catégorie nommée ":name" ?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la devise nommée ":name" ?', + 'piggyBank_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la tirelire nommée ":name" ?', + 'journal_areYouSure' => 'Êtes-vous sûr de vouloir supprimer la description de l\'opération ":description" ?', + 'mass_journal_are_you_sure' => 'Êtes-vous sûr de que vouloir supprimer ces opérations ?', + 'tag_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le tag ":tag" ?', + 'journal_link_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le lien entre :source et :destination?', + 'linkType_areYouSure' => 'Êtes-vous sûr de vouloir supprimer le type de lien ":name" (":inward" / ":outward") ?', + 'permDeleteWarning' => 'Supprimer quelque chose dans Firefly est permanent et ne peut pas être annulé.', + 'mass_make_selection' => 'Vous pouvez toujours empêcher des éléments d’être supprimés en décochant la case à cocher.', + 'delete_all_permanently' => 'Supprimer la selection définitivement', + 'update_all_journals' => 'Mettre à jour ces opérations', + 'also_delete_transactions' => 'La seule opération liée à ce compte sera aussi supprimée.|Les :count opérations liées à ce compte seront aussi supprimées.', + 'also_delete_connections' => 'La seule transaction liée à ce type de lien perdra cette connexion. | Toutes les transactions :count liées à ce type de lien perdront leur connexion.', + 'also_delete_rules' => 'La seule règle liée à ce groupe de règles sera aussi supprimée.|Les :count règles liées à ce groupe de règles seront aussi supprimées.', + 'also_delete_piggyBanks' => 'La seule tirelire liée à ce compte sera aussi supprimée.|Les :count tirelires liées à ce compte seront aussi supprimées.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Vérifier les mises à jour', 'email' => 'Adresse Email', 'password' => 'Mot de passe', @@ -233,5 +236,6 @@ return [ 'repetition_end' => 'Repetition ends', 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index c3117e41a3..609ca1d477 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index 9a34e69703..2b53424127 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'La valeur de :attribute est inconnue', 'accepted' => 'Le champ :attribute doit être accepté.', 'bic' => 'Ce n’est pas un code BIC valide.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute doit être supérieur à zéro.', 'active_url' => 'Le champ :attribute n\'est pas une URL valide.', 'after' => 'Le champ :attribute doit être une date postérieure à :date.', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 3476f636e4..a4b8c09d66 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', 'pref_two_factor_auth_reset_code' => 'Setel ulang kode verifikasi', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Simpan Pengaturan', 'saved_preferences' => 'Preferensi disimpan!', 'preferences_general' => 'Umum', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo akhir periode', 'splitByAccount' => 'Dibagi oleh akun', 'coveredWithTags' => 'Ditutupi dengan tag', - 'leftUnbalanced' => 'Meninggalkan tidak seimbang', 'leftInBudget' => 'Yang tersisa di anggaran', 'sumOfSums' => 'Jumlah dari jumlah', 'noCategory' => '(Tidak ada kategori)', @@ -1260,4 +1260,15 @@ return [ 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index f0ce510ae0..676f38a3dd 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Convert deposit', 'convert_Transfer' => 'Mengkonversi transfer', - 'amount' => 'Jumlah', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Tanggal', - 'interest_date' => 'Tanggal bunga', - 'book_date' => 'Tanggal buku', - 'process_date' => 'Tanggal pemrosesan', - 'category' => 'Kategori', - 'tags' => 'Tag', - 'deletePermanently' => 'Hapus secara permanen', - 'cancel' => 'Membatalkan', - 'targetdate' => 'Tanggal target', - 'startdate' => 'Mulai tanggal', - 'tag' => 'Menandai', - 'under' => 'Dibawah', - 'symbol' => 'Simbol', - 'code' => 'Kode', - 'iban' => 'IBAN', - 'accountNumber' => 'Nomor akun', - 'creditCardNumber' => 'Nomor kartu kredit', - 'has_headers' => 'Judul', - 'date_format' => 'Format tanggal', - 'specifix' => 'Perbaikan spesifik bank atau berkas', - 'attachments[]' => 'Lampiran', - 'store_new_withdrawal' => 'Simpan penarikan baru', - 'store_new_deposit' => 'Simpan deposit baru', - 'store_new_transfer' => 'Simpan transfer baru', - 'add_new_withdrawal' => 'Tambahkan penarikan baru', - 'add_new_deposit' => 'Tambahkan deposit baru', - 'add_new_transfer' => 'Tambahkan transfer baru', - 'title' => 'Judul', - 'notes' => 'Catatan', - 'filename' => 'Nama file', - 'mime' => 'Tipe mime', - 'size' => 'Ukuran', - 'trigger' => 'Pelatuk', - 'stop_processing' => 'Berhenti memproses', - 'start_date' => 'Mulai dari jangkauan', - 'end_date' => 'Akhir rentang', - 'export_start_range' => 'Mulai dari rentang ekspor', - 'export_end_range' => 'Akhir rentang ekspor', - 'export_format' => 'Format file', - 'include_attachments' => 'Sertakan lampiran yang diunggah', - 'include_old_uploads' => 'Sertakan data yang diimpor', - 'accounts' => 'Mengekspor transaksi dari akun ini', - 'delete_account' => 'Delete account ":name"', - 'delete_bill' => 'Hapus tagihan ":name"', - 'delete_budget' => 'Hapus anggaran ":name"', - 'delete_category' => 'Hapus kategori ":name"', - 'delete_currency' => 'Hapus mata uang ":name"', - 'delete_journal' => 'Hapus transaksi dengan deskripsi ":description"', - 'delete_attachment' => 'Hapus lampiran ":name"', - 'delete_rule' => 'Hapus aturan ":title"', - 'delete_rule_group' => 'Hapus grup aturan ":title"', - 'delete_link_type' => 'Hapus jenis tautan ":name"', - 'delete_user' => 'Hapus pengguna ":email"', - 'user_areYouSure' => 'Jika Anda menghapus pengguna ":email", semuanya akan hilang. Tidak ada undo, undelete atau apapun. Jika Anda menghapus diri Anda sendiri, Anda akan kehilangan akses ke Firefly III ini.', - 'attachment_areYouSure' => 'Yakin ingin menghapus lampiran yang bernama ":name"?', - 'account_areYouSure' => 'Yakin ingin menghapus akun dengan nama ":name"?', - 'bill_areYouSure' => 'Yakin ingin menghapus tagihan yang bernama ":name"?', - 'rule_areYouSure' => 'Yakin ingin menghapus aturan yang berjudul ":title"?', - 'ruleGroup_areYouSure' => 'Yakin ingin menghapus grup aturan yang berjudul ":title"?', - 'budget_areYouSure' => 'Yakin ingin menghapus anggaran dengan nama ":name"?', - 'category_areYouSure' => 'Yakin ingin menghapus kategori yang bernama ":name"?', - 'currency_areYouSure' => 'Yakin ingin menghapus mata uang dengan nama ":name"?', - 'piggyBank_areYouSure' => 'Yakin ingin menghapus piggy bank yang bernama ":name"?', - 'journal_areYouSure' => 'Yakin ingin menghapus transaksi yang dijelaskan ":description"?', - 'mass_journal_are_you_sure' => 'Yakin ingin menghapus transaksi ini?', - 'tag_areYouSure' => 'Yakin ingin menghapus tag ":tag"?', - 'journal_link_areYouSure' => 'Yakin ingin menghapus tautan antara :source and :destination?', - 'linkType_areYouSure' => 'Yakin ingin menghapus jenis tautan ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'Anda masih dapat mencegah agar item dihapus dengan menghapus kotak centang.', - 'delete_all_permanently' => 'Hapus yang dipilih secara permanen', - 'update_all_journals' => 'Perbarui transaksi ini', - 'also_delete_transactions' => 'Satu-satunya transaksi yang terhubung ke akun ini akan dihapus juga. | Semua :count transaksi yang terhubung ke akun ini akan dihapus juga.', - 'also_delete_connections' => 'Satu-satunya transaksi yang terkait dengan jenis link ini akan kehilangan koneksi ini. Semua :count transaksi yang terkait dengan jenis link ini akan kehilangan koneksi mereka.', - 'also_delete_rules' => 'Aturan satu-satunya yang terhubung ke grup aturan ini akan dihapus juga. Aturan All :count yang terhubung ke grup aturan ini akan dihapus juga.', - 'also_delete_piggyBanks' => 'Satu-satunya piggy bank yang terhubung ke akun ini akan dihapus juga. Semua :count piggy bank yang terhubung ke akun ini akan dihapus juga.', - 'bill_keep_transactions' => 'Satu-satunya transaksi yang terhubung dengan tagihan ini tidak akan dihapus. Semua :count transaksi yang terhubung ke tagihan ini akan terhindar dari penghapusan.', - 'budget_keep_transactions' => 'Satu-satunya transaksi yang terhubung dengan anggaran ini tidak akan dihapus. Semua :count transaksi yang terhubung dengan anggaran ini akan terhindar dari penghapusan.', - 'category_keep_transactions' => 'Satu-satunya transaksi yang terhubung ke kategori ini tidak akan dihapus. Semua :count transaksi yang terhubung ke kategori ini akan terhindar dari penghapusan.', - 'tag_keep_transactions' => 'Satu-satunya transaksi yang terhubung ke tag ini tidak akan dihapus. Semua :count transaksi yang terhubung ke tag ini akan terhindar dari penghapusan.', - 'check_for_updates' => 'Check for updates', + 'amount' => 'Jumlah', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Tanggal', + 'interest_date' => 'Tanggal bunga', + 'book_date' => 'Tanggal buku', + 'process_date' => 'Tanggal pemrosesan', + 'category' => 'Kategori', + 'tags' => 'Tag', + 'deletePermanently' => 'Hapus secara permanen', + 'cancel' => 'Membatalkan', + 'targetdate' => 'Tanggal target', + 'startdate' => 'Mulai tanggal', + 'tag' => 'Menandai', + 'under' => 'Dibawah', + 'symbol' => 'Simbol', + 'code' => 'Kode', + 'iban' => 'IBAN', + 'accountNumber' => 'Nomor akun', + 'creditCardNumber' => 'Nomor kartu kredit', + 'has_headers' => 'Judul', + 'date_format' => 'Format tanggal', + 'specifix' => 'Perbaikan spesifik bank atau berkas', + 'attachments[]' => 'Lampiran', + 'store_new_withdrawal' => 'Simpan penarikan baru', + 'store_new_deposit' => 'Simpan deposit baru', + 'store_new_transfer' => 'Simpan transfer baru', + 'add_new_withdrawal' => 'Tambahkan penarikan baru', + 'add_new_deposit' => 'Tambahkan deposit baru', + 'add_new_transfer' => 'Tambahkan transfer baru', + 'title' => 'Judul', + 'notes' => 'Catatan', + 'filename' => 'Nama file', + 'mime' => 'Tipe mime', + 'size' => 'Ukuran', + 'trigger' => 'Pelatuk', + 'stop_processing' => 'Berhenti memproses', + 'start_date' => 'Mulai dari jangkauan', + 'end_date' => 'Akhir rentang', + 'export_start_range' => 'Mulai dari rentang ekspor', + 'export_end_range' => 'Akhir rentang ekspor', + 'export_format' => 'Format file', + 'include_attachments' => 'Sertakan lampiran yang diunggah', + 'include_old_uploads' => 'Sertakan data yang diimpor', + 'accounts' => 'Mengekspor transaksi dari akun ini', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Hapus tagihan ":name"', + 'delete_budget' => 'Hapus anggaran ":name"', + 'delete_category' => 'Hapus kategori ":name"', + 'delete_currency' => 'Hapus mata uang ":name"', + 'delete_journal' => 'Hapus transaksi dengan deskripsi ":description"', + 'delete_attachment' => 'Hapus lampiran ":name"', + 'delete_rule' => 'Hapus aturan ":title"', + 'delete_rule_group' => 'Hapus grup aturan ":title"', + 'delete_link_type' => 'Hapus jenis tautan ":name"', + 'delete_user' => 'Hapus pengguna ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Jika Anda menghapus pengguna ":email", semuanya akan hilang. Tidak ada undo, undelete atau apapun. Jika Anda menghapus diri Anda sendiri, Anda akan kehilangan akses ke Firefly III ini.', + 'attachment_areYouSure' => 'Yakin ingin menghapus lampiran yang bernama ":name"?', + 'account_areYouSure' => 'Yakin ingin menghapus akun dengan nama ":name"?', + 'bill_areYouSure' => 'Yakin ingin menghapus tagihan yang bernama ":name"?', + 'rule_areYouSure' => 'Yakin ingin menghapus aturan yang berjudul ":title"?', + 'ruleGroup_areYouSure' => 'Yakin ingin menghapus grup aturan yang berjudul ":title"?', + 'budget_areYouSure' => 'Yakin ingin menghapus anggaran dengan nama ":name"?', + 'category_areYouSure' => 'Yakin ingin menghapus kategori yang bernama ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Yakin ingin menghapus mata uang dengan nama ":name"?', + 'piggyBank_areYouSure' => 'Yakin ingin menghapus piggy bank yang bernama ":name"?', + 'journal_areYouSure' => 'Yakin ingin menghapus transaksi yang dijelaskan ":description"?', + 'mass_journal_are_you_sure' => 'Yakin ingin menghapus transaksi ini?', + 'tag_areYouSure' => 'Yakin ingin menghapus tag ":tag"?', + 'journal_link_areYouSure' => 'Yakin ingin menghapus tautan antara :source and :destination?', + 'linkType_areYouSure' => 'Yakin ingin menghapus jenis tautan ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'Anda masih dapat mencegah agar item dihapus dengan menghapus kotak centang.', + 'delete_all_permanently' => 'Hapus yang dipilih secara permanen', + 'update_all_journals' => 'Perbarui transaksi ini', + 'also_delete_transactions' => 'Satu-satunya transaksi yang terhubung ke akun ini akan dihapus juga. | Semua :count transaksi yang terhubung ke akun ini akan dihapus juga.', + 'also_delete_connections' => 'Satu-satunya transaksi yang terkait dengan jenis link ini akan kehilangan koneksi ini. Semua :count transaksi yang terkait dengan jenis link ini akan kehilangan koneksi mereka.', + 'also_delete_rules' => 'Aturan satu-satunya yang terhubung ke grup aturan ini akan dihapus juga. Aturan All :count yang terhubung ke grup aturan ini akan dihapus juga.', + 'also_delete_piggyBanks' => 'Satu-satunya piggy bank yang terhubung ke akun ini akan dihapus juga. Semua :count piggy bank yang terhubung ke akun ini akan dihapus juga.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Check for updates', 'email' => 'Alamat email', 'password' => 'Kata sandi', @@ -233,5 +236,6 @@ return [ 'repetition_end' => 'Repetition ends', 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/id_ID/import.php b/resources/lang/id_ID/import.php index 93809b1375..e726d3e7a7 100644 --- a/resources/lang/id_ID/import.php +++ b/resources/lang/id_ID/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index 7b662fd211..6bc2224290 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'Nilai dari :attribute tidak diketahui', 'accepted' => ':attribute harus diterima.', 'bic' => 'Ini bukan BIC yang valid.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute harus lebih besar dari nol.', 'active_url' => ':attribute bukan URL yang valid.', 'after' => ':attribute harus tanggal setelah :date.', diff --git a/resources/lang/it_IT/breadcrumbs.php b/resources/lang/it_IT/breadcrumbs.php index f631ee70e8..aa8ff8bb13 100644 --- a/resources/lang/it_IT/breadcrumbs.php +++ b/resources/lang/it_IT/breadcrumbs.php @@ -23,7 +23,7 @@ declare(strict_types=1); return [ - 'home' => 'Home', + 'home' => 'Pagina principale', 'edit_currency' => 'Modifica valuta ":name"', 'delete_currency' => 'Elimina valuta ":name"', 'newPiggyBank' => 'Crea un nuovo salvadanaio', @@ -39,7 +39,7 @@ return [ 'reports' => 'Resoconti', 'search_result' => 'Risultati di ricerca per ":query"', 'withdrawal_list' => 'Spese', - 'deposit_list' => 'Reddito, entrate e depositi', + 'deposit_list' => 'Redditi, entrate e depositi', 'transfer_list' => 'Trasferimenti', 'transfers_list' => 'Trasferimenti', 'reconciliation_list' => 'Riconciliazioni', diff --git a/resources/lang/it_IT/config.php b/resources/lang/it_IT/config.php index 69ac82d6e0..8ba46bb310 100644 --- a/resources/lang/it_IT/config.php +++ b/resources/lang/it_IT/config.php @@ -27,7 +27,7 @@ return [ 'locale' => 'it, Italiano, it_IT, it_IT.utf8, it_IT.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', - 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_date_day' => '%A %B %e %Y', 'month_and_day_no_year' => '%B %e', 'date_time' => '%e %B %Y, @ %T', 'specific_day' => '%e %B %Y', @@ -41,11 +41,11 @@ return [ 'week_in_year_js' => '[Week] s, AAAA', 'year_js' => 'AAAA', 'half_year_js' => 'T AAAA', - 'dow_1' => 'Monday', - 'dow_2' => 'Tuesday', - 'dow_3' => 'Wednesday', - 'dow_4' => 'Thursday', - 'dow_5' => 'Friday', - 'dow_6' => 'Saturday', - 'dow_7' => 'Sunday', + 'dow_1' => 'Lunedì', + 'dow_2' => 'Martedì', + 'dow_3' => 'Mercoledì', + 'dow_4' => 'Giovedì', + 'dow_5' => 'Venerdì', + 'dow_6' => 'Sabato', + 'dow_7' => 'Domenica', ]; diff --git a/resources/lang/it_IT/demo.php b/resources/lang/it_IT/demo.php index bf37e30f8f..3a3f7fda89 100644 --- a/resources/lang/it_IT/demo.php +++ b/resources/lang/it_IT/demo.php @@ -25,8 +25,8 @@ declare(strict_types=1); return [ 'no_demo_text' => 'Spiacenti, non esiste un testo dimostrativo aggiuntivo per questa pagina.', 'see_help_icon' => 'Tuttavia, l\'icona in alto a destra potrebbe dirti di più.', - 'index' => 'Benvenuto in Firefly III! In questa pagina ottieni una rapida panoramica delle tue finanze. Per ulteriori informazioni, controlla Account e → Account asset e, naturalmente, i budget e i resoconti. O semplicemente dai un\'occhiata in giro e vedi dove finisci.', - 'accounts-index' => 'I conti degli asset sono i tuoi conti bancari personali. I conti spese sono gli account a cui si spendono soldi, come negozi e amici. I conti delle entrate sono conti da cui ricevi denaro, come il tuo lavoro, il governo o altre fonti di reddito. In questa pagina puoi modificarli o rimuoverli.', + 'index' => 'Benvenuto in Firefly III! In questa pagina ottieni una rapida panoramica delle tue finanze. Per ulteriori informazioni, controlla Conti → Conti attività e, naturalmente, le pagine Budget e Resoconti. O semplicemente dai un\'occhiata in giro e vedi dove finisci.', + 'accounts-index' => 'I conti attività sono i conti bancari personali. I conti spese sono i conti verso cui si spendono soldi, come negozi e amici. I conti entrate sono conti da cui ricevi denaro, come il tuo lavoro, il governo o altre fonti di reddito. In questa pagina puoi modificarli o rimuoverli.', 'budgets-index' => 'Questa pagina ti mostra una panoramica dei tuoi budget. La barra in alto mostra l\'importo disponibile per essere preventivato. Questo può essere personalizzato per qualsiasi periodo facendo clic sull\'importo a destra. La quantità che hai effettivamente speso è mostrata nella barra sottostante. Di seguito sono indicate le spese per budget e ciò che hai preventivato per loro.', 'reports-index-start' => 'Firefly III supporta un certo numero di tipi di resoconto. Leggi facendo clic sull\'icona in alto a destra.', 'reports-index-examples' => 'Assicurati di dare un occhiata a questi esempi: una panoramica finanziaria mensile , una panoramica finanziaria annuale e una panoramica del budget .', @@ -34,6 +34,6 @@ return [ 'transactions-index' => 'Queste spese, depositi e trasferimenti non sono particolarmente fantasiosi. Sono stati generati automaticamente.', 'piggy-banks-index' => 'Come puoi vedere, ci sono tre salvadanai. Utilizzare i pulsanti più e meno per influenzare la quantità di denaro in ogni salvadanaio. Fare clic sul nome del salvadanaio per visualizzare la gestione per ciascun salvadanaio.', 'import-index' => 'Qualsiasi file CSV può essere importato in Firefly III. Supporta anche l\'importazione di dati da bunq e Spectre. Altre banche e aggregatori finanziari saranno implementati in futuro. Tuttavia, come utente demo, puoi vedere solo il provider "fittizio" in azione. Genererà alcune transazioni casuali per mostrarti come funziona il processo.', - 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', - 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-index' => 'Questa caratteristica è in corso di sviluppo e potrebbe non funzionare come ci si aspetta.', + 'recurring-create' => 'Questa caratteristica è in corso di sviluppo e potrebbe non funzionare come ci si aspetta.', ]; diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 7835737f09..cc270a8c5f 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -33,8 +33,8 @@ return [ 'today' => 'oggi', 'customRange' => 'Range personalizzato', 'apply' => 'Applica', - 'select_date' => 'Seleziona data..', - 'cancel' => 'Cancella', + 'select_date' => 'Seleziona data...', + 'cancel' => 'Annulla', 'from' => 'Da', 'to' => 'A', 'showEverything' => 'Mostra tutto', @@ -47,7 +47,7 @@ return [ 'create_new_stuff' => 'Crea nuove cose', 'new_withdrawal' => 'Nuovo prelievo', 'create_new_transaction' => 'Crea nuova transazione', - 'go_to_asset_accounts' => 'Visualizza i tuoi movimenti', + 'go_to_asset_accounts' => 'Visualizza i tuoi conti attività', 'go_to_budgets' => 'Vai ai tuoi budget', 'go_to_categories' => 'Vai alle tue categorie', 'go_to_bills' => 'Vai alle tue bollette', @@ -57,9 +57,9 @@ return [ 'new_deposit' => 'Nuova entrata', 'new_transfer' => 'Nuovo trasferimento', 'new_transfers' => 'Nuovo trasferimento', - 'new_asset_account' => 'Nuova attività conto', - 'new_expense_account' => 'Nuova spesa conto', - 'new_revenue_account' => 'Nuova entrata conto', + 'new_asset_account' => 'Nuovo conto attività', + 'new_expense_account' => 'Nuovo conto spese', + 'new_revenue_account' => 'Nuovo conto entrate', 'new_budget' => 'Nuovo budget', 'new_bill' => 'Nuova bolletta', 'block_account_logout' => 'Sei stato disconnesso. Gli account bloccati non possono utilizzare questo sito. Ti sei registrato con un indirizzo email valido?', @@ -69,44 +69,44 @@ return [ 'flash_error' => 'Errore!', 'flash_info_multiple' => 'C\'è un messaggio | Ci sono :count messages', 'flash_error_multiple' => 'C\'è un errore | Ci sono :count errors', - 'net_worth' => 'Valore netto', + 'net_worth' => 'Patrimonio', 'route_has_no_help' => 'Non c\'è aiuto per questa rotta.', 'help_for_this_page' => 'Aiuto per questa pagina', 'no_help_could_be_found' => 'Non è stato trovato alcun testo di aiuto.', 'no_help_title' => 'Ci scusiamo, si è verificato un errore.', 'two_factor_welcome' => 'Ciao, :user!', - 'two_factor_enter_code' => 'Per continuare, inserisci il tuo codice di autenticazione a due fattori. La tua applicazione può generarlo per te.', - 'two_factor_code_here' => 'Inserisci codice qui', + 'two_factor_enter_code' => 'Per continuare inserisci il tuo codice di autenticazione a due fattori. La tua applicazione può generarlo per te.', + 'two_factor_code_here' => 'Inserisci qui il codice', 'two_factor_title' => 'Autenticazione a due fattori', - 'authenticate' => 'Autenticato', + 'authenticate' => 'Autenticati', 'two_factor_forgot_title' => 'Autenticazione a due fattori persa', 'two_factor_forgot' => 'Ho dimenticato la mia chiave a due fattori.', 'two_factor_lost_header' => 'Hai perso l\'autenticazione a due fattori?', 'two_factor_lost_intro' => 'Sfortunatamente, questo non è qualcosa che puoi resettare dall\'interfaccia web. Hai due scelte.', 'two_factor_lost_fix_self' => 'Se si esegue la propria istanza di Firefly III, controllare i log in storage/logs per istruzioni.', - 'two_factor_lost_fix_owner' => 'In caso contrario, invia un mail al proprietario del sito :site_owner e chiedi loro di ripristinare la possibilità di autenticarsi a due fattori.', + 'two_factor_lost_fix_owner' => 'In caso contrario, invia un mail al proprietario del sito, :site_owner, e chiedi loro di resettare l\'autenticazione a due fattori.', 'warning_much_data' => ':days di caricamento dei dati potrebbero richiedere un pò di tempo.', 'registered' => 'Ti sei registrato con successo!', - 'Default asset account' => 'Attività conto predefinito', - 'no_budget_pointer' => 'Sembra che tu non abbia ancora dei budget. Dovresti crearne alcuni nella pagina budget. I budget possono aiutarti a tenere traccia delle spese.', + 'Default asset account' => 'Conto attività predefinito', + 'no_budget_pointer' => 'Sembra che tu non abbia ancora dei budget. Dovresti crearne alcuni nella pagina dei budget. I budget possono aiutarti a tenere traccia delle spese.', 'Savings account' => 'Conti risparmio', 'Credit card' => 'Carta di Credito', - 'source_accounts' => 'Origine conto(i)', - 'destination_accounts' => 'Destinazione Conto(i)', + 'source_accounts' => 'Conti origine', + 'destination_accounts' => 'Conti destinazione', 'user_id_is' => 'Il tuo ID utente è :user', 'field_supports_markdown' => 'Questo campo supporta Markdown.', 'need_more_help' => 'Se hai bisogno di ulteriore aiuto con Firefly III, ti preghiamo di aprire un ticket su Github.', 'reenable_intro_text' => 'Puoi anche riattivare la guida introduttiva.', 'intro_boxes_after_refresh' => 'Le caselle di introduzione riappariranno quando si aggiorna la pagina.', 'show_all_no_filter' => 'Mostra tutte le transazioni senza raggrupparle per data.', - 'expenses_by_category' => 'Spese per Categorie', + 'expenses_by_category' => 'Spese per categorie', 'expenses_by_budget' => 'Spese per budget', - 'income_by_category' => 'Reddito per categoria', - 'expenses_by_asset_account' => 'Spese per attività conto', + 'income_by_category' => 'Entrate per categoria', + 'expenses_by_asset_account' => 'Spese per conto attività', 'expenses_by_expense_account' => 'Spese per conto spese', 'cannot_redirect_to_account' => 'Spiacente ma Firefly III non può reindirizzarti alla pagina corretta.', 'sum_of_expenses' => 'Totale spese', - 'sum_of_income' => 'Totale reddito', + 'sum_of_income' => 'Somma delle entrate', 'spent_in_specific_budget' => 'Speso nel budget ":budget"', 'sum_of_expenses_in_budget' => 'Spesa totale nel budget ":budget"', 'left_in_budget_limit' => 'Lasciato a spendere in base al budget', @@ -125,10 +125,10 @@ return [ 'multi_select_select_all' => 'Seleziona tutto', 'multi_select_n_selected' => 'selezionato', 'multi_select_all_selected' => 'Seleziona tutto', - 'multi_select_filter_placeholder' => 'Cerca..', + 'multi_select_filter_placeholder' => 'Cerca...', 'intro_next_label' => 'Avanti', 'intro_prev_label' => 'Indietro', - 'intro_skip_label' => 'Salta', + 'intro_skip_label' => 'Salta ogni', 'intro_done_label' => 'Fatto', 'between_dates_breadcrumb' => 'Fra :start e :end', 'all_journals_without_budget' => 'Tutte le transazioni senza un budget', @@ -147,19 +147,19 @@ return [ 'all_transfers' => 'Tutti i trasferimenti', 'title_transfers_between' => 'Tutti i trasferimenti fra :start e :end', 'all_transfer' => 'Tutti i trasferimenti', - 'all_journals_for_tag' => 'Tutte le transazioni per Etichetta ":tag"', + 'all_journals_for_tag' => 'Tutte le transazioni per l\'etichetta ":tag"', 'title_transfer_between' => 'Tutti i trasferimenti fra :start e :end', 'all_journals_for_category' => 'Turre le transazioni per categoria :name', 'all_journals_for_budget' => 'Tutte le transazione per budget :name', 'chart_all_journals_for_budget' => 'Grafico di tutte le transazioni per budget :name', 'journals_in_period_for_category' => 'Tutte le transazioni per Categoria :name fra :start e :end', - 'journals_in_period_for_tag' => 'Tutte le transazioni per Etichetta :tag fra :start e :end', + 'journals_in_period_for_tag' => 'Tutte le transazioni per l\'etichetta :tag fra :start e :end', 'not_available_demo_user' => 'La funzione a cui tenti di accedere non è disponibile per gli utenti demo.', - 'exchange_rate_instructions' => 'Conto attività "@name" accetta solo transazioni in @native_currency. Se invece desideri utilizzare @foreign_currency, assicurati che anche l\'importo in @native_currency sia noto:', - 'transfer_exchange_rate_instructions' => 'Il conto attività di origine "@source_name" accetta solo transazioni in @source_currency.Il conto attività di destinazione "@dest_name" accetta solo transazioni in @dest_currency. È necessario fornire l\'importo trasferito correttamente in entrambe le valute.', + 'exchange_rate_instructions' => 'Il conto attività "@name" accetta solo transazioni in @native_currency. Se invece desideri utilizzare @foreign_currency, assicurati che anche l\'importo in @native_currency sia noto:', + 'transfer_exchange_rate_instructions' => 'Il conto attività di origine "@source_name" accetta solo transazioni in @source_currency. Il conto attività di destinazione "@dest_name" accetta solo transazioni in @dest_currency. È necessario fornire l\'importo trasferito correttamente in entrambe le valute.', 'transaction_data' => 'Data Transazione', - 'invalid_server_configuration' => 'Configurazione Server non corretta', - 'invalid_locale_settings' => 'Firefly III non è in grado di formattare importi monetari perché al server mancano i pacchetti richiesti. Ci sono istruzioni su come eseguire questa operazione.', + 'invalid_server_configuration' => 'Configurazione del server non corretta', + 'invalid_locale_settings' => 'Firefly III non è in grado di formattare gli importi monetari perché al server mancano i pacchetti richiesti. Ci sono istruzioni su come eseguire questa operazione.', 'quickswitch' => 'Interruttore veloce', 'sign_in_to_start' => 'Accedi per iniziare la sessione', 'sign_in' => 'Accedi', @@ -189,14 +189,14 @@ return [ 'check_for_updates_permission' => 'Firefly III può controllare gli aggiornamenti, ma è necessario il tuo permesso per farlo. Vai alla amministrazione per indicare se desideri che questa funzione sia abilitata.', 'updates_ask_me_later' => 'Chiedimelo più tardi', 'updates_do_not_check' => 'Non controllare gli aggiornamenti', - 'updates_enable_check' => 'Abilita il controllo per gli aggiornamenti', + 'updates_enable_check' => 'Abilita il controllo degli aggiornamenti', 'admin_update_check_now_title' => 'Controlla gli aggiornamenti ora', - 'admin_update_check_now_explain' => 'Se si preme il pulsante, Firefly III vedrà se la versione corrente è la più recente.', + 'admin_update_check_now_explain' => 'Se si preme il pulsante, Firefly III controllerà se la versione corrente è la più recente.', 'check_for_updates_button' => 'Controlla ora!', 'update_new_version_alert' => 'È disponibile una nuova versione di Firefly III. Stai eseguendo v:your_version, l\'ultima versione è v:new_version che è stata rilasciata :date.', 'update_current_version_alert' => 'Stai eseguendo v:version, che è l\'ultima versione disponibile.', 'update_newer_version_alert' => 'Stai eseguendo v:your_version, che è più recente rispetto all\'ultima versione, v:new_version.', - 'update_check_error' => 'Si è verificato un errore durante il controllo degli aggiornamenti. Si prega di visualizzare i file di registro.', + 'update_check_error' => 'Si è verificato un errore durante il controllo degli aggiornamenti. Si prega di visualizzare i file di log.', // search 'search' => 'Cerca', @@ -206,15 +206,15 @@ return [ 'search_box' => 'Ricerca', 'search_box_intro' => 'Benvenuto nella funzione di ricerca di Firefly III. Inserisci la query di ricerca nella casella. Assicurati di controllare il file della guida perché la ricerca è abbastanza avanzata.', 'search_error' => 'Errore durante la ricerca', - 'search_searching' => 'Ricerca ...', + 'search_searching' => 'Ricerca in corso...', 'search_results' => 'Risultati ricerca', // repeat frequencies: - 'repeat_freq_yearly' => 'annuale', + 'repeat_freq_yearly' => 'annualmente', 'repeat_freq_half-year' => 'ogni sei mesi', - 'repeat_freq_quarterly' => 'trimestrale', - 'repeat_freq_monthly' => 'mensile', - 'repeat_freq_weekly' => 'settimanale', + 'repeat_freq_quarterly' => 'trimestralmente', + 'repeat_freq_monthly' => 'mensilmente', + 'repeat_freq_weekly' => 'settimanalmente', 'weekly' => 'settimanale', 'quarterly' => 'trimestrale', 'half-year' => 'ogni sei mesi', @@ -224,71 +224,71 @@ return [ 'import_and_export' => 'Importa e esporta', 'export_data' => 'Esporta dati', 'export_and_backup_data' => 'Esporta dati', - 'export_data_intro' => 'Utilizzare i dati esportati per passare a una nuova applicazione finanziaria. Si noti che questi file non sono intesi come backup. Non contengono abbastanza metadati per ripristinare completamente una nuova installazione di Firefly III. Se si desidera eseguire un backup dei dati, eseguire direttamente il backup del database.', - 'export_format' => 'Esporta formato', + 'export_data_intro' => 'Utilizza i dati esportati per passare a una nuova applicazione finanziaria. Si noti che questi file non sono intesi come backup. Non contengono abbastanza metadati per ripristinare completamente una nuova installazione di Firefly III. Se desideri eseguire un backup dei dati, esegui direttamente il backup del database.', + 'export_format' => 'Formato esportazione', 'export_format_csv' => 'Valori separati da virgola (file CSV)', 'export_format_mt940' => 'Formato compatibile MT940', 'include_old_uploads_help' => 'Firefly III non getta via i file CSV originali importati in passato. Puoi includerli nell\'esportazione.', 'do_export' => 'Esporta', 'export_status_never_started' => 'L\'esportazione non è ancora iniziata', - 'export_status_make_exporter' => 'Creare una cosa esportatore...', + 'export_status_make_exporter' => 'Creazione esportatore...', 'export_status_collecting_journals' => 'Raccolta delle tue transazioni...', 'export_status_collected_journals' => 'Raccolto le tue transazioni!', - 'export_status_converting_to_export_format' => 'Convertire le tue transazioni...', + 'export_status_converting_to_export_format' => 'Conversione delle tue transazioni...', 'export_status_converted_to_export_format' => 'Convertite le vostre transazioni!', - 'export_status_creating_journal_file' => 'Creare il file di esportazione...', - 'export_status_created_journal_file' => 'Creato il file di esportazione!', - 'export_status_collecting_attachments' => 'Raccogli tutti i tuoi allegati...', + 'export_status_creating_journal_file' => 'Creazione del file di esportazione...', + 'export_status_created_journal_file' => 'File di esportazione creato!', + 'export_status_collecting_attachments' => 'Raccolta di tutti i tuoi allegati...', 'export_status_collected_attachments' => 'Raccolti tutti i tuoi allegati!', - 'export_status_collecting_old_uploads' => 'Raccogli tutti i tuoi caricamenti precedenti...', + 'export_status_collecting_old_uploads' => 'Raccolta di tutti i tuoi caricamenti precedenti...', 'export_status_collected_old_uploads' => 'Raccolti tutti i tuoi caricamenti precedenti!', - 'export_status_creating_zip_file' => 'Creare un file zip...', + 'export_status_creating_zip_file' => 'Creazione di un file zip...', 'export_status_created_zip_file' => 'File zip creato!', - 'export_status_finished' => 'Esportazione terminata correttamente!!', + 'export_status_finished' => 'L\'esportazione è terminata con successo! Evviva!', 'export_data_please_wait' => 'Attendere prego...', // rules 'rules' => 'Regole', 'rule_name' => 'Nome regola', 'rule_triggers' => 'La regola si innesca quando', - 'rule_actions' => 'La regola lo farà', + 'rule_actions' => 'La regola eseguirà', 'new_rule' => 'Nuova regola', 'new_rule_group' => 'Nuovo gruppo di regole', - 'rule_priority_up' => 'Dare maggiore priorità alla regola', - 'rule_priority_down' => 'Dare alla regola meno priorità', + 'rule_priority_up' => 'Dai maggiore priorità alla regola', + 'rule_priority_down' => 'Dai minore priorità alla regola', 'make_new_rule_group' => 'Crea un nuovo gruppo di regole', 'store_new_rule_group' => 'Memorizza un nuovo gruppo di regole', 'created_new_rule_group' => 'Nuovo gruppo di regole ":title" memorizzate!', - 'updated_rule_group' => 'Gruppo di regole aggiornato con successo ":title".', + 'updated_rule_group' => 'Gruppo di regole ":title" aggiornato con successo.', 'edit_rule_group' => 'Modifica il gruppo di regole ":title"', 'delete_rule_group' => 'Elimina il gruppo di regole ":title"', 'deleted_rule_group' => 'Gruppo regole eliminato ":title"', 'update_rule_group' => 'Aggiorna gruppo di regole', 'no_rules_in_group' => 'Non ci sono regole in questo gruppo', - 'move_rule_group_up' => 'Sposta il gruppo di regole su', - 'move_rule_group_down' => 'Sposta il gruppo di regole in basso', - 'save_rules_by_moving' => 'Salva questa(e) regola(e) spostandola(e) in un altro gruppo di regole:', + 'move_rule_group_up' => 'Sposta sopra il gruppo di regole', + 'move_rule_group_down' => 'Sposta sotto il gruppo di regole', + 'save_rules_by_moving' => 'Salva queste regole spostandole in un altro gruppo di regole:', 'make_new_rule' => 'Crea una nuova regola nel gruppo di regole ":title"', 'rule_is_strict' => 'regola severa', 'rule_is_not_strict' => 'regola non severa', 'rule_help_stop_processing' => 'Quando selezioni questa casella, le regole successive in questo gruppo non verranno eseguite.', 'rule_help_strict' => 'Nelle regole severe TUTTI i trigger devono venire azionati perché l\'azione venga eseguita. Nelle regole non severe, è sufficiente UN QUALSIASI trigger perché l\'azione venga eseguita.', - 'rule_help_active' => 'Le regole non attive non spareranno mai.', + 'rule_help_active' => 'Le regole non attive non verranno mai eseguite.', 'stored_new_rule' => 'Nuova regola memorizzata con titolo ":title"', 'deleted_rule' => 'Regola eliminata con titolo ":title"', 'store_new_rule' => 'Salva nuova regola', 'updated_rule' => 'Regola aggiornata con titolo ":title"', 'default_rule_group_name' => 'Regole predefinite', - 'default_rule_group_description' => 'Tutte le tue regole non in un gruppo particolare.', + 'default_rule_group_description' => 'Tutte le tue regole che non sono in un gruppo specifico.', 'default_rule_name' => 'La tua prima regola predefinita', - 'default_rule_description' => 'Questa regola è un esempio. Puoi tranquillamente cancellarla.', + 'default_rule_description' => 'Questa regola è un esempio. Puoi tranquillamente eliminarla.', 'default_rule_trigger_description' => 'L\'uomo che vendette il mondo', 'default_rule_trigger_from_account' => 'David Bowie', 'default_rule_action_prepend' => 'Comprato il mondo da ', 'default_rule_action_set_category' => 'Grandi spese', 'trigger' => 'Trigger', 'trigger_value' => 'Attiva al valore', - 'stop_processing_other_triggers' => 'Interrompi l\'elaborazione di altri trigger', + 'stop_processing_other_triggers' => 'Smetti di elaborare altri trigger', 'add_rule_trigger' => 'Aggiungi un nuovo trigger', 'action' => 'Azione', 'action_value' => 'Valore azione', @@ -332,19 +332,19 @@ return [ 'rule_trigger_transaction_type' => 'La transazione è di tipo ":trigger_value"', 'rule_trigger_category_is_choice' => 'La categoria è...', 'rule_trigger_category_is' => 'La categoria è ":trigger_value"', - 'rule_trigger_amount_less_choice' => 'L\'importo è inferiore a..', + 'rule_trigger_amount_less_choice' => 'L\'importo è inferiore a...', 'rule_trigger_amount_less' => 'L\'importo è inferiore a :trigger_value', - 'rule_trigger_amount_exactly_choice' => 'L\'importo è..', + 'rule_trigger_amount_exactly_choice' => 'L\'importo è...', 'rule_trigger_amount_exactly' => 'L\'importo è :trigger_value', - 'rule_trigger_amount_more_choice' => 'L\'importo è più di..', + 'rule_trigger_amount_more_choice' => 'L\'importo è più di...', 'rule_trigger_amount_more' => 'L\'importo è più di :trigger_value', - 'rule_trigger_description_starts_choice' => 'La descrizione inizia con..', + 'rule_trigger_description_starts_choice' => 'La descrizione inizia con...', 'rule_trigger_description_starts' => 'La descrizione inizia con ":trigger_value"', - 'rule_trigger_description_ends_choice' => 'La descrizione termina con..', + 'rule_trigger_description_ends_choice' => 'La descrizione termina con...', 'rule_trigger_description_ends' => 'La descrizione termina con ":trigger_value"', - 'rule_trigger_description_contains_choice' => 'La descrizione contiene..', + 'rule_trigger_description_contains_choice' => 'La descrizione contiene...', 'rule_trigger_description_contains' => 'La descrizione contiene ":trigger_value"', - 'rule_trigger_description_is_choice' => 'La descrizione è..', + 'rule_trigger_description_is_choice' => 'La descrizione è...', 'rule_trigger_description_is' => 'La descrizione è ":trigger_value"', 'rule_trigger_budget_is_choice' => 'Il budget è...', 'rule_trigger_budget_is' => 'Il budget è ":trigger_value"', @@ -352,8 +352,8 @@ return [ 'rule_trigger_tag_is' => 'Una etichetta è ":trigger_value"', 'rule_trigger_currency_is_choice' => 'La valuta della transazione è...', 'rule_trigger_currency_is' => 'La valuta della transazione è ":trigger_value"', - 'rule_trigger_has_attachments_choice' => 'Ha almeno questo molti allegati', - 'rule_trigger_has_attachments' => 'Almeno :trigger_value allegato (i)', + 'rule_trigger_has_attachments_choice' => 'Ha almeno così tanti allegati', + 'rule_trigger_has_attachments' => 'Ha almeno :trigger_value allegati', 'rule_trigger_store_journal' => 'Quando viene creata una transazione', 'rule_trigger_update_journal' => 'Quando una transazione viene aggiornata', 'rule_trigger_has_no_category_choice' => 'Non ha categoria', @@ -364,53 +364,53 @@ return [ 'rule_trigger_has_no_budget' => 'La transazione non ha un budget', 'rule_trigger_has_any_budget_choice' => 'Ha un (qualsiasi) budget', 'rule_trigger_has_any_budget' => 'La transazione ha un (qualsiasi) budget', - 'rule_trigger_has_no_tag_choice' => 'Non ha etichetta(e)', - 'rule_trigger_has_no_tag' => 'La transazione non ha etichetta(e)', + 'rule_trigger_has_no_tag_choice' => 'Non ha etichette', + 'rule_trigger_has_no_tag' => 'La transazione non ha etichette', 'rule_trigger_has_any_tag_choice' => 'Ha una o più etichette (qualsiasi)', - 'rule_trigger_has_any_tag' => 'La transazione ha una (qualsiasi) o più etichette', - 'rule_trigger_any_notes_choice' => 'Ha (qualsiasi) note', - 'rule_trigger_any_notes' => 'La transazione ha (qualsiasi) note', + 'rule_trigger_has_any_tag' => 'La transazione ha una o più etichette (qualsiasi)', + 'rule_trigger_any_notes_choice' => 'Ha una (qualsiasi) nota', + 'rule_trigger_any_notes' => 'La transazione ha una (qualsiasi) nota', 'rule_trigger_no_notes_choice' => 'Non ha note', 'rule_trigger_no_notes' => 'La transazione non ha note', - 'rule_trigger_notes_are_choice' => 'Le note sono..', + 'rule_trigger_notes_are_choice' => 'Le note sono...', 'rule_trigger_notes_are' => 'Le note sono ":trigger_value"', - 'rule_trigger_notes_contain_choice' => 'Le note contengono..', + 'rule_trigger_notes_contain_choice' => 'Le note contengono...', 'rule_trigger_notes_contain' => 'Le note contengono ":trigger_value"', - 'rule_trigger_notes_start_choice' => 'Le note iniziano con..', + 'rule_trigger_notes_start_choice' => 'Le note iniziano con...', 'rule_trigger_notes_start' => 'Le note iniziano con ":trigger_value"', - 'rule_trigger_notes_end_choice' => 'Le note finiscono con..', + 'rule_trigger_notes_end_choice' => 'Le note finiscono con...', 'rule_trigger_notes_end' => 'Le note finiscono con ":trigger_value"', 'rule_action_set_category' => 'Imposta categoria a ":action_value"', - 'rule_action_clear_category' => 'Cancella categoria', + 'rule_action_clear_category' => 'Rimuovi dalla categoria', 'rule_action_set_budget' => 'Imposta il budget su ":action_value"', - 'rule_action_clear_budget' => 'Cancella budget', + 'rule_action_clear_budget' => 'Rimuovi dal budget', 'rule_action_add_tag' => 'Aggiungi etichetta ":action_value"', - 'rule_action_remove_tag' => 'Rimuovi etichetta ":action_value"', + 'rule_action_remove_tag' => 'Rimuovi l\'etichetta ":action_value"', 'rule_action_remove_all_tags' => 'Rimuovi tutte le etichette', 'rule_action_set_description' => 'Imposta la descrizione a ":action_value"', - 'rule_action_append_description' => 'Aggiungi descrizione con ":action_value"', - 'rule_action_prepend_description' => 'Anteporre descrizione con ":action_value"', - 'rule_action_set_category_choice' => 'Imposta categoria a..', - 'rule_action_clear_category_choice' => 'Cancella qualsiasi categoria', + 'rule_action_append_description' => 'Aggiungi alla descrizione ":action_value"', + 'rule_action_prepend_description' => 'Anteponi alla descrizione ":action_value"', + 'rule_action_set_category_choice' => 'Imposta come categoria...', + 'rule_action_clear_category_choice' => 'Rimuovi da tutte le categorie', 'rule_action_set_budget_choice' => 'Imposta il budget su...', - 'rule_action_clear_budget_choice' => 'Cancella qualsiasi budget', - 'rule_action_add_tag_choice' => 'Aggiungi etichetta..', - 'rule_action_remove_tag_choice' => 'Rimuovi etichetta..', + 'rule_action_clear_budget_choice' => 'Rimuovi da tutti i budget', + 'rule_action_add_tag_choice' => 'Aggiungi l\'etichetta...', + 'rule_action_remove_tag_choice' => 'Rimuovi l\'etichetta...', 'rule_action_remove_all_tags_choice' => 'Rimuovi tutte le etichette', - 'rule_action_set_description_choice' => 'Imposta la descrizione a..', - 'rule_action_append_description_choice' => 'Aggiungi descrizione con..', - 'rule_action_prepend_description_choice' => 'Anteporre descrizione..', - 'rule_action_set_source_account_choice' => 'Imposta l\'account di origine su...', - 'rule_action_set_source_account' => 'Imposta l\'account di origine su :action_value', - 'rule_action_set_destination_account_choice' => 'Imposta l\'account di destinazione su...', - 'rule_action_set_destination_account' => 'Imposta l\'account di destinazione su :action_value', - 'rule_action_append_notes_choice' => 'Aggiungi note con..', - 'rule_action_append_notes' => 'Aggiungi note con ":action_value"', - 'rule_action_prepend_notes_choice' => 'Anteponi note con..', - 'rule_action_prepend_notes' => 'Anteponi note con ":action_value"', - 'rule_action_clear_notes_choice' => 'Rimuovi eventuali note', - 'rule_action_clear_notes' => 'Rimuovi eventuali note', - 'rule_action_set_notes_choice' => 'Imposta le note su..', + 'rule_action_set_description_choice' => 'Imposta come descrizione...', + 'rule_action_append_description_choice' => 'Aggiungi alla descrizione...', + 'rule_action_prepend_description_choice' => 'Anteponi alla descrizione...', + 'rule_action_set_source_account_choice' => 'Imposta come conto di origine...', + 'rule_action_set_source_account' => 'Imposta come conto di origine :action_value', + 'rule_action_set_destination_account_choice' => 'Imposta come conto di destinazione...', + 'rule_action_set_destination_account' => 'Imposta come conto di destinazione :action_value', + 'rule_action_append_notes_choice' => 'Aggiungi alle note...', + 'rule_action_append_notes' => 'Aggiungi alle note ":action_value"', + 'rule_action_prepend_notes_choice' => 'Anteponi alle note...', + 'rule_action_prepend_notes' => 'Anteponi alle note ":action_value"', + 'rule_action_clear_notes_choice' => 'Rimuovi tutte le note', + 'rule_action_clear_notes' => 'Rimuovi tutte le note', + 'rule_action_set_notes_choice' => 'Imposta come note...', 'rule_action_link_to_bill_choice' => 'Collega ad una bolletta...', 'rule_action_link_to_bill' => 'Collegamento alla bolletta ":action_value"', 'rule_action_set_notes' => 'Imposta le note su ":action_value"', @@ -422,7 +422,7 @@ return [ 'rule_for_bill_title' => 'Regole generata automaticamente per la bolletta ":name"', 'rule_for_bill_description' => 'Questa regola è generata automaticamente per l\'abbinamento con la bolletta ":name".', 'create_rule_for_bill' => 'Crea una nuova regola per la bolletta ":name"', - 'create_rule_for_bill_txt' => 'Contratulazioni, hai appena creato una nuova bolletta chiamata ":name"! Firefly III può automagicamente abbinare le nuove uscite a questa bolletta. Per esempio, ogni volta che paghi l\'affitto la bolletta "affitto" verrà collegata a questa spesa. In questo modo Firefly III può visualizzare con accuratezza quali bollette sono in scadenza e quali no. Per far ciò è necessario creare una nuova regola. Firefly III ha inserito al posto tuo alcuni dettagli ragionevoli. Assicurati che questi siano corretti. Se questi valori sono corretti, Firefly III automaticamente collegherà il prelievo giusto alla bolletta giusta. Controlla che i trigger siano corretti e aggiungene altri se sono sbagliati.', + 'create_rule_for_bill_txt' => 'Congratulazioni, hai appena creato una nuova bolletta chiamata ":name"! Firefly III può automagicamente abbinare le nuove uscite a questa bolletta. Per esempio, ogni volta che paghi l\'affitto la bolletta "affitto" verrà collegata a questa spesa. In questo modo Firefly III può visualizzare con accuratezza quali bollette sono in scadenza e quali no. Per far ciò è necessario creare una nuova regola. Firefly III ha inserito al posto tuo alcuni dettagli ragionevoli. Assicurati che questi siano corretti. Se questi valori sono corretti, Firefly III automaticamente collegherà il prelievo giusto alla bolletta giusta. Controlla che i trigger siano corretti e aggiungine altri se sono sbagliati.', 'new_rule_for_bill_title' => 'Regola per la bolletta ":name"', 'new_rule_for_bill_description' => 'Questa regola contrassegna le transazioni per la bolletta ":name".', @@ -437,16 +437,16 @@ return [ 'sums_apply_to_range' => 'Tutte le somme si applicano all\'intervallo selezionato', 'mapbox_api_key' => 'Per utilizzare la mappa, ottieni una chiave API da Mapbox. Apri il tuo file .env e inserisci questo codice dopo MAPBOX_API_KEY=.', 'press_tag_location' => 'Fai clic destro o premi a lungo per impostare la posizione dell\'erichetta.', - 'clear_location' => 'Cancella posizione', + 'clear_location' => 'Rimuovi dalla posizione', // preferences - 'pref_home_screen_accounts' => 'Conti nella schermata iniziale', - 'pref_home_screen_accounts_help' => 'Quali conti dovrebbero essere visualizzati sulla home page?', - 'pref_view_range' => 'Visualizza gamma', + 'pref_home_screen_accounts' => 'Conti nella pagina iniziale', + 'pref_home_screen_accounts_help' => 'Quali conti vuoi che vengano visualizzati nella pagina principale?', + 'pref_view_range' => 'Intervallo di visualizzazione', 'pref_view_range_help' => 'Alcuni grafici sono raggruppati automaticamente in periodi. Che periodo preferiresti?', - 'pref_1D' => '1 Giorno', - 'pref_1W' => '1 Settimana', - 'pref_1M' => '1 Mese', + 'pref_1D' => 'Un giorno', + 'pref_1W' => 'Una settimana', + 'pref_1M' => 'Un mese', 'pref_3M' => 'Tre mesi (trimestre)', 'pref_6M' => 'Sei mesi', 'pref_1Y' => 'Un anno', @@ -454,10 +454,10 @@ return [ 'pref_languages_help' => 'Firefly III supporta diverse lingue.', 'pref_custom_fiscal_year' => 'Impostazioni anno fiscale', 'pref_custom_fiscal_year_label' => 'Abilita', - 'pref_custom_fiscal_year_help' => 'Nei paesi che utilizzano un anno finanziario diverso dal 1 ° gennaio al 31 dicembre, è possibile attivarlo e specificare i giorni di inizio / fine anno fiscale', + 'pref_custom_fiscal_year_help' => 'Nei paesi che utilizzano un anno finanziario diverso rispetto al dal 1 gennaio al 31 dicembre, è possibile attivarlo e specificare i giorni di inizio / fine dell\'anno fiscale', 'pref_fiscal_year_start_label' => 'Data di inizio anno fiscale', 'pref_two_factor_auth' => 'Verifica in due passaggi', - 'pref_two_factor_auth_help' => 'Quando abiliti la verifica in due passaggi (nota anche come autenticazione a due fattori), aggiungi un ulteriore livello di sicurezza al tuo account. Accedi con qualcosa che conosci (la tua password) e qualcosa che hai (un codice di verifica). I codici di verifica sono generati da un\'applicazione sul telefono, come Authy o Autenticatore Google.', + 'pref_two_factor_auth_help' => 'Quando abiliti la verifica a due passaggi (nota anche come autenticazione a due fattori), aggiungi un ulteriore livello di sicurezza al tuo account. Accedi con qualcosa che conosci (la tua password) e qualcosa che hai (un codice di verifica). I codici di verifica sono generati da un\'applicazione sul telefono, come Authy o Google Authenticator.', 'pref_enable_two_factor_auth' => 'Abilita la verifica in due passaggi', 'pref_two_factor_auth_disabled' => 'Codice di verifica in due passaggi rimosso e disabilitato', 'pref_two_factor_auth_remove_it' => 'Non dimenticare di rimuovere l\'account dalla tua app di autenticazione!', @@ -465,18 +465,19 @@ return [ 'pref_two_factor_auth_code_help' => 'Esegui la scansione del codice QR con un\'applicazione sul tuo telefono come Authy o Google Authenticator e inserisci il codice generato.', 'pref_two_factor_auth_reset_code' => 'Reimposta il codice di verifica', 'pref_two_factor_auth_disable_2fa' => 'Disattiva 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Salva le impostazioni', 'saved_preferences' => 'Preferenze salvate!', 'preferences_general' => 'Generale', - 'preferences_frontpage' => 'Schermata Home', + 'preferences_frontpage' => 'Pagina principale', 'preferences_security' => 'Sicurezza', 'preferences_layout' => 'Impaginazione', - 'pref_home_show_deposits' => 'Mostra i depositi sulla schermata principale', - 'pref_home_show_deposits_info' => 'La schermata iniziale mostra già i tuoi conti spese. Dovrebbe mostrare anche i tuoi account delle entrate?', + 'pref_home_show_deposits' => 'Mostra i depositi nella pagina principale', + 'pref_home_show_deposits_info' => 'La pagina iniziale mostra già i tuoi conti spese. Vuoi che mostri anche i tuoi conti entrate?', 'pref_home_do_show_deposits' => 'Sì, mostrali', 'successful_count' => 'di cui :count con successo', 'list_page_size_title' => 'Dimensioni pagina', - 'list_page_size_help' => 'Qualsiasi elenco di cose (conti, transazioni, ecc.) Mostra al massimo questo numero per pagina.', + 'list_page_size_help' => 'Ogni elenco (di conti, di transazioni, ecc) mostra al massimo questo numero di elementi per pagina.', 'list_page_size_label' => 'Dimensioni pagina', 'between_dates' => '(:start e :end)', 'pref_optional_fields_transaction' => 'Campi opzionali per le transazioni', @@ -484,12 +485,12 @@ return [ 'optional_tj_date_fields' => 'Campi data', 'optional_tj_business_fields' => 'Campi aziendali', 'optional_tj_attachment_fields' => 'Campi allegati', - 'pref_optional_tj_interest_date' => 'Data di interesse', - 'pref_optional_tj_book_date' => 'Data del libro', - 'pref_optional_tj_process_date' => 'Data di lavorazione', - 'pref_optional_tj_due_date' => 'Scadenza', - 'pref_optional_tj_payment_date' => 'Data di pagamento', - 'pref_optional_tj_invoice_date' => 'Data bolletta', + 'pref_optional_tj_interest_date' => 'Data interessi', + 'pref_optional_tj_book_date' => 'Data contabile', + 'pref_optional_tj_process_date' => 'Data elaborazione', + 'pref_optional_tj_due_date' => 'Data scadenza', + 'pref_optional_tj_payment_date' => 'Data pagamento', + 'pref_optional_tj_invoice_date' => 'Data fatturazione', 'pref_optional_tj_internal_reference' => 'Riferimento interno', 'pref_optional_tj_notes' => 'Note', 'pref_optional_tj_attachments' => 'Allegati', @@ -503,9 +504,9 @@ return [ 'delete_account' => 'Elimina account', 'current_password' => 'Password corrente', 'new_password' => 'Nuova password', - 'new_password_again' => 'Nuova password (ancora)', - 'delete_your_account' => 'cancella il tuo account', - 'delete_your_account_help' => 'L\'eliminazione del tuo account eliminerà anche conti, transazioni, qualsiasi cosa che potresti aver salvato in Firefly III. Sarà ELIMINATO.', + 'new_password_again' => 'Nuova password (ripeti)', + 'delete_your_account' => 'Elimina il tuo account', + 'delete_your_account_help' => 'L\'eliminazione del tuo account eliminerà anche conti, transazioni, qualsiasi cosa che potresti aver salvato in Firefly III. Sarà tutto PERDUTO.', 'delete_your_account_password' => 'Inserisci la tua password per continuare.', 'password' => 'Password', 'are_you_sure' => 'Sei sicuro? Non puoi annullare questo.', @@ -516,20 +517,20 @@ return [ 'invalid_password' => 'Password non valida!', 'what_is_pw_security' => 'Che cos\'è "verifica la sicurezza della password"?', 'secure_pw_title' => 'Come scegliere una password sicura', - 'secure_pw_history' => 'Nell\'agosto 2017, il noto ricercatore di sicurezza Troy Hunt ha pubblicato una lista di 306 milioni di password rubate. Queste password sono state rubate durante i break in aziende come LinkedIn, Adobe e NeoPets (e molte altre).', + 'secure_pw_history' => 'Nell\'agosto 2017 il noto ricercatore di sicurezza Troy Hunt ha pubblicato una lista di 306 milioni di password rubate. Queste password sono state rubate durante incursioni in aziende come LinkedIn, Adobe e NeoPets (e molte altre).', 'secure_pw_check_box' => 'Selezionando la casella, Firefly III invierà l\'hash SHA1 della tua password a il sito web di Troy Hunt per vedere se è presente nell\'elenco. Questo ti impedirà di usare password non sicure come raccomandato nell\'ultima Pubblicazione speciale NIST su questo argomento.', 'secure_pw_sha1' => 'Ma pensavo che SHA1 fosse rotto?', 'secure_pw_hash_speed' => 'Sì, ma non in questo contesto. Come puoi leggere su il sito web che spiega come hanno rotto SHA1 , ora è leggermente più facile trovare una "collisione": un\'altra stringa che risulta nello stesso hash SHA1. Ora ci vogliono solo 10.000 anni usando una macchina a GPU singola.', - 'secure_pw_hash_security' => 'Questa collisione non sarebbe uguale alla tua password, né sarebbe utile su (un sito come) Firefly III. Questa applicazione non utilizza SHA1 per la verifica della password. Quindi è sicuro controllare questa casella. La tua password viene sottoposta a hash e inviata tramite HTTPS.', + 'secure_pw_hash_security' => 'Questa collisione non sarebbe uguale alla tua password, né sarebbe utile su (un sito come) Firefly III. Questa applicazione non utilizza SHA1 per la verifica della password. Quindi è sicuro selezionare questa casella. La tua password viene sottoposta a hash e solo i primi cinque caratteri di questo hash vengono inviati tramite HTTPS.', 'secure_pw_should' => 'Devo controllare la scatola?', 'secure_pw_long_password' => 'Se hai appena generato una password lunga e monouso per Firefly III utilizzando un qualche tipo di generatore di password: no.', 'secure_pw_short' => 'Se hai appena inserito la password, usi sempre: Si prega di.', 'command_line_token' => 'Token della riga di comando', - 'explain_command_line_token' => 'È necessario questo token per eseguire le opzioni della riga di comando, come l\'importazione o l\'esportazione di dati. Senza di esso, tali comandi sensibili non funzioneranno. Non condividere il token della riga di comando. Nessuno ti chiederà questo segno, nemmeno io. Se temi di aver perso questo, o quando sei insicuro, rigenera questo token usando il pulsante.', + 'explain_command_line_token' => 'È necessario questo token per eseguire le opzioni dalla riga di comando, come l\'importazione o l\'esportazione di dati. Senza di esso tali comandi sensibili non funzioneranno. Non condividere il token della riga di comando. Nessuno ti chiederà questo token, nemmeno io. Se temi di averlo perso, o se sei paranoico, rigenera questo token usando il pulsante.', 'regenerate_command_line_token' => 'Rigenera il token della riga di comando', 'token_regenerated' => 'È stato generato un nuovo token della riga di comando', 'change_your_email' => 'Cambia il tuo indirizzo email', - 'email_verification' => 'Un messaggio di posta elettronica verrà inviato al tuo vecchio e nuovo indirizzo email. Per motivi di sicurezza, non potrai accedere fino a quando non avrai verificato il tuo nuovo indirizzo email. Se non si è sicuri che l\'installazione di Firefly III sia in grado di inviare e-mail, si prega di non utilizzare questa funzione. Se sei un amministratore, puoi verificarlo nella amministrazione.', + 'email_verification' => 'Un messaggio di posta elettronica verrà inviato al vecchio E al nuovo indirizzo email. Per motivi di sicurezza, non potrai accedere fino a quando non avrai verificato il tuo nuovo indirizzo email. Se non si è sicuri che l\'installazione di Firefly III sia in grado di inviare e-mail, si prega di non utilizzare questa funzione. Se sei un amministratore, puoi verificarlo in Amministrazione.', 'email_changed_logout' => 'Fino a quando non verifichi il tuo indirizzo email, non puoi effettuare il login.', 'login_with_new_email' => 'Ora puoi accedere con il tuo nuovo indirizzo email.', 'login_with_old_email' => 'Ora puoi accedere nuovamente con il tuo vecchio indirizzo email.', @@ -541,15 +542,15 @@ return [ 'update_attachment' => 'Aggiorna allegati', 'delete_attachment' => 'Elimina allegato ":name"', 'attachment_deleted' => 'Allegato eliminato ":name"', - 'attachment_updated' => 'Allegato aggiornato ":name"', + 'attachment_updated' => 'Allegato ":name" aggiornato', 'upload_max_file_size' => 'Dimensione massima del file: :size', 'list_all_attachments' => 'Lista di tutti gli allegati', // transaction index 'title_expenses' => 'Spese', 'title_withdrawal' => 'Spese', - 'title_revenue' => 'Reddito / Entrata', - 'title_deposit' => 'Reddito / Entrata', + 'title_revenue' => 'Entrate', + 'title_deposit' => 'Redditi / entrate', 'title_transfer' => 'Trasferimenti', 'title_transfers' => 'Trasferimenti', @@ -573,19 +574,19 @@ return [ 'convert_Transfer_to_deposit' => 'Converti questo trasferimento in un deposito', 'convert_Transfer_to_withdrawal' => 'Converti questo trasferimento in un prelievo', 'convert_please_set_revenue_source' => 'Si prega di scegliere il conto delle entrate da dove verranno i soldi.', - 'convert_please_set_asset_destination' => 'Si prega di scegliere il conto patrimoniale dove andranno i soldi.', + 'convert_please_set_asset_destination' => 'Scegli il conto attività in cui andranno i soldi.', 'convert_please_set_expense_destination' => 'Si prega di scegliere il conto spese dove andranno i soldi.', - 'convert_please_set_asset_source' => 'Si prega di scegliere il conto patrimoniale da dove verranno i soldi.', + 'convert_please_set_asset_source' => 'Scegli il conto attività da cui verranno i soldi.', 'convert_explanation_withdrawal_deposit' => 'Se converti questo prelievo in un deposito, l\'importo verrà :amount depositato in :sourceName anziché prelevato da esso.', 'convert_explanation_withdrawal_transfer' => 'Se converti questo prelievo in un trasferimento, l\'importo verrà :amount trasferito da :sourceName a un nuovo conto attività, invece di essere pagato a :destinationName.', 'convert_explanation_deposit_withdrawal' => 'Se converti questo deposito in un prelievo, l\'importo verrà rimosso :amount da :destinationName anziché aggiunto ad esso.', 'convert_explanation_deposit_transfer' => 'Se converti questo deposito in un trasferimento, :amount verrà trasferito da un conto attivo di tua scelta in :destinationName.', 'convert_explanation_transfer_withdrawal' => 'Se converti questo trasferimento in un prelievo, l\'importo :amount andrà da :sourceName a una nuova destinazione a titolo di spesa, anziché a :destinationName come trasferimento.', - 'convert_explanation_transfer_deposit' => 'Se converti questo trasferimento in un deposito, :amount verrà depositato nell\'account :destinationName anziché essere trasferito lì.', + 'convert_explanation_transfer_deposit' => 'Se converti questo trasferimento in un deposito, :amount verranno depositati nel conto :destinationName anziché esservi trasferiti.', 'converted_to_Withdrawal' => 'La transazione è stata convertita in un prelievo', 'converted_to_Deposit' => 'La transazione è stata convertita in un deposito', 'converted_to_Transfer' => 'La transazione è stata convertita in un trasferimento', - 'invalid_convert_selection' => 'Tl\'account che hai selezionato è già utilizzato in questa transazione o non esiste.', + 'invalid_convert_selection' => 'Il conto che hai selezionato è già utilizzato in questa transazione o non esiste.', 'source_or_dest_invalid' => 'Impossibile trovare i dettagli corretti della transazione. Non è possibile effettuare la conversione.', // create new stuff: @@ -594,7 +595,7 @@ return [ 'create_new_transfer' => 'Crea nuovo trasferimento', 'create_new_asset' => 'Crea un nuovo conto attività', 'create_new_expense' => 'Crea un nuovo conto di spesa', - 'create_new_revenue' => 'Crea un nuovo conto di entrate', + 'create_new_revenue' => 'Crea un nuovo conto entrate', 'create_new_piggy_bank' => 'Crea un nuovo salvadanaio', 'create_new_bill' => 'Crea una nuova bolletta', @@ -609,8 +610,8 @@ return [ 'updated_currency' => 'Valuta :name aggiornata', 'ask_site_owner' => 'Chiedi a :owner di aggiungere, rimuovere o modificare valute.', 'currencies_intro' => 'Firefly III supporta varie valute che è possibile impostare e abilitare qui.', - 'make_default_currency' => 'rendere predefinito', - 'default_currency' => 'predefinito', + 'make_default_currency' => 'rendi predefinita', + 'default_currency' => 'predefinita', // forms: 'mandatoryFields' => 'Campi obbligatori', @@ -631,11 +632,11 @@ return [ 'delete_budget' => 'Elimina budget ":name"', 'deleted_budget' => 'Budget eliminato ":name"', 'edit_budget' => 'Modifica budget ":name"', - 'updated_budget' => 'Budget aggiornato ":name"', - 'update_amount' => 'Importo aggiornato', + 'updated_budget' => 'Budget ":name" aggiornato', + 'update_amount' => 'Aggiorna importo', 'update_budget' => 'Budget aggiornato', - 'update_budget_amount_range' => 'Aggiornamento (previsto) importo disponibile tra :start and :end', - 'budget_period_navigator' => 'Navigatore periodico', + 'update_budget_amount_range' => 'Aggiorna l\'importo disponibile (previsto) tra il :start e il :end', + 'budget_period_navigator' => 'Navigatore dei periodi', 'info_on_available_amount' => 'Cosa ho a disposizione?', 'available_amount_indication' => 'Utilizza questi importi per ottenere un\'indicazione di quale potrebbe essere il tuo budget totale.', 'suggested' => 'Consigliato', @@ -645,11 +646,11 @@ return [ // bills: 'match_between_amounts' => 'La bolletta abbina le transazioni tra :low e :high.', 'bill_related_rules' => 'Regole relative a questa bolletta', - 'repeats' => 'Ripeti', + 'repeats' => 'Si ripete', 'connected_journals' => 'Transazioni connesse', 'auto_match_on' => 'Abbinato automaticamente da Firefly III', 'auto_match_off' => 'Non abbinato automaticamente a Firefly III', - 'next_expected_match' => 'Prossima partita prevista', + 'next_expected_match' => 'Prossimo abbinamento previsto', 'delete_bill' => 'Elimina bolletta ":name"', 'deleted_bill' => 'Bolletta eliminata ":name"', 'edit_bill' => 'Modifica bolletta ":name"', @@ -673,8 +674,8 @@ return [ // accounts: 'details_for_asset' => 'Dettagli per conto attività ":name"', 'details_for_expense' => 'Dettagli per conto spese ":name"', - 'details_for_revenue' => 'Dettagli per conto delle entrate ":name"', - 'details_for_cash' => 'Dettagli per conto in contanti ":name"', + 'details_for_revenue' => 'Dettagli per conto entrate ":name"', + 'details_for_cash' => 'Dettagli per il conto contanti ":name"', 'store_new_asset_account' => 'Salva nuovo conto attività', 'store_new_expense_account' => 'Salva il nuovo conto spese', 'store_new_revenue_account' => 'Salva il nuovo conto entrate', @@ -684,31 +685,31 @@ return [ 'delete_asset_account' => 'Elimina conto attività ":name"', 'delete_expense_account' => 'Elimina conto spese ":name"', 'delete_revenue_account' => 'Elimina conto entrate ":name"', - 'asset_deleted' => 'Conto attività eliminato correttamente ":name"', - 'expense_deleted' => 'Conto spese eliminato correttamente ":name"', - 'revenue_deleted' => 'Conto entrate eliminato correttamente ":name"', + 'asset_deleted' => 'Conto attività ":name" eliminato correttamente', + 'expense_deleted' => 'Conto spese ":name" eliminato correttamente', + 'revenue_deleted' => 'Conto entrate ":name" eliminato correttamente', 'update_asset_account' => 'Aggiorna conto attività', 'update_expense_account' => 'Aggiorna conto spese', 'update_revenue_account' => 'Aggiorna conto entrate', 'make_new_asset_account' => 'Crea un nuovo conto attività', 'make_new_expense_account' => 'Crea un nuovo conto spesa', 'make_new_revenue_account' => 'Crea nuovo conto entrate', - 'asset_accounts' => 'Conti patrimoniali', + 'asset_accounts' => 'Conti attività', 'expense_accounts' => 'Conti spese', 'revenue_accounts' => 'Conti entrate', - 'cash_accounts' => 'Conti cassa', - 'Cash account' => 'Conto cassa', + 'cash_accounts' => 'Conti contanti', + 'Cash account' => 'Conto contanti', 'reconcile_account' => 'Riconciliazione conto ":account"', 'delete_reconciliation' => 'Elimina riconciliazione', 'update_reconciliation' => 'Aggiorna riconciliazione', 'amount_cannot_be_zero' => 'L\'importo non può essere zero', 'end_of_reconcile_period' => 'Fine periodo riconciliazione: :period', 'start_of_reconcile_period' => 'Inizio periodo riconciliazione: :period', - 'start_balance' => 'Saldo inizio', - 'end_balance' => 'Saldo fine', + 'start_balance' => 'Saldo iniziale', + 'end_balance' => 'Saldo finale', 'update_balance_dates_instruction' => 'Abbina gli importi e le date sopra al tuo estratto conto e premi "Inizia la riconciliazione"', 'select_transactions_instruction' => 'Seleziona le transazioni che appaiono sul tuo estratto conto.', - 'select_range_and_balance' => 'Innanzitutto verifica l\'intervallo di date e i saldi. Quindi premere "Inizia riconciliazione"', + 'select_range_and_balance' => 'Innanzitutto verifica l\'intervallo di date e i saldi. Quindi premi "Inizia riconciliazione"', 'date_change_instruction' => 'Se cambi ora l\'intervallo di date, qualsiasi progresso andrà perso.', 'update_selection' => 'Aggiorna selezione', 'store_reconcile' => 'Memorizza la riconciliazione', @@ -718,9 +719,9 @@ return [ 'reconcile_options' => 'Opzioni di riconciliazione', 'reconcile_range' => 'Intervallo di riconciliazione', 'start_reconcile' => 'Avvia la riconciliazione', - 'cash' => 'cassa', + 'cash' => 'contanti', 'account_type' => 'Tipo conto', - 'save_transactions_by_moving' => 'Salva questa(e) transazione(i) spostandola(e) in un altro conto:', + 'save_transactions_by_moving' => 'Salva queste transazioni spostandole in un altro conto:', 'stored_new_account' => 'Nuovo conto ":name" stored!', 'updated_account' => 'Aggiorna conto ":name"', 'credit_card_options' => 'Opzioni Carta di Credito', @@ -734,19 +735,19 @@ return [ 'reconcile_has_more' => 'Il tuo conto Firefly III ha più denaro irispetto a quanto afferma la tua banca. Ci sono diverse opzioni Si prega di scegliere cosa fare. Quindi, premere "Conferma riconciliazione".', 'reconcile_has_less' => 'Il tuo conto Firefly III ha meno denaro rispetto a quanto afferma la tua banca. Ci sono diverse opzioni Si prega di scegliere cosa fare. Quindi, premi "Conferma riconciliazione".', 'reconcile_is_equal' => 'La tua contabilità di Firefly III e le tue coordinate bancarie corrispondono. Non c\'è niente da fare. Si prega di premere "Conferma riconciliazione" per confermare l\'inserimento.', - 'create_pos_reconcile_transaction' => 'Cancella le transazioni selezionate e crea una correzione aggiungendo :amount a questo conto attività.', - 'create_neg_reconcile_transaction' => 'Cancella le transazioni selezionate e crea una correzione rimuovendo :amount da questo conto attività.', - 'reconcile_do_nothing' => 'Cancella le transazioni selezionate, ma non correggere.', + 'create_pos_reconcile_transaction' => 'Concilia le transazioni selezionate e crea una correzione aggiungendo :amount a questo conto attività.', + 'create_neg_reconcile_transaction' => 'Concilia le transazioni selezionate e crea una correzione rimuovendo :amount da questo conto attività.', + 'reconcile_do_nothing' => 'Concilia le transazioni selezionate, ma non correggere.', 'reconcile_go_back' => 'Puoi sempre modificare o eliminare una correzione in un secondo momento.', 'must_be_asset_account' => 'È possibile riconciliare solo i conti attività', 'reconciliation_stored' => 'Riconciliazione memorizzata', 'reconcilliation_transaction_title' => 'Riconciliazione (:from a :to)', 'reconcile_this_account' => 'Riconcilia questo conto', 'confirm_reconciliation' => 'Conferma riconciliazione', - 'submitted_start_balance' => 'Bilancio di partenza presentato', + 'submitted_start_balance' => 'Saldo iniziale presentato', 'selected_transactions' => 'Transazioni selezionate (:count)', - 'already_cleared_transactions' => 'Transazioni selezionate (:count)', - 'submitted_end_balance' => 'Bilancio finale inviato', + 'already_cleared_transactions' => 'Transazioni già conciliate (:count)', + 'submitted_end_balance' => 'Saldo finale inviato', 'initial_balance_description' => 'Saldo iniziale per ":account"', // categories: @@ -766,45 +767,45 @@ return [ 'without_category_between' => 'Senza categoria tra :start e :end', // transactions: - 'update_withdrawal' => 'Aggiorna spesa', + 'update_withdrawal' => 'Aggiorna prelievo', 'update_deposit' => 'Aggiorna entrata', 'update_transfer' => 'Aggiorna trasferimento', - 'updated_withdrawal' => 'Spesa aggiornata ":description"', + 'updated_withdrawal' => 'Prelievo ":description" aggiornato', 'updated_deposit' => 'Entrata aggiornata ":description"', 'updated_transfer' => 'Trasferimento ":description" aggiornato', - 'delete_withdrawal' => 'Elimina spesa ":description"', + 'delete_withdrawal' => 'Elimina prelievo ":description"', 'delete_deposit' => 'Elimina entrata ":description"', 'delete_transfer' => 'Elimina trasferimento ":description"', - 'deleted_withdrawal' => 'Spesa eliminata correttamente ":description"', - 'deleted_deposit' => 'Entrata eliminata correttamente ":description"', + 'deleted_withdrawal' => 'Prelievo ":description" eliminato correttamente', + 'deleted_deposit' => 'Entrata ":description" eliminata correttamente', 'deleted_transfer' => 'Trasferimento ":description" eliminato correttamente', - 'stored_journal' => 'Nuova transazione creata correttamente ":description"', + 'stored_journal' => 'Nuova transazione ":description" creata correttamente', 'select_transactions' => 'Seleziona transazioni', 'rule_group_select_transactions' => 'Applica ":title" a transazioni', 'rule_select_transactions' => 'Applica ":title" a transazioni', 'stop_selection' => 'Smetti di selezionare le transazioni', 'reconcile_selected' => 'Riconcilia', - 'mass_delete_journals' => 'Elimina un numero di transazioni', - 'mass_edit_journals' => 'Modifica un numero di transazioni', - 'mass_bulk_journals' => 'Modifica in blocco un numero di transazioni', - 'mass_bulk_journals_explain' => 'Se non si desidera modificare le transazioni una alla volta utilizzando la funzione di modifica di massa, è possibile aggiornarle in una volta sola. Basta selezionare le categorie, le etichette o i budget preferiti nei campi sottostanti e tutte le transazioni nella tabella verranno aggiornate.', + 'mass_delete_journals' => 'Elimina un certo numero di transazioni', + 'mass_edit_journals' => 'Modifica un certo numero di transazioni', + 'mass_bulk_journals' => 'Modifica in blocco un certo numero di transazioni', + 'mass_bulk_journals_explain' => 'Se non si desidera modificare le transazioni una alla volta utilizzando la funzione di modifica in blocco, è possibile aggiornarle in una volta sola. Basta selezionare le categorie, le etichette o i budget preferiti nei campi sottostanti e tutte le transazioni nella tabella verranno aggiornate.', 'bulk_set_new_values' => 'Usa gli inserimenti qui sotto per impostare nuovi valori. Se li lasci vuoti, saranno resi vuoti per tutti. Inoltre, si noti che solo i prelievi avranno un budget.', 'no_bulk_category' => 'Non aggiornare la categoria', 'no_bulk_budget' => 'Non aggiornare il budget', - 'no_bulk_tags' => 'Non aggiornare la(e) etichetta(e)', - 'bulk_edit' => 'Modifica collettiva', - 'cannot_edit_other_fields' => 'Non puoi modificare in massa altri campi oltre a quelli qui, perché non c\'è spazio per mostrarli. Segui il link e modificali uno per uno, se è necessario modificare questi campi.', + 'no_bulk_tags' => 'Non aggiornare le etichette', + 'bulk_edit' => 'Modifica in blocco', + 'cannot_edit_other_fields' => 'Non puoi modificare in blocco altri campi oltre a quelli presenti perché non c\'è spazio per mostrarli. Segui il link e modificali uno per uno se è necessario modificare questi campi.', 'no_budget' => 'nessuno', - 'no_budget_squared' => '(nessun bilancio)', + 'no_budget_squared' => '(nessun budget)', 'perm-delete-many' => 'Eliminare molti oggetti in una volta sola può essere molto pericoloso. Per favore sii cauto.', - 'mass_deleted_transactions_success' => 'Transazione(i) eliminata(e) :amount.', - 'mass_edited_transactions_success' => 'Transazione(i) aggiornata(e) :amount.', + 'mass_deleted_transactions_success' => ':amount transazioni eliminate .', + 'mass_edited_transactions_success' => ':amount transazioni aggiornate', 'opt_group_no_account_type' => '(nessun tipo di conto)', 'opt_group_defaultAsset' => 'Conto attività predefinito', 'opt_group_savingAsset' => 'Conti risparmio', - 'opt_group_sharedAsset' => 'Conti risorse condivise', + 'opt_group_sharedAsset' => 'Conti attività condivisi', 'opt_group_ccAsset' => 'Carte di credito', - 'opt_group_cashWalletAsset' => 'Contanti', + 'opt_group_cashWalletAsset' => 'Portafogli', 'notes' => 'Note', 'unknown_journal_error' => 'Impossibile memorizzare la transazione. Controllare i file di log.', @@ -812,14 +813,14 @@ return [ 'welcome' => 'Benvenuto in Firefly III!', 'submit' => 'Invia', 'getting_started' => 'Inizia', - 'to_get_started' => 'È bello vedere che hai installato Firefly III con successo. Per iniziare con questo strumento, inserisci il nome della tua banca e il saldo del tuo conto corrente principale. Non preoccuparti se hai più account. È possibile aggiungere quelli più tardi. Firefly III ha bisogno di qualcosa per iniziare.', - 'savings_balance_text' => 'Firefly III creerà automaticamente un conto di risparmio per te. Per impostazione predefinita, non ci saranno soldi nel tuo conto di risparmio, ma se comunichi a Firefly III il saldo verrà archiviato come tale.', + 'to_get_started' => 'È bello vedere che hai installato Firefly III con successo. Per iniziare con questo strumento, inserisci il nome della tua banca e il saldo del tuo conto corrente principale. Non preoccuparti se hai più conti. È possibile aggiungere quelli più tardi. Firefly III ha bisogno di qualcosa per iniziare.', + 'savings_balance_text' => 'Firefly III creerà automaticamente un conto di risparmio per te. Per impostazione predefinita, non ci saranno soldi nel tuo conto di risparmio, ma se comunichi a Firefly III il saldo verrà salvato come tale.', 'finish_up_new_user' => 'Questo è tutto! Puoi continuare premendo Invia. Verrai indirizzato all\'indice di Firefly III.', 'stored_new_accounts_new_user' => 'I tuoi nuovi conti sono stati salvati.', 'set_preferred_language' => 'Se preferisci usare Firefly III in un\'altra lingua, impostala qui.', 'language' => 'Lingua', 'new_savings_account' => 'Conto di risparmio :bank_name', - 'cash_wallet' => 'Contanti', + 'cash_wallet' => 'Portafoglio', 'currency_not_present' => 'Se la valuta che usi normalmente non è elencata, non preoccuparti. Puoi creare le tue valute in Opzioni > Valute.', // home page: @@ -845,7 +846,7 @@ return [ 'currencies' => 'Valute', 'accounts' => 'Conti', 'Asset account' => 'Conto attività', - 'Default account' => 'Conto predefinito', + 'Default account' => 'Conto attività', 'Expense account' => 'Conto spese', 'Revenue account' => 'Conto entrate', 'Initial balance account' => 'Saldo iniziale conto', @@ -854,17 +855,17 @@ return [ 'reports' => 'Resoconti', 'transactions' => 'Transazioni', 'expenses' => 'Spese', - 'income' => 'Reddito / Entrata', + 'income' => 'Redditi / entrate', 'transfers' => 'Trasferimenti', - 'moneyManagement' => 'Gestione danaro', + 'moneyManagement' => 'Gestione denaro', 'piggyBanks' => 'Salvadanai', 'bills' => 'Bollette', - 'withdrawal' => 'Uscite', + 'withdrawal' => 'Prelievo', 'opening_balance' => 'Saldo di apertura', 'deposit' => 'Entrata', 'account' => 'Conto', 'transfer' => 'Trasferimento', - 'Withdrawal' => 'Spesa', + 'Withdrawal' => 'Prelievo', 'Deposit' => 'Entrata', 'Transfer' => 'Trasferimento', 'bill' => 'Bolletta', @@ -896,18 +897,17 @@ return [ 'reports_can_bookmark' => 'Ricorda che i resoconti possono essere aggiunti ai segnalibri.', 'incomeVsExpenses' => 'Entrate verso spese', 'accountBalances' => 'Saldo dei conti', - 'balanceStart' => 'Saldo inizio periodo', - 'balanceEnd' => 'Saldo fine del periodo', + 'balanceStart' => 'Saldo all\'inizio del periodo', + 'balanceEnd' => 'Saldo alla fine del periodo', 'splitByAccount' => 'Dividi per conto', 'coveredWithTags' => 'Coperto con etichetta', - 'leftUnbalanced' => 'Sbilanciato a sinistra', - 'leftInBudget' => 'Rimasto nel budget', + 'leftInBudget' => 'Rimanenza nel budget', 'sumOfSums' => 'Somma dei conti', 'noCategory' => '(nessuna categoria)', - 'notCharged' => 'Non caricato (ancora)', + 'notCharged' => 'Non (ancora) addebitato', 'inactive' => 'Disattivo', 'active' => 'Attivo', - 'difference' => 'Differenze', + 'difference' => 'Differenza', 'money_flowing_in' => 'Entrate', 'money_flowing_out' => 'Uscite', 'topX' => 'Superiore :number', @@ -915,13 +915,13 @@ return [ 'show_only_top' => 'Mostra solo in alto :number', 'report_type' => 'Tipo resoconto', 'report_type_default' => 'Resoconto finanziario predefinito', - 'report_type_audit' => 'Panoramica cronologica delle transazioni (controllo)', + 'report_type_audit' => 'Panoramica cronologica delle transazioni (verifica)', 'report_type_category' => 'Resoconto categoria', 'report_type_budget' => 'Resoconto budget', 'report_type_tag' => 'Resoconto etichetta', 'report_type_account' => 'Resoconto conto spese/entrate', 'more_info_help' => 'Ulteriori informazioni su questi tipi di resoconti sono disponibili nelle pagine della guida. Premi l\'icona (?) nell\'angolo in alto a destra.', - 'report_included_accounts' => 'Conti inclusi', + 'report_included_accounts' => 'Inclusi i conti', 'report_date_range' => 'Date intervallo', 'report_preset_ranges' => 'Intervalli preimpostati', 'shared' => 'Condiviso', @@ -930,37 +930,37 @@ return [ 'expense_entry' => 'Spese per il conto ":name" tra :start e :end', 'category_entry' => 'Spese nella categoria ":name" tra :start e :end', 'budget_spent_amount' => 'Spese nel budget ":budget" tra :start e :end', - 'balance_amount' => 'Spese nel budget ":budget" pagate dal conto ":account" tra :start e :end', - 'no_audit_activity' => 'Nessuna attività è stata registrata nel conto :account_name tra :start e :end.', - 'audit_end_balance' => 'Il saldo del conto di :account_name alla fine di :end era: :balance', + 'balance_amount' => 'Spese nel budget ":budget" pagate dal conto ":account" tra il :start e il :end', + 'no_audit_activity' => 'Nessuna attività è stata registrata nel conto :account_name tra il :start e il :end.', + 'audit_end_balance' => 'Il saldo del conto :account_name alla fine di :end era: :balance', 'reports_extra_options' => 'Opzioni extra', 'report_has_no_extra_options' => 'Questo resoconto non ha opzioni extra', 'reports_submit' => 'Mostra resoconto', 'end_after_start_date' => 'La data della fine del resoconto deve essere successiva alla data di inizio.', - 'select_category' => 'Seleziona la(e) categoria (e)', + 'select_category' => 'Seleziona le categorie', 'select_budget' => 'Seleziona i budget.', - 'select_tag' => 'Seleziona etichetta(e).', - 'income_per_category' => 'Reddito per categoria', - 'expense_per_category' => 'Spese per categoria', + 'select_tag' => 'Seleziona etichette.', + 'income_per_category' => 'Entrate per categoria', + 'expense_per_category' => 'Spesa per categoria', 'expense_per_budget' => 'Spese per budget', - 'income_per_account' => 'Reddito per conto', + 'income_per_account' => 'Entrate per conto', 'expense_per_account' => 'Spese per conto', 'expense_per_tag' => 'Spese per etichetta', - 'income_per_tag' => 'Reddito per etichetta', - 'include_expense_not_in_budget' => 'Spese non incluse nei budget selezionati', - 'include_expense_not_in_account' => 'Spese non incluse nel(nei) conto(i) selezionato(i)', - 'include_expense_not_in_category' => 'Spese incluse / non incluse nella(e) categoria(e) selezionata(e)', - 'include_income_not_in_category' => 'Entrate non incluse nella(e) categoria(e) selezionata(e)', - 'include_income_not_in_account' => 'Entrate non incluse nel(i) conto(i) selezionato(i)', - 'include_income_not_in_tags' => 'Entrate incluse / non incluse nella(e) etichetta(e) selezionata(e)', - 'include_expense_not_in_tags' => 'Spese incluse / non incluse nella(e) etichetta(e) selezionata(e)', + 'income_per_tag' => 'Entrate per etichetta', + 'include_expense_not_in_budget' => 'Incluse le spese non appartenenti ai budget selezionati', + 'include_expense_not_in_account' => 'Incluse le spese non appartenenti ai conti selezionati', + 'include_expense_not_in_category' => 'Incluse le spese non appartenenti alle categorie selezionate', + 'include_income_not_in_category' => 'Incluse le entrate non appartenenti alle categorie selezionate', + 'include_income_not_in_account' => 'Incluse le entrate non appartenenti ai conti selezionati', + 'include_income_not_in_tags' => 'Incluse le entrate non appartenenti alle etichette selezionate', + 'include_expense_not_in_tags' => 'Incluse le spese non appartenenti alle etichette selezionate', 'everything_else' => 'Tutto il resto', 'income_and_expenses' => 'Entrate e spese', - 'spent_average' => 'Speso (media)', - 'income_average' => 'Reddito (media)', + 'spent_average' => 'Spesi (media)', + 'income_average' => 'Entrate (media)', 'transaction_count' => 'Conteggio transazioni', 'average_spending_per_account' => 'Spesa media per conto', - 'average_income_per_account' => 'Reddito medio per conto', + 'average_income_per_account' => 'Media entrate per conto', 'total' => 'Totale', 'description' => 'Descrizione', 'sum_of_period' => 'Somma del periodo', @@ -975,7 +975,7 @@ return [ 'in_out_accounts' => 'Guadagnati e spesi per combinazione', 'in_out_per_category' => 'Guadagnati e spesi per categoria', 'out_per_budget' => 'Speso per budget', - 'select_expense_revenue' => 'Seleziona conto spese / entrate', + 'select_expense_revenue' => 'Seleziona conto spese/entrate', // charts: 'chart' => 'Grafico', @@ -992,7 +992,7 @@ return [ 'journal-amount' => 'Entrata fattura corrente', 'name' => 'Nome', 'date' => 'Data', - 'paid' => 'Pagato', + 'paid' => 'Pagati', 'unpaid' => 'Da pagare', 'day' => 'Giorno', 'budgeted' => 'Preventivato', @@ -1043,16 +1043,16 @@ return [ // tags 'delete_tag' => 'Elimina etichetta ":tag"', - 'deleted_tag' => 'Etichetta eliminata ":tag"', + 'deleted_tag' => 'Etichetta ":tag" eliminata', 'new_tag' => 'Crea nuova etichetta', 'edit_tag' => 'Modifica etichetta ":tag"', - 'updated_tag' => 'Etichetta aggiornata ":tag"', + 'updated_tag' => 'Etichetta ":tag" aggiornata', 'created_tag' => 'Etichetta ":tag" creata correttamente', 'transaction_journal_information' => 'Informazione Transazione', 'transaction_journal_meta' => 'Meta informazioni', 'total_amount' => 'Importo totale', - 'number_of_decimals' => 'Numero decimali', + 'number_of_decimals' => 'Cifre decimali', // administration 'administration' => 'Amministrazione', @@ -1060,9 +1060,9 @@ return [ 'list_all_users' => 'Tutti gli utenti', 'all_users' => 'Tutti gli utenti', 'instance_configuration' => 'Configurazione', - 'firefly_instance_configuration' => 'Opzioni Configurazione di Firefly III', + 'firefly_instance_configuration' => 'Opzioni di configurazione di Firefly III', 'setting_single_user_mode' => 'Modo utente singolo', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', + 'setting_single_user_mode_explain' => 'Per impostazione predefinita, Firefly III accetta solo una (1) registrazione: tu. Questa è una misura di sicurezza, che impedisce ad altri di usare la tua istanza a meno che tu non le autorizzi. Le future registrazioni sono bloccate. Bene! quando deselezioni questa casella, gli altri possono usare la tua istanza, supponendo che possano raggiungerla (quando è connessa a Internet).', 'store_configuration' => 'Salva configurazione', 'single_user_administration' => 'Amministrazione utenti per :email', 'edit_user' => 'Modifica utente :email', @@ -1072,19 +1072,19 @@ return [ 'total_size' => 'dimensione totale', 'budget_or_budgets' => 'budget', 'budgets_with_limits' => 'budget con importo configurato', - 'nr_of_rules_in_total_groups' => 'regola(e) :count_rules in gruppo(i) :count_groups', - 'tag_or_tags' => 'etichetta(e)', + 'nr_of_rules_in_total_groups' => ':count_rules regole in :count_groups gruppi di regole', + 'tag_or_tags' => 'etichette', 'configuration_updated' => 'La configurazione è stata aggiornata', 'setting_is_demo_site' => 'Sito Demo', 'setting_is_demo_site_explain' => 'Se si seleziona questa casella, questa installazione si comporterà come se fosse il sito demo, che può avere strani effetti collaterali.', - 'block_code_bounced' => 'Il(i) messaggio(i) email rimbalzati', + 'block_code_bounced' => 'Messaggi email respinti', 'block_code_expired' => 'Conto demo scaduto', 'no_block_code' => 'Nessun motivo per bloccare o non bloccare un utente', 'block_code_email_changed' => 'L\'utente non ha ancora confermato il nuovo indirizzo emails', 'admin_update_email' => 'Contrariamente alla pagina del profilo, l\'utente NON riceverà alcuna notifica al proprio indirizzo email!', 'update_user' => 'Aggiorna utente', 'updated_user' => 'I dati dell\'utente sono stati modificati.', - 'delete_user' => 'Elimia utente :email', + 'delete_user' => 'Elimina utente :email', 'user_deleted' => 'L\'utente è stato eliminato', 'send_test_email' => 'Invia un messaggio di posta elettronica di prova', 'send_test_email_text' => 'Per vedere se la tua installazione è in grado di inviare e-mail, ti preghiamo di premere questo pulsante. Qui non vedrai un errore (se presente), i file di log rifletteranno eventuali errori. Puoi premere questo pulsante tutte le volte che vuoi. Non c\'è controllo dello spam. Il messaggio verrà inviato a :email e dovrebbe arrivare a breve.', @@ -1092,12 +1092,12 @@ return [ 'send_test_triggered' => 'Il test è stato attivato. Controlla la tua casella di posta e i file di log.', // links - 'journal_link_configuration' => 'Configurazione dei collegamenti di transazione', + 'journal_link_configuration' => 'Configurazione dei collegamenti di una transazione', 'create_new_link_type' => 'Crea un nuovo tipo di collegamento', 'store_new_link_type' => 'Memorizza nuovo tipo di collegamento', 'update_link_type' => 'Aggiorna il tipo di collegamento', 'edit_link_type' => 'Modifica il tipo di collegamento ":name"', - 'updated_link_type' => 'Tipo di collegamento aggiornato ":name"', + 'updated_link_type' => 'Tipo del collegamento ":name" aggiornato', 'delete_link_type' => 'Elimina il tipo di collegamento ":name"', 'deleted_link_type' => 'Tipo di collegamento eliminato ":name"', 'stored_new_link_type' => 'Memorizza nuovo tipo di collegamento ":name"', @@ -1105,7 +1105,7 @@ return [ 'link_type_help_name' => 'Ie. "Duplicati"', 'link_type_help_inward' => 'Ie. "duplicati"', 'link_type_help_outward' => 'Ie. "è duplicato da"', - 'save_connections_by_moving' => 'Salvare il collegamento tra questa(e) transazione(i) spostandola(e) su un altro tipo di collegamento:', + 'save_connections_by_moving' => 'Salva il collegamento tra queste transazioni spostandole su un altro tipo di collegamento:', 'do_not_save_connection' => '(non salvare la connessione)', 'link_transaction' => 'Collega transazione', 'link_to_other_transaction' => 'Collega questa transazione a un\'altra transazione', @@ -1119,7 +1119,7 @@ return [ 'journals_error_linked' => 'Queste transazioni sono già collegate.', 'journals_link_to_self' => 'Non puoi collegare una transazione con se stessa', 'journal_links' => 'Collegamenti di transazione', - 'this_withdrawal' => 'Questa spesa', + 'this_withdrawal' => 'Questo prelievo', 'this_deposit' => 'Questa entrata', 'this_transfer' => 'Questo trasferimento', 'overview_for_link' => 'Panoramica per tipo di collegamento ":name"', @@ -1131,11 +1131,11 @@ return [ // link translations: 'relates to_inward' => 'inerente a', - 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsato da', - 'is (partially) paid for by_inward' => 'è (parzialmente) pagato da', - 'is (partially) reimbursed by_inward' => 'è (parzialmente) rimborsato da', + 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsata da', + 'is (partially) paid for by_inward' => 'è (parzialmente) pagata da', + 'is (partially) reimbursed by_inward' => 'è (parzialmente) rimborsata da', 'relates to_outward' => 'inerente a', - '(partially) refunds_outward' => '(parzialmente) rimborsi', + '(partially) refunds_outward' => '(parzialmente) rimborsa', '(partially) pays for_outward' => '(parzialmente) paga per', '(partially) reimburses_outward' => '(parzialmente) rimborsa', @@ -1144,36 +1144,36 @@ return [ 'add_another_split' => 'Aggiungi un\'altra divisione', 'split-transactions' => 'Dividi le transazioni', 'do_split' => 'Fai una divisione', - 'split_this_withdrawal' => 'Dividi questa spesa', + 'split_this_withdrawal' => 'Dividi questo prelievo', 'split_this_deposit' => 'Dividi questa entrata', 'split_this_transfer' => 'Dividi questo trasferimento', 'cannot_edit_multiple_source' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di origine.', 'cannot_edit_multiple_dest' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di destinazione.', 'cannot_edit_reconciled' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché è stata contrassegnata come riconciliata.', - 'cannot_edit_opening_balance' => 'Non è possibile modificare il bilancio di apertura di un conto.', + 'cannot_edit_opening_balance' => 'Non è possibile modificare il saldo di apertura di un conto.', 'no_edit_multiple_left' => 'Non hai selezionato transazioni valide da modificare.', 'cannot_convert_split_journal' => 'Impossibile convertire una transazione divisa', // Import page (general strings only) - 'import_index_title' => 'Import transactions into Firefly III', - 'import_data' => 'Importa i dati', - 'import_transactions' => 'Import transactions', + 'import_index_title' => 'Importa le transazioni in Firefly III', + 'import_data' => 'Importa dati', + 'import_transactions' => 'Importa transazioni', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Questa funzione non è disponibile quando si utilizza Firefly III in un ambiente Sandstorm.io.', // empty lists? no objects? instructions: 'no_accounts_title_asset' => 'Creiamo un conto attività!', - 'no_accounts_intro_asset' => 'Non hai ancora un conto attività. I conti cespiti sono i tuoi conti principali: il tuo conto corrente, il tuo conto di risparmio, il conto condiviso o persino la tua carta di credito.', - 'no_accounts_imperative_asset' => 'Per iniziare a utilizzare Firefly III è necessario creare almeno un conto attività. Facciamo così ora:', + 'no_accounts_intro_asset' => 'Non hai ancora un conto attività. I conti attività sono i tuoi conti principali: il tuo conto corrente, il tuo conto di risparmio, il conto condiviso o persino la tua carta di credito.', + 'no_accounts_imperative_asset' => 'Per iniziare a utilizzare Firefly III è necessario creare almeno un conto attività. Facciamolo ora:', 'no_accounts_create_asset' => 'Crea un conto attività', 'no_accounts_title_expense' => 'Creiamo un conto spese!', 'no_accounts_intro_expense' => 'Non hai ancora un conto spesa. I conti spese sono i luoghi in cui si spendono soldi, come negozi e supermercati.', 'no_accounts_imperative_expense' => 'I conti spesa vengono creati automaticamente quando si creano le transazioni, ma è possibile crearne anche manualmente, se lo si desidera. Ne creiamo uno ora:', 'no_accounts_create_expense' => 'Crea conto spesa', 'no_accounts_title_revenue' => 'Creiamo un conto entrate!', - 'no_accounts_intro_revenue' => 'Non hai ancora un conto entrate. I conti delle entrate sono i luoghi in cui ricevi denaro, come il tuo stipendio e altre entrate.', - 'no_accounts_imperative_revenue' => 'I conti entrate vengono creati automaticamente quando si creano le transazioni, ma è possibile crearne anche manualmente, se lo si desidera. Ne creiamo uno ora:', + 'no_accounts_intro_revenue' => 'Non hai ancora un conto entrate. I conti delle entrate sono i luoghi da cui ricevi denaro, come il tuo datore di lavoro.', + 'no_accounts_imperative_revenue' => 'I conti entrate vengono creati automaticamente quando si creano le transazioni, ma è possibile crearne anche manualmente, se lo si desidera. Creiamone uno ora:', 'no_accounts_create_revenue' => 'Crea conto entrate', 'no_budgets_title_default' => 'Creiamo un budget', 'no_budgets_intro_default' => 'Non hai ancora budget. I budget sono usati per organizzare le tue spese in gruppi logici, ai quali puoi dare un tetto indicativo per limitare le tue spese.', @@ -1192,7 +1192,7 @@ return [ 'no_transactions_imperative_withdrawal' => 'Hai effettuato delle spese? Dovresti registrarle:', 'no_transactions_create_withdrawal' => 'Crea spese', 'no_transactions_title_deposit' => 'Creiamo delle entrate!', - 'no_transactions_intro_deposit' => 'non hai ancora entrate registrate. È necessario creare voci di reddito per iniziare a gestire le tue finanze.', + 'no_transactions_intro_deposit' => 'Non hai ancora delle entrate registrate. Dovresti creare delle voci di entrata per iniziare a gestire le tue finanze.', 'no_transactions_imperative_deposit' => 'Hai ricevuto dei soldi? Dovresti scriverlo:', 'no_transactions_create_deposit' => 'Crea una entrata', 'no_transactions_title_transfers' => 'Creiamo un trasferimento!', @@ -1209,55 +1209,66 @@ return [ 'no_bills_create_default' => 'Crea bolletta', // recurring transactions - 'recurrences' => 'Recurring transactions', - 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', - 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', - 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', - 'no_recurring_create_default' => 'Create a recurring transaction', - 'make_new_recurring' => 'Create a recurring transaction', - 'recurring_daily' => 'Every day', - 'recurring_weekly' => 'Every week on :weekday', - 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', - 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', - 'recurring_yearly' => 'Every year on :date', - 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', - 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', - 'created_transactions' => 'Related transactions', - 'expected_Withdrawals' => 'Expected withdrawals', - 'expected_Deposits' => 'Expected deposits', - 'expected_Transfers' => 'Expected transfers', - 'created_Withdrawals' => 'Created withdrawals', - 'created_Deposits' => 'Created deposits', - 'created_Transfers' => 'Created transfers', - 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurrences' => 'Transazioni ricorrenti', + 'no_recurring_title_default' => 'Creiamo una transazione ricorrente!', + 'no_recurring_intro_default' => 'Non hai ancora una transazione ricorrente. Puoi utilizzare queste per lasciare che Firefly III crei automaticamente le transazioni per te.', + 'no_recurring_imperative_default' => 'Questa è una caratteristica piuttosto avanzata che può essere estremamente utile. Assicurati di leggere la documentazione (l\'icona (?) nell\'angolo in alto a destra) prima di continuare.', + 'no_recurring_create_default' => 'Crea una transazione ricorrente', + 'make_new_recurring' => 'Crea una transazione ricorrente', + 'recurring_daily' => 'Ogni giorno', + 'recurring_weekly' => 'Ogni settimana di :weekday', + 'recurring_monthly' => 'Ogni mese il giorno :dayOfMonth', + 'recurring_ndom' => 'Ogni :dayOfMonth° :weekday del mese', + 'recurring_yearly' => 'Ogni anno il :date', + 'overview_for_recurrence' => 'Panoramica per la transazione ricorrente ":title"', + 'warning_duplicates_repetitions' => 'In alcuni casi rari è possibile che delle date appaiano due volte in questa lista. Questo può succedere quando ripetizioni multiple collidono. Firefly III si assicurerà sempre di generare una sola transazione per giorno.', + 'created_transactions' => 'Transazioni collegate', + 'expected_Withdrawals' => 'Prelievi previsti', + 'expected_Deposits' => 'Deposito previsto', + 'expected_Transfers' => 'Trasferimento previsto', + 'created_Withdrawals' => 'Prelievi creati', + 'created_Deposits' => 'Depositi creati', + 'created_Transfers' => 'Trasferimenti creati', + 'created_from_recurrence' => 'Creata dalla transazione ricorrente ":title" (#:id)', - 'recurring_meta_field_tags' => 'Tags', - 'recurring_meta_field_notes' => 'Notes', - 'recurring_meta_field_bill_id' => 'Bill', - 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', - 'create_new_recurrence' => 'Create new recurring transaction', - 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', - 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', - 'no_currency' => '(no currency)', - 'mandatory_for_recurring' => 'Mandatory recurrence information', - 'mandatory_for_transaction' => 'Mandatory transaction information', - 'optional_for_recurring' => 'Optional recurrence information', - 'optional_for_transaction' => 'Optional transaction information', - 'change_date_other_options' => 'Change the "first date" to see more options.', - 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', - 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', - 'repeat_forever' => 'Repeat forever', - 'repeat_until_date' => 'Repeat until date', - 'repeat_times' => 'Repeat a number of times', - 'recurring_skips_one' => 'Every other', - 'recurring_skips_more' => 'Skips :count occurrences', - 'store_new_recurrence' => 'Store recurring transaction', - 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', - 'edit_recurrence' => 'Edit recurring transaction ":title"', - 'recurring_repeats_until' => 'Repeats until :date', - 'recurring_repeats_forever' => 'Repeats forever', - 'recurring_repeats_x_times' => 'Repeats :count time(s)', - 'update_recurrence' => 'Update recurring transaction', - 'updated_recurrence' => 'Updated recurring transaction ":title"', - 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'recurring_meta_field_tags' => 'Etichette', + 'recurring_meta_field_notes' => 'Note', + 'recurring_meta_field_bill_id' => 'Bolletta', + 'recurring_meta_field_piggy_bank_id' => 'Salvadanaio', + 'create_new_recurrence' => 'Crea una nuova transazione ricorrente', + 'help_first_date' => 'Indica quando la ricorrenza dovrebbe avvenire per la prima volta. Questo deve essere nel futuro.', + 'help_first_date_no_past' => 'Indica quando la ricorrenza dovrebbe avvenire per la prima volta. Firefly III non creerà transazioni nel passato.', + 'no_currency' => '(nessuna valuta)', + 'mandatory_for_recurring' => 'Informazioni obbligatorie sulla ricorrenza', + 'mandatory_for_transaction' => 'Informazioni obbligatorie sulla transazione', + 'optional_for_recurring' => 'Informazioni facoltative sulla ricorrenza', + 'optional_for_transaction' => 'Informazioni facoltative sulla transazione', + 'change_date_other_options' => 'Cambia la "prima volta" per visualizzare maggiori opzioni.', + 'mandatory_fields_for_tranaction' => 'Il valore qui presente finirà nella transazione che verrà creata', + 'click_for_calendar' => 'Clicca qui per visualizzare in un calendario quando la transazione si ripete.', + 'repeat_forever' => 'Ripeti per sempre', + 'repeat_until_date' => 'Ripeti fino alla data', + 'repeat_times' => 'Ripeti per un certo numero di volte', + 'recurring_skips_one' => 'Una volta sì e una volta no', + 'recurring_skips_more' => 'Salta per :count volte', + 'store_new_recurrence' => 'Salva transazione ricorrente', + 'stored_new_recurrence' => 'La transazione ricorrente ":title" è stata salvata con successo.', + 'edit_recurrence' => 'Modifica transazione ricorrente ":title"', + 'recurring_repeats_until' => 'Si ripete fino al :date', + 'recurring_repeats_forever' => 'Si ripete per sempre', + 'recurring_repeats_x_times' => 'Si ripete per :count volte', + 'update_recurrence' => 'Aggiorna transazione ricorrente', + 'updated_recurrence' => 'Transazione ricorrente ":title" aggiornata', + 'recurrence_is_inactive' => 'Questa transazione ricorrente non è attiva e non genererà nuove transazioni.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index c52706c4e7..e9ebdf5f50 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -28,15 +28,15 @@ return [ 'bank_balance' => 'Saldo', 'savings_balance' => 'Saldo risparmio', 'credit_card_limit' => 'Limite Carta di Credito', - 'automatch' => 'Partita automaticamente', - 'skip' => 'Salta', + 'automatch' => 'Abbina automaticamente', + 'skip' => 'Salta ogni', 'name' => 'Nome', 'active' => 'Attivo', 'amount_min' => 'Importo minimo', 'amount_max' => 'Importo massimo', - 'match' => 'Partite su', + 'match' => 'Abbina con', 'strict' => 'Modalità severa', - 'repeat_freq' => 'Ripetizioni', + 'repeat_freq' => 'Si ripete', 'journal_currency_id' => 'Valuta', 'currency_id' => 'Valuta', 'transaction_currency_id' => 'Valuta', @@ -49,8 +49,8 @@ return [ 'verify_password' => 'Verifica password di sicurezza', 'source_account' => 'Conto sorgente', 'destination_account' => 'Conto destinazione', - 'journal_destination_account_id' => 'Conto attività (destinatione)', - 'asset_destination_account' => 'Conto attività (destinatione)', + 'journal_destination_account_id' => 'Conto attività (destinazione)', + 'asset_destination_account' => 'Conto attività (destinazione)', 'asset_source_account' => 'Conto attività (sorgente)', 'journal_description' => 'Descrizione', 'note' => 'Note', @@ -65,9 +65,9 @@ return [ 'virtualBalance' => 'Saldo virtuale', 'targetamount' => 'Importo obiettivo', 'accountRole' => 'Ruolo del conto', - 'openingBalanceDate' => 'Data di apertura del bilancio', + 'openingBalanceDate' => 'Data saldo di apertura', 'ccType' => 'Piano di pagamento con carta di credito', - 'ccMonthlyPaymentDate' => 'Data di pagamento mensile della carta di credito', + 'ccMonthlyPaymentDate' => 'Data di addebito mensile della carta di credito', 'piggy_bank_id' => 'Salvadanaio', 'returnHere' => 'Ritorna qui', 'returnHereExplanation' => 'Dopo averlo archiviato, torna qui per crearne un altro.', @@ -85,101 +85,104 @@ return [ 'api_key' => 'Chiave API', 'remember_me' => 'Ricordami', - 'source_account_asset' => 'Conto origine (conto risorse)', + 'source_account_asset' => 'Conto origine (conto attività)', 'destination_account_expense' => 'Conto destinazione (conto spese)', - 'destination_account_asset' => 'Conto destinazione (conto risorse)', + 'destination_account_asset' => 'Conto destinazione (conto attività)', 'source_account_revenue' => 'Conto sorgente (conto entrate)', 'type' => 'Tipo', - 'convert_Withdrawal' => 'Converti spesa', + 'convert_Withdrawal' => 'Converti prelievo', 'convert_Deposit' => 'Converti entrata', 'convert_Transfer' => 'Converti trasferimento', - 'amount' => 'Importo', - 'foreign_amount' => 'Importo estero', - 'existing_attachments' => 'Allegati esistenti', - 'date' => 'Data', - 'interest_date' => 'Data interesse', - 'book_date' => 'Agenda', - 'process_date' => 'Data di lavorazione', - 'category' => 'Categoria', - 'tags' => 'Etichette', - 'deletePermanently' => 'Elimina definitivamente', - 'cancel' => 'Cancella', - 'targetdate' => 'Data fine', - 'startdate' => 'Data inizio', - 'tag' => 'Etichetta', - 'under' => 'Sotto', - 'symbol' => 'Simbolo', - 'code' => 'Codice', - 'iban' => 'IBAN', - 'accountNumber' => 'Numero conto', - 'creditCardNumber' => 'Numero Carta di credito', - 'has_headers' => 'Intestazioni', - 'date_format' => 'Formato data', - 'specifix' => 'Correzioni bancarie o file specifiche', - 'attachments[]' => 'Allegati', - 'store_new_withdrawal' => 'Salva nuovo prelievo', - 'store_new_deposit' => 'Salva nuovo deposito', - 'store_new_transfer' => 'Salva nuova trasferimento', - 'add_new_withdrawal' => 'Aggiungi nuovo prelievo', - 'add_new_deposit' => 'Aggiungi nuovo deposito', - 'add_new_transfer' => 'Aggiungi nuovo giroconto', - 'title' => 'Titolo', - 'notes' => 'Note', - 'filename' => 'Nome file', - 'mime' => 'Tipo Mime', - 'size' => 'Dimensione', - 'trigger' => 'Trigger', - 'stop_processing' => 'Interrompere l\'elaborazione', - 'start_date' => 'Inizio periodo', - 'end_date' => 'Fine periodo', - 'export_start_range' => 'Inizio intervallo esportazione', - 'export_end_range' => 'Fine intervallo esportazione', - 'export_format' => 'Formato file', - 'include_attachments' => 'Includi allegati caricati', - 'include_old_uploads' => 'Includi dati importati', - 'accounts' => 'Esporta le transazioni da questi conti', - 'delete_account' => 'Elimina conto ":name"', - 'delete_bill' => 'Elimina bolletta ":name"', - 'delete_budget' => 'Elimina budget ":name"', - 'delete_category' => 'Elimina categoria ":name"', - 'delete_currency' => 'Elimina valuta ":name"', - 'delete_journal' => 'Elimina transazione con descrizione ":description"', - 'delete_attachment' => 'Elimina allegato ":name"', - 'delete_rule' => 'Elimina regola ":title"', - 'delete_rule_group' => 'Elimina gruppo regole":title"', - 'delete_link_type' => 'Elimina tipo collegamento ":name"', - 'delete_user' => 'Elimina utente ":email"', - 'user_areYouSure' => 'Se cancelli l\'utente ":email", verrà eliminato tutto. Non sarà più possibile recuperare i dati eliminati. Se cancelli te stesso, perderai l\'accesso a questa istanza di Firefly III.', - 'attachment_areYouSure' => 'Sei sicuro di voler eliminare l\'allegato ":name"?', - 'account_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', - 'bill_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', - 'rule_areYouSure' => 'Sei sicuro di voler eliminare la regola ":title"?', - 'ruleGroup_areYouSure' => 'Sei sicuro di voler eliminare il gruppo regole ":title"?', - 'budget_areYouSure' => 'Sei sicuro di voler eliminare il budget ":name"?', - 'category_areYouSure' => 'Sei sicuro di voler eliminare categoria ":name"?', - 'currency_areYouSure' => 'Sei sicuro di voler eliminare la valuta ":name"?', - 'piggyBank_areYouSure' => 'Sei sicuro di voler eliminare il salvadanaio ":name"?', - 'journal_areYouSure' => 'Sei sicuro di voler eliminare la transazione ":description"?', - 'mass_journal_are_you_sure' => 'Sei sicuro di voler eliminare queste transazioni?', - 'tag_areYouSure' => 'Sei sicuro di voler eliminare l\'etichetta ":tag"?', - 'journal_link_areYouSure' => 'Sei sicuro di voler eliminare il collegamento tra :source e :destination?', - 'linkType_areYouSure' => 'Sei sicuro di voler eliminare il tipo di collegamento ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'L\'eliminazione dei dati da Firefly III è permanente e non può essere annullata.', - 'mass_make_selection' => 'Puoi comunque impedire l\'eliminazione degli elementi rimuovendo la spunta nella casella di controllo.', - 'delete_all_permanently' => 'Elimina selezionato in modo permanente', - 'update_all_journals' => 'Aggiorna queste transazioni', - 'also_delete_transactions' => 'Bene! verrà cancellata anche l\'unica transazione connessa a questo conto. | Bene! Tutte :count le transazioni di conteggio collegate a questo conto verranno eliminate.', - 'also_delete_connections' => 'L\'unica transazione collegata a questo tipo di collegamento perderà questa connessione. | Tutto :count le transazioni di conteggio collegate a questo tipo di collegamento perderanno la connessione.', - 'also_delete_rules' => 'Anche l\'unica regola collegata a questo gruppo di regole verrà eliminata. | Tutto :count verranno eliminate anche le regole di conteggio collegate a questo gruppo di regole.', - 'also_delete_piggyBanks' => 'Verrà eliminato anche l\'unico salvadanaio collegato a questo conto. | Tutti :count il conteggio del salvadanaio collegato a questo conto verrà eliminato.', - 'bill_keep_transactions' => 'L\'unica transazione connessa a questa bolletta non verrà eliminata. | Tutte le :count transazioni del conto collegate a questa bolletta non verranno cancellate.', - 'budget_keep_transactions' => 'L\'unica transazione collegata a questo budget non verrà eliminata. | Tutte le :count transazioni del conto collegate a questo budget non verranno cancellate.', - 'category_keep_transactions' => 'L\'unica transazione collegata a questa categoria non verrà eliminata. | Tutto :count le transazioni del conto collegate a questa categoria non verranno cancellate.', - 'tag_keep_transactions' => 'L\'unica transazione connessa a questa etichetta non verrà eliminata. | Tutto :count le transazioni del conto collegate a questa etichetta non verranno cancellate.', - 'check_for_updates' => 'Controlla gli aggiornamenti', + 'amount' => 'Importo', + 'foreign_amount' => 'Importo estero', + 'existing_attachments' => 'Allegati esistenti', + 'date' => 'Data', + 'interest_date' => 'Data interesse', + 'book_date' => 'Agenda', + 'process_date' => 'Data elaborazione', + 'category' => 'Categoria', + 'tags' => 'Etichette', + 'deletePermanently' => 'Elimina definitivamente', + 'cancel' => 'Annulla', + 'targetdate' => 'Data fine', + 'startdate' => 'Data inizio', + 'tag' => 'Etichetta', + 'under' => 'Sotto', + 'symbol' => 'Simbolo', + 'code' => 'Codice', + 'iban' => 'IBAN', + 'accountNumber' => 'Numero conto', + 'creditCardNumber' => 'Numero carta di credito', + 'has_headers' => 'Intestazioni', + 'date_format' => 'Formato data', + 'specifix' => 'Correzioni bancarie o file specifiche', + 'attachments[]' => 'Allegati', + 'store_new_withdrawal' => 'Salva nuovo prelievo', + 'store_new_deposit' => 'Salva nuovo deposito', + 'store_new_transfer' => 'Salva nuova trasferimento', + 'add_new_withdrawal' => 'Aggiungi nuovo prelievo', + 'add_new_deposit' => 'Aggiungi nuovo deposito', + 'add_new_transfer' => 'Aggiungi un nuovo trasferimento', + 'title' => 'Titolo', + 'notes' => 'Note', + 'filename' => 'Nome file', + 'mime' => 'Tipo Mime', + 'size' => 'Dimensione', + 'trigger' => 'Trigger', + 'stop_processing' => 'Interrompere l\'elaborazione', + 'start_date' => 'Inizio periodo', + 'end_date' => 'Fine periodo', + 'export_start_range' => 'Data iniziale dell\'intervallo d\'esportazione', + 'export_end_range' => 'Data finale dell\'intervallo d\'esportazione', + 'export_format' => 'Formato file', + 'include_attachments' => 'Includi allegati caricati', + 'include_old_uploads' => 'Includi dati importati', + 'accounts' => 'Esporta le transazioni da questi conti', + 'delete_account' => 'Elimina conto ":name"', + 'delete_bill' => 'Elimina bolletta ":name"', + 'delete_budget' => 'Elimina budget ":name"', + 'delete_category' => 'Elimina categoria ":name"', + 'delete_currency' => 'Elimina valuta ":name"', + 'delete_journal' => 'Elimina transazione con descrizione ":description"', + 'delete_attachment' => 'Elimina allegato ":name"', + 'delete_rule' => 'Elimina regola ":title"', + 'delete_rule_group' => 'Elimina gruppo regole":title"', + 'delete_link_type' => 'Elimina tipo collegamento ":name"', + 'delete_user' => 'Elimina utente ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Se cancelli l\'utente ":email", verrà eliminato tutto. Non sarà più possibile recuperare i dati eliminati. Se cancelli te stesso, perderai l\'accesso a questa istanza di Firefly III.', + 'attachment_areYouSure' => 'Sei sicuro di voler eliminare l\'allegato ":name"?', + 'account_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', + 'bill_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', + 'rule_areYouSure' => 'Sei sicuro di voler eliminare la regola ":title"?', + 'ruleGroup_areYouSure' => 'Sei sicuro di voler eliminare il gruppo regole ":title"?', + 'budget_areYouSure' => 'Sei sicuro di voler eliminare il budget ":name"?', + 'category_areYouSure' => 'Sei sicuro di voler eliminare categoria ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Sei sicuro di voler eliminare la valuta ":name"?', + 'piggyBank_areYouSure' => 'Sei sicuro di voler eliminare il salvadanaio ":name"?', + 'journal_areYouSure' => 'Sei sicuro di voler eliminare la transazione ":description"?', + 'mass_journal_are_you_sure' => 'Sei sicuro di voler eliminare queste transazioni?', + 'tag_areYouSure' => 'Sei sicuro di voler eliminare l\'etichetta ":tag"?', + 'journal_link_areYouSure' => 'Sei sicuro di voler eliminare il collegamento tra :source e :destination?', + 'linkType_areYouSure' => 'Sei sicuro di voler eliminare il tipo di collegamento ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'L\'eliminazione dei dati da Firefly III è definitiva e non può essere annullata.', + 'mass_make_selection' => 'Puoi comunque impedire l\'eliminazione degli elementi rimuovendo la spunta nella casella di controllo.', + 'delete_all_permanently' => 'Elimina selezionati definitamente', + 'update_all_journals' => 'Aggiorna queste transazioni', + 'also_delete_transactions' => 'Anche l\'unica transazione collegata a questo conto verrà eliminata.|Anche tutte le :count transazioni collegate a questo conto verranno eliminate.', + 'also_delete_connections' => 'L\'unica transazione collegata a questo tipo di collegamento perderà questa connessione. | Tutto :count le transazioni di conteggio collegate a questo tipo di collegamento perderanno la connessione.', + 'also_delete_rules' => 'Anche l\'unica regola collegata a questo gruppo di regole verrà eliminata. | Tutto :count verranno eliminate anche le regole di conteggio collegate a questo gruppo di regole.', + 'also_delete_piggyBanks' => 'Verrà eliminato anche l\'unico salvadanaio collegato a questo conto. | Tutti :count il conteggio del salvadanaio collegato a questo conto verrà eliminato.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Controlla gli aggiornamenti', - 'email' => 'Indirizzo Email', + 'email' => 'Indirizzo email', 'password' => 'Password', 'password_confirmation' => 'Password (ancora)', 'blocked' => 'È bloccato?', @@ -201,13 +204,13 @@ return [ 'import_file' => 'Importa file', 'configuration_file' => 'Configurazione file', 'import_file_type' => 'Importa tipo file', - 'csv_comma' => 'A virgola (,)', - 'csv_semicolon' => 'A punto e virgola (;)', - 'csv_tab' => 'A spazio (invisibile)', + 'csv_comma' => 'Una virgola (,)', + 'csv_semicolon' => 'Un punto e virgola (;)', + 'csv_tab' => 'Una tabulazione (invisibile)', 'csv_delimiter' => 'Delimitatore campi CSV', 'csv_import_account' => 'Conto di importazione predefinito', 'csv_config' => 'Configurazione importa CSV', - 'client_id' => 'ID Client', + 'client_id' => 'Client ID', 'service_secret' => 'Servizio segreto', 'app_secret' => 'App segreto', 'app_id' => 'ID dell\'app', @@ -218,20 +221,21 @@ return [ 'due_date' => 'Data scadenza', 'payment_date' => 'Data pagamento', - 'invoice_date' => 'Data bolletta', + 'invoice_date' => 'Data fatturazione', 'internal_reference' => 'Referenze interne', 'inward' => 'Descrizione interna', 'outward' => 'Descrizione esterna', 'rule_group_id' => 'Gruppo regole', - 'transaction_description' => 'Transaction description', - 'first_date' => 'First date', - 'transaction_type' => 'Transaction type', - 'repeat_until' => 'Repeat until', - 'recurring_description' => 'Recurring transaction description', - 'repetition_type' => 'Type of repetition', - 'foreign_currency_id' => 'Foreign currency', - 'repetition_end' => 'Repetition ends', - 'repetitions' => 'Repetitions', - 'calendar' => 'Calendar', + 'transaction_description' => 'Descrizione transazione', + 'first_date' => 'Prima volta', + 'transaction_type' => 'Tipo transazione', + 'repeat_until' => 'Ripeti fino a', + 'recurring_description' => 'Descrizione transazione ricorrente', + 'repetition_type' => 'Tipo ripetizione', + 'foreign_currency_id' => 'Valuta estera', + 'repetition_end' => 'La ripetizione termina il', + 'repetitions' => 'Ripetizioni', + 'calendar' => 'Calendario', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index 44210b0a23..d8e5d65400 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -30,7 +30,7 @@ return [ 'prerequisites_breadcrumb_bunq' => 'Prerequisiti per bunq', 'job_configuration_breadcrumb' => 'Configurazione per ":key"', 'job_status_breadcrumb' => 'Stato di importazione per ":key"', - 'cannot_create_for_provider' => 'Firefly III non può creare un lavoro per il provider ":provider".', + 'cannot_create_for_provider' => 'Firefly III non può creare un\'operazione per il provider ":provider".', // index page: 'general_index_title' => 'Importa un file', @@ -82,7 +82,7 @@ return [ 'prerequisites_saved_for_bunq' => 'Chiave API e IP memorizzati!', // job configuration: - 'job_config_apply_rules_title' => 'Configurazione del lavoro - applicare le tue regole?', + 'job_config_apply_rules_title' => 'Configurazione dell\'operazione - applicare le tue regole?', 'job_config_apply_rules_text' => 'Una volta avviato il fornitore fittizio, le tue regole possono essere applicate alle transazioni. Questo aggiunge del tempo all\'importazione.', 'job_config_input' => 'Il tuo input', // job configuration for the fake provider: @@ -121,26 +121,29 @@ return [ 'spectre_login_status_disabled' => 'Disabilitato', 'spectre_login_new_login' => 'Accedi con un\'altra banca o con una di queste banche con credenziali diverse.', 'job_config_spectre_accounts_title' => 'Seleziona i conti dai quali importare', - 'job_config_spectre_accounts_text' => 'Hai selezionato ":name" (:country). Hai :count conti disponibili da questo fornitore. Seleziona i conti attività di Firefly III in cui devono essere memorizzate le transazioni da questi conti. Ricorda che, per importare i dati, sia l\'account Firefly III sia l\'account ":name" devono avere la stessa valuta.', + 'job_config_spectre_accounts_text' => 'Hai selezionato ":name" (:country). Hai :count conti disponibili da questo fornitore. Seleziona i conti attività di Firefly III in cui devono essere memorizzate le transazioni da questi conti. Ricorda che, per importare i dati, sia il conto di Firefly III sia il conto ":name" devono avere la stessa valuta.', 'spectre_no_supported_accounts' => 'Non puoi importare da questo conto perché le valute non corrispondono.', 'spectre_do_not_import' => '(non importare)', 'spectre_no_mapping' => 'Sembra che tu non abbia selezionato nessun account da cui importare.', 'imported_from_account' => 'Importato da ":account"', 'spectre_account_with_number' => 'Conto :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'Account bunq', 'job_config_bunq_accounts_text' => 'Questi sono i conti associati al tuo account bunq. Seleziona i conti dai quali vuoi effettuare l\'importazione e in quale conto devono essere importate le transazioni.', - 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato nessun account.', - 'should_download_config' => 'Ti consigliamo di scaricare il file di configurazione per questo lavoro. Ciò renderà le importazioni future più facili.', + 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato alcun conto.', + 'should_download_config' => 'Ti consigliamo di scaricare il file di configurazione per questa operazione. Ciò renderà le importazioni future più facili.', 'share_config_file' => 'Se hai importato dati da una banca pubblica, dovresti condividere il tuo file di configurazione così da rendere più facile per gli altri utenti importare i loro dati. La condivisione del file di configurazione non espone i tuoi dettagli finanziari.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', 'spectre_extra_key_status' => 'Stato', 'spectre_extra_key_card_type' => 'Tipo carta', 'spectre_extra_key_account_name' => 'Nome conto', - 'spectre_extra_key_client_name' => 'Nome cliente', + 'spectre_extra_key_client_name' => 'Nome client', 'spectre_extra_key_account_number' => 'Numero conto', 'spectre_extra_key_blocked_amount' => 'Importo bloccato', 'spectre_extra_key_available_amount' => 'Importo disponibile', @@ -176,7 +179,7 @@ return [ 'job_config_roles_do_map_value' => 'Mappa questi valori', 'job_config_roles_no_example' => 'Nessun dato di esempio disponibile', 'job_config_roles_fa_warning' => 'Se contrassegni una colonna come contenente un importo in una valuta straniera, devi anche impostare la colonna che contiene di quale valuta si tratta.', - 'job_config_roles_rwarning' => 'Come minimo, contrassegna una colonna come colonna dell\'importo. Si consiglia di selezionare anche una colonna per la descrizione, la data e il conto opposto.', + 'job_config_roles_rwarning' => 'Come minimo contrassegna una colonna come colonna dell\'importo. Si consiglia di selezionare anche una colonna per la descrizione, la data e il conto della controparte.', 'job_config_roles_colum_count' => 'Colonna', // job config for the file provider (stage: mapping): 'job_config_map_title' => 'Configurazione di importazione (4/4) - Collega i dati importati con i dati di Firefly III', @@ -209,29 +212,29 @@ return [ // general errors and warnings: - 'bad_job_status' => 'Per accedere a questa pagina, il tuo lavoro di importazione non può avere lo stato ":status".', + 'bad_job_status' => 'Per accedere a questa pagina l\'operazione di importazione non può avere lo stato ":status".', // column roles for CSV import: 'column__ignore' => '(ignora questa colonna)', - 'column_account-iban' => 'Conto patrimonio (IBAN)', - 'column_account-id' => 'Conto patrimonio ID (matching FF3)', - 'column_account-name' => 'Conto patrimonio (nome)', + 'column_account-iban' => 'Conto attività (IBAN)', + 'column_account-id' => 'ID conto attività (mappa FF3)', + 'column_account-name' => 'Conto attività (nome)', 'column_amount' => 'Importo', 'column_amount_foreign' => 'Importo (in altra valuta)', 'column_amount_debit' => 'Importo (colonna debito)', 'column_amount_credit' => 'Importo (colonna credito)', 'column_amount-comma-separated' => 'Importo (virgola come separatore decimale)', - 'column_bill-id' => 'ID conto (matching FF3)', + 'column_bill-id' => 'ID bolletta (mappa FF3)', 'column_bill-name' => 'Nome conto', - 'column_budget-id' => 'ID budget (matching FF3)', + 'column_budget-id' => 'ID budget (mappa FF3)', 'column_budget-name' => 'Nome budget', - 'column_category-id' => 'ID Categoria (matching FF3)', + 'column_category-id' => 'ID categoria (mappa FF3)', 'column_category-name' => 'Nome categoria', 'column_currency-code' => 'Codice valuta (ISO 4217)', 'column_foreign-currency-code' => 'Codice valuta straniera (ISO 4217)', - 'column_currency-id' => 'ID valuta (matching FF3)', - 'column_currency-name' => 'Nome valuta (matching FF3)', - 'column_currency-symbol' => 'Simbolo valuta (matching FF3)', + 'column_currency-id' => 'ID valuta (mappa FF3)', + 'column_currency-name' => 'Nome valuta (mappa FF3)', + 'column_currency-symbol' => 'Simbolo valuta (mappa FF3)', 'column_date-interest' => 'Data calcolo interessi', 'column_date-book' => 'Data prenotazione della transazione', 'column_date-process' => 'Data processo della transazione', @@ -240,15 +243,15 @@ return [ 'column_date-payment' => 'Data di pagamento della transazione', 'column_date-invoice' => 'Data di fatturazione della transazione', 'column_description' => 'Descrizione', - 'column_opposing-iban' => 'Conto opposto (IBAN)', - 'column_opposing-bic' => 'Conto della controparte (BIC)', - 'column_opposing-id' => 'ID Conto opposto (matching FF3)', + 'column_opposing-iban' => 'Conto controparte (IBAN)', + 'column_opposing-bic' => 'Conto controparte (BIC)', + 'column_opposing-id' => 'ID conto controparte (mappa FF3)', 'column_external-id' => 'ID esterno', - 'column_opposing-name' => 'Conto opposto (nome)', + 'column_opposing-name' => 'Conto controparte (nome)', 'column_rabo-debit-credit' => 'Indicatore Rabo di addebito / accredito specifico della banca', 'column_ing-debit-credit' => 'Indicatore di debito / credito specifico ING', 'column_sepa-ct-id' => 'ID end-to-end del bonifico SEPA', - 'column_sepa-ct-op' => 'Conto opposto bonifico SEPA', + 'column_sepa-ct-op' => 'Identificatore SEPA conto controparte', 'column_sepa-db' => 'Addebito diretto SEPA', 'column_sepa-cc' => 'Codice Compensazione SEPA', 'column_sepa-ci' => 'Identificativo Creditore SEPA', @@ -256,9 +259,9 @@ return [ 'column_sepa-country' => 'Codice Paese SEPA', 'column_tags-comma' => 'Etichette (separate da virgola)', 'column_tags-space' => 'Etichette (separate con spazio)', - 'column_account-number' => 'Conto patrimonio (numero conto)', - 'column_opposing-number' => 'Conto opposto (numero conto)', - 'column_note' => 'Nota(e)', + 'column_account-number' => 'Conto attività (numero conto)', + 'column_opposing-number' => 'Conto controparte (numero conto)', + 'column_note' => 'Note', 'column_internal-reference' => 'Riferimento interno', ]; diff --git a/resources/lang/it_IT/intro.php b/resources/lang/it_IT/intro.php index a5b2a44c83..70721ef6e1 100644 --- a/resources/lang/it_IT/intro.php +++ b/resources/lang/it_IT/intro.php @@ -25,7 +25,7 @@ declare(strict_types=1); return [ // index 'index_intro' => 'Benvenuti nella pagina indice di Firefly III. Si prega di prendersi il tempo necessario per questa introduzione per avere un\'idea di come funziona Firefly III.', - 'index_accounts-chart' => 'Questo grafico mostra il saldo attuale dei contit risorse. Puoi selezionare gli conti visibili qui nelle tue preferenze.', + 'index_accounts-chart' => 'Questo grafico mostra il saldo attuale dei conti attività. Puoi selezionare i conti visibili qui nelle tue preferenze.', 'index_box_out_holder' => 'Questa piccola casella e le caselle accanto a questa ti daranno una rapida panoramica della tua situazione finanziaria.', 'index_help' => 'Se hai bisogno di aiuto per una pagina o un modulo, premi questo pulsante.', 'index_outro' => 'La maggior parte delle pagine di Firefly III inizieranno con un piccolo tour come questo. Vi prego di contattarci quando avete domande o commenti. Grazie!', @@ -35,7 +35,7 @@ return [ 'accounts_create_iban' => 'Dai ai tuoi conti un IBAN valido. Ciò potrebbe rendere molto facile l\'importazione dei dati in futuro.', 'accounts_create_asset_opening_balance' => 'I conti attività possono avere un "saldo di apertura", che indica l\'inizio della cronologia di questo conto in Firefly III.', 'accounts_create_asset_currency' => 'Firefly III supporta più valute. I conti attività hanno una valuta principale, che devi impostare qui.', - 'accounts_create_asset_virtual' => 'A volte può aiutare a fornire al tuo conto un saldo virtuale: un importo aggiuntivo sempre aggiunto o rimosso dal saldo effettivo.', + 'accounts_create_asset_virtual' => 'A volte può aiutare a fornire al tuo conto un saldo virtuale: un ulteriore importo sempre aggiunto o rimosso dal saldo effettivo.', // budgets index 'budgets_index_intro' => 'I budget sono usati per gestire le tue finanze e formano una delle funzioni principali di Firefly III.', @@ -59,11 +59,11 @@ return [ 'reports_report_audit_optionsBox' => 'Utilizza queste caselle di controllo per mostrare o nascondere le colonne che ti interessano.', 'reports_report_category_intro' => 'Questo resoconto ti fornirà informazioni su una o più categorie.', - 'reports_report_category_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e del reddito per categoria o per conto.', - 'reports_report_category_incomeAndExpensesChart' => 'Questo grafico mostra le tue spese e il reddito per categoria.', + 'reports_report_category_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e delle entrate per categoria o per conto.', + 'reports_report_category_incomeAndExpensesChart' => 'Questo grafico mostra le tue spese e le tue entrate per categoria.', 'reports_report_tag_intro' => 'Questo resoconto ti fornirà informazioni su uno o più etichette.', - 'reports_report_tag_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e del reddito per etichetta, conto, categoria o budget.', + 'reports_report_tag_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e delle entrate per etichetta, conto, categoria o budget.', 'reports_report_tag_incomeAndExpensesChart' => 'Questo grafico mostra le tue spese e entrate per etichetta.', 'reports_report_budget_intro' => 'Questo resoconto ti fornirà informazioni su uno o più budget.', @@ -85,7 +85,7 @@ return [ // create piggy 'piggy-banks_create_name' => 'Qual è il tuo obiettivo? Un nuovo divano, una macchina fotografica, soldi per le emergenze?', - 'piggy-banks_create_date' => 'È possibile impostare una data di destinazione o una scadenza per il salvadanaio.', + 'piggy-banks_create_date' => 'È possibile impostare una data come obiettivo o una scadenza per il salvadanaio.', // show piggy 'piggy-banks_show_piggyChart' => 'Questo grafico mostrerà lo storico di questo salvadanaio.', @@ -94,7 +94,7 @@ return [ // bill index 'bills_index_rules' => 'Qui puoi vedere quali regole verranno controllate se questa bolletta viene "toccata"', - 'bills_index_paid_in_period' => 'Questo campo indica quando il conto è stato pagato l\'ultima volta.', + 'bills_index_paid_in_period' => 'Questo campo indica quando la bolletta è stato pagata l\'ultima volta.', 'bills_index_expected_in_period' => 'Questo campo indica per ciascuna bolletta se e quando ci si aspetta che la bolletta successiva arrivi.', // show bill @@ -111,7 +111,7 @@ return [ 'bills_create_skip_holder' => 'Se una bolletta si ripete ogni 2 settimane, il campo "salta" deve essere impostato a "1" per saltare ogni volta una settimana.', // rules index - 'rules_index_intro' => 'Firefly III ti consente di gestire le regole, che verranno automaticamente applicate a qualsiasi transazione creata o modificata.', + 'rules_index_intro' => 'Firefly III ti consente di gestire delle regole che verranno automaticamente applicate a qualsiasi transazione creata o modificata.', 'rules_index_new_rule_group' => 'È possibile combinare le regole in gruppi per una gestione più semplice.', 'rules_index_new_rule' => 'Crea quante regole desideri.', 'rules_index_prio_buttons' => 'Ordinali come meglio credi.', diff --git a/resources/lang/it_IT/list.php b/resources/lang/it_IT/list.php index dbece38b90..a4d4346fd7 100644 --- a/resources/lang/it_IT/list.php +++ b/resources/lang/it_IT/list.php @@ -26,21 +26,21 @@ return [ 'buttons' => 'Pulsanti', 'icon' => 'Icona', 'id' => 'ID', - 'create_date' => 'Creato a', - 'update_date' => 'Aggiornato a', - 'updated_at' => 'Aggiornato a', + 'create_date' => 'Creato il', + 'update_date' => 'Aggiornato il', + 'updated_at' => 'Aggiornato il', 'balance_before' => 'Bilancio prima', 'balance_after' => 'Bilancio dopo', 'name' => 'Nome', 'role' => 'Ruolo', - 'currentBalance' => 'Bilancio corrente', + 'currentBalance' => 'Saldo corrente', 'linked_to_rules' => 'Regole rilevanti', 'active' => 'Attivo', 'lastActivity' => 'Ultima attività', - 'balanceDiff' => 'Differenze bilancio', - 'matchesOn' => 'Abbinato', + 'balanceDiff' => 'Differenze saldi', + 'matchesOn' => 'Abbinato con', 'account_type' => 'Tipo conto', - 'created_at' => 'Creato a', + 'created_at' => 'Creato il', 'account' => 'Conto', 'matchingAmount' => 'Importo', 'split_number' => 'Diviso #', @@ -48,7 +48,7 @@ return [ 'source' => 'Sorgente', 'next_expected_match' => 'Prossimo abbinamento previsto', 'automatch' => 'Abbinamento automatico?', - 'repeat_freq' => 'Ripetizioni', + 'repeat_freq' => 'Si ripete', 'description' => 'Descrizione', 'amount' => 'Importo', 'internal_reference' => 'Referenze interne', @@ -58,7 +58,7 @@ return [ 'process_date' => 'Data lavorazione', 'due_date' => 'Data scadenza', 'payment_date' => 'Data pagamento', - 'invoice_date' => 'Data bolletta', + 'invoice_date' => 'Data fatturazione', 'interal_reference' => 'Referenze interne', 'notes' => 'Note', 'from' => 'Da', @@ -67,46 +67,46 @@ return [ 'budget' => 'Budget', 'category' => 'Categoria', 'bill' => 'Conto', - 'withdrawal' => 'Spesa', + 'withdrawal' => 'Prelievo', 'deposit' => 'Deposito', 'transfer' => 'Trasferimento', 'type' => 'Tipo', 'completed' => 'Completato', 'iban' => 'IBAN', - 'paid_current_period' => 'Pagato in questo periodo', + 'paid_current_period' => 'Pagati in questo periodo', 'email' => 'Email', - 'registered_at' => 'Registrato a', + 'registered_at' => 'Registrato il', 'is_blocked' => 'È bloccato', 'is_admin' => 'È amministratore', - 'has_two_factor' => 'Ha 2 Fattori', + 'has_two_factor' => 'Ha 2FA', 'blocked_code' => 'Codice blocco', 'source_account' => 'Conto sorgente', 'destination_account' => 'Conto destinazione', - 'accounts_count' => 'Numero conti', - 'journals_count' => 'Numero transazioni', - 'attachments_count' => 'Numero allegati', - 'bills_count' => 'Numero conti', - 'categories_count' => 'Numero categorie', - 'export_jobs_count' => 'Numero esportazioni', - 'import_jobs_count' => 'Numero importazioni', + 'accounts_count' => 'Numero di conti', + 'journals_count' => 'Numero di transazioni', + 'attachments_count' => 'Numero di allegati', + 'bills_count' => 'Numero di bollette', + 'categories_count' => 'Numero di categorie', + 'export_jobs_count' => 'Numero delle operazioni di esportazione', + 'import_jobs_count' => 'Numero delle operazioni di importazione', 'budget_count' => 'Numero di budget', - 'rule_and_groups_count' => 'Numero regole e gruppi regole', - 'tags_count' => 'Numero etichette', + 'rule_and_groups_count' => 'Numero di regole e gruppi di regole', + 'tags_count' => 'Numero di etichette', 'tags' => 'Etichette', 'inward' => 'Descrizione interna', 'outward' => 'Descrizione esterna', - 'number_of_transactions' => 'Numero transazioni', + 'number_of_transactions' => 'Numero di transazioni', 'total_amount' => 'Importo totale', 'sum' => 'Somma', 'sum_excluding_transfers' => 'Somma (esclusi i trasferimenti)', - 'sum_withdrawals' => 'Somma prelievi', + 'sum_withdrawals' => 'Somma dei prelievi', 'sum_deposits' => 'Somma versamenti', 'sum_transfers' => 'Somma dei trasferimenti', 'reconcile' => 'Riconcilia', 'account_on_spectre' => 'Conto (Spectre)', 'do_import' => 'Importo da questo conto', 'sepa-ct-id' => 'Identificativo End-To-End SEPA', - 'sepa-ct-op' => 'Identificativo Conto Oppositore SEPA', + 'sepa-ct-op' => 'Identificativo SEPA Conto Controparte', 'sepa-db' => 'Identificativo Mandato SEPA', 'sepa-country' => 'Paese SEPA', 'sepa-cc' => 'Codice Compensazione SEPA', @@ -123,9 +123,9 @@ return [ 'spectre_last_use' => 'Ultimo accesso', 'spectre_status' => 'Stato', 'bunq_payment_id' => 'ID pagamento bunq', - 'repetitions' => 'Repetitions', - 'title' => 'Title', - 'transaction_s' => 'Transaction(s)', - 'field' => 'Field', - 'value' => 'Value', + 'repetitions' => 'Ripetizioni', + 'title' => 'Titolo', + 'transaction_s' => 'Transazioni', + 'field' => 'Campo', + 'value' => 'Valore', ]; diff --git a/resources/lang/it_IT/passwords.php b/resources/lang/it_IT/passwords.php index 3e44682679..5261554c24 100644 --- a/resources/lang/it_IT/passwords.php +++ b/resources/lang/it_IT/passwords.php @@ -24,9 +24,9 @@ declare(strict_types=1); return [ 'password' => 'Le password devono contenere almeno sei caratteri e devono corrispondere alla conferma.', - 'user' => 'Non possiamo trovare un utente con questo indirizzo e-mail.', + 'user' => 'Non riusciamo a trovare un utente con questo indirizzo e-mail.', 'token' => 'Questo token di reimpostazione della password non è valido.', - 'sent' => 'Abbiamo inviato via e-mail il tuo link per la reimpostazione della password!', + 'sent' => 'Abbiamo inviato via e-mail il link per la reimpostazione della password!', 'reset' => 'La tua password è stata resettata!', 'blocked' => 'Riprova.', ]; diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index 944289803d..3232c022c7 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'Il valore di :attribute è sconosciuto', 'accepted' => 'L\' :attribute deve essere accettato.', 'bic' => 'Questo non è un BIC valido.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute deve essere maggiore di zero.', 'active_url' => ':attribute non è un URL valido.', 'after' => ':attribute deve essere una data dopo :date.', @@ -67,7 +69,7 @@ return [ 'digits' => ':attribute deve essere :digits cifre.', 'digits_between' => ':attribute deve essere :min e :max cifre.', 'email' => ':attribute deve essere un indirizzo email valido.', - 'filled' => ':attribute il campo è obbligatiorio.', + 'filled' => 'Il campo :attribute è obbligatorio.', 'exists' => ':attribute selezionato non è valido.', 'image' => ':attribute deve essere un\'immagine.', 'in' => ':attribute selezionato non è valido.', @@ -86,13 +88,13 @@ return [ 'not_in' => ':attribute selezionato è invalido.', 'numeric' => ':attribute deve essere un numero.', 'regex' => ':attribute formato non valido', - 'required' => ':attribute il campo è obbligatiorio.', - 'required_if' => ':attribute il campo è richiesto quando :other is :value.', - 'required_unless' => ':attribute il campo è richiesto a meno che :other è in :values.', - 'required_with' => ':attribute il campo è richiesto quando :values è presente.', - 'required_with_all' => ':attribute il campo è richiesto quando :values è presente.', - 'required_without' => ':attribute il campo è richiesto quando :values non è presente.', - 'required_without_all' => ':attribute il campo è richiesto quando nessuno di :values sono presenti.', + 'required' => 'Il campo :attribute è obbligatorio.', + 'required_if' => 'Il campo :attribute è obbligatorio quando :other è :value.', + 'required_unless' => 'Il campo :attribute è obbligatorio a meno che :other è in :values.', + 'required_with' => 'Il campo :attribute è obbligatorio quando :values è presente.', + 'required_with_all' => 'Il campo :attribute è obbligatorio quando :values è presente.', + 'required_without' => 'Il campo :attribute è obbligatorio quando :values non è presente.', + 'required_without_all' => 'Il campo :attribute è obbligatorio quando nessuno di :values è presente.', 'same' => ':attribute e :other deve combaciare.', 'size.numeric' => ':attribute deve essere :size.', 'amount_min_over_max' => 'L\'importo minimo non può essere maggiore dell\'importo massimo.', @@ -103,7 +105,7 @@ return [ 'string' => ':attribute deve essere una stringa.', 'url' => ':attribute il formato non è valido.', 'timezone' => ':attribute deve essere una zona valida.', - '2fa_code' => ':attribute il campo non è valido.', + '2fa_code' => 'Il campo :attribute non è valido.', 'dimensions' => ':attribute ha dimensioni di immagine non valide.', 'distinct' => ':attribute il campo ha un valore doppio.', 'file' => ':attribute deve essere un file.', @@ -112,7 +114,7 @@ return [ 'amount_zero' => 'L\'importo totale non può essere zero', 'unique_piggy_bank_for_user' => 'Il nome del salvadanaio deve essere unico.', 'secure_password' => 'Questa non è una password sicura. Per favore riprova. Per ulteriori informazioni, visitare http://bit.ly/FF3-password-security', - 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', + 'valid_recurrence_rep_type' => 'Tipo di ripetizione non valida per la transazione ricorrente', 'attributes' => [ 'email' => 'indirizzo email', 'description' => 'descrizione', @@ -120,9 +122,9 @@ return [ 'name' => 'nome', 'piggy_bank_id' => 'ID salvadanaio', 'targetamount' => 'importo obiettivo', - 'openingBalanceDate' => 'data bilancio apertura', + 'openingBalanceDate' => 'data saldo di apertura', 'openingBalance' => 'saldo apertura', - 'match' => 'partita', + 'match' => 'abbinamento', 'amount_min' => 'importo minimo', 'amount_max' => 'importo massimo', 'title' => 'titolo', diff --git a/resources/lang/nl_NL/config.php b/resources/lang/nl_NL/config.php index aa5530210a..aab20ae1cb 100644 --- a/resources/lang/nl_NL/config.php +++ b/resources/lang/nl_NL/config.php @@ -27,7 +27,7 @@ return [ 'locale' => 'nl, Dutch, nl_NL, nl_NL.utf8, nl_NL.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', - 'month_and_date_day' => '%A %B %e %Y', + 'month_and_date_day' => '%A %e %B %Y', 'month_and_day_no_year' => '%B %e', 'date_time' => '%e %B %Y, @ %T', 'specific_day' => '%e %B %Y', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index cc3c55ffdf..4973ef5577 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scan deze QR code met een app op je telefoon (zoals Authy of Google Authenticator). Vul de code die je terug krijgt hier in.', 'pref_two_factor_auth_reset_code' => 'Reset de verificatiecode', 'pref_two_factor_auth_disable_2fa' => '2FA uitzetten', + '2fa_use_secret_instead' => 'Als je de QR code niet kan scannen gebruik dan de geheime code: :secret.', 'pref_save_settings' => 'Instellingen opslaan', 'saved_preferences' => 'Voorkeuren opgeslagen!', 'preferences_general' => 'Algemeen', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo aan het einde van de periode', 'splitByAccount' => 'Per betaalrekening', 'coveredWithTags' => 'Gecorrigeerd met tags', - 'leftUnbalanced' => 'Ongecorrigeerd', 'leftInBudget' => 'Over van budget', 'sumOfSums' => 'Alles bij elkaar', 'noCategory' => '(zonder categorie)', @@ -1260,4 +1260,15 @@ return [ 'update_recurrence' => 'Wijzig periodieke transactie', 'updated_recurrence' => 'Periodieke transactie ":title" is gewijzigd', 'recurrence_is_inactive' => 'Deze periodieke transactie is niet actief en maakt geen transacties aan.', + 'delete_recurring' => 'Verwijder periodieke transactie ":title"', + 'new_recurring_transaction' => 'Nieuwe periodieke transactie', + 'help_weekend' => 'Wat moet Firefly III doen als de periodieke transactie in het weekend valt?', + 'do_nothing' => 'Gewoon transactie maken', + 'skip_transaction' => 'Transactie overslaan', + 'jump_to_friday' => 'Transactie maken op de vrijdag ervoor', + 'jump_to_monday' => 'Transactie maken op de zaterdag erna', + 'will_jump_friday' => 'Wordt op de vrijdag ervoor gemaakt i.p.v. in het weekend.', + 'will_jump_monday' => 'Wordt op de maandag ervoor gemaakt i.p.v. in het weekend.', + 'except_weekends' => 'Behalve de weekenden', + 'recurrence_deleted' => 'Periodieke transactie ":title" verwijderd', ]; diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index e83fb084f0..2aec96d3d5 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Verander inkomsten', 'convert_Transfer' => 'Verander overschrijving', - 'amount' => 'Bedrag', - 'foreign_amount' => 'Bedrag in vreemde valuta', - 'existing_attachments' => 'Bestaande bijlagen', - 'date' => 'Datum', - 'interest_date' => 'Rentedatum', - 'book_date' => 'Boekdatum', - 'process_date' => 'Verwerkingsdatum', - 'category' => 'Categorie', - 'tags' => 'Tags', - 'deletePermanently' => 'Verwijderen', - 'cancel' => 'Annuleren', - 'targetdate' => 'Doeldatum', - 'startdate' => 'Startdatum', - 'tag' => 'Tag', - 'under' => 'Onder', - 'symbol' => 'Symbool', - 'code' => 'Code', - 'iban' => 'IBAN', - 'accountNumber' => 'Rekeningnummer', - 'creditCardNumber' => 'Creditcardnummer', - 'has_headers' => 'Kolomnamen op de eerste rij?', - 'date_format' => 'Datumformaat', - 'specifix' => 'Bank- or of bestandsspecifieke opties', - 'attachments[]' => 'Bijlagen', - 'store_new_withdrawal' => 'Nieuwe uitgave opslaan', - 'store_new_deposit' => 'Nieuwe inkomsten opslaan', - 'store_new_transfer' => 'Nieuwe overschrijving opslaan', - 'add_new_withdrawal' => 'Maak nieuwe uitgave', - 'add_new_deposit' => 'Maak nieuwe inkomsten', - 'add_new_transfer' => 'Maak nieuwe overschrijving', - 'title' => 'Titel', - 'notes' => 'Notities', - 'filename' => 'Bestandsnaam', - 'mime' => 'Bestandstype', - 'size' => 'Grootte', - 'trigger' => 'Trigger', - 'stop_processing' => 'Stop met verwerken', - 'start_date' => 'Start van bereik', - 'end_date' => 'Einde van bereik', - 'export_start_range' => 'Start van exportbereik', - 'export_end_range' => 'Einde van exportbereik', - 'export_format' => 'Bestandsformaat', - 'include_attachments' => 'Sla ook geüploade bijlagen op', - 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op', - 'accounts' => 'Exporteer boekingen van deze rekeningen', - 'delete_account' => 'Verwijder rekening ":name"', - 'delete_bill' => 'Verwijder contract ":name"', - 'delete_budget' => 'Verwijder budget ":name"', - 'delete_category' => 'Verwijder categorie ":name"', - 'delete_currency' => 'Verwijder valuta ":name"', - 'delete_journal' => 'Verwijder transactie met omschrijving ":description"', - 'delete_attachment' => 'Verwijder bijlage ":name"', - 'delete_rule' => 'Verwijder regel ":title"', - 'delete_rule_group' => 'Verwijder regelgroep ":title"', - 'delete_link_type' => 'Verwijder linktype ":name"', - 'delete_user' => 'Verwijder gebruiker ":email"', - 'user_areYouSure' => 'Als je gebruiker ":email" verwijdert is alles weg. Je kan dit niet ongedaan maken of ont-verwijderen of wat dan ook. Als je jezelf verwijdert ben je ook je toegang tot deze installatie van Firefly III kwijt.', - 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?', - 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?', - 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?', - 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?', - 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?', - 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?', - 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?', - 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?', - 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?', - 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?', - 'mass_journal_are_you_sure' => 'Weet je zeker dat je al deze transacties wilt verwijderen?', - 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?', - 'journal_link_areYouSure' => 'Weet je zeker dat je de koppeling tussen :source en :destination wilt verwijderen?', - 'linkType_areYouSure' => 'Weet je zeker dat je linktype ":name" (":inward" / ":outward") wilt verwijderen?', - 'permDeleteWarning' => 'Dingen verwijderen uit Firefly III is permanent en kan niet ongedaan gemaakt worden.', - 'mass_make_selection' => 'Je kan items alsnog redden van de ondergang door het vinkje weg te halen.', - 'delete_all_permanently' => 'Verwijder geselecteerde items permanent', - 'update_all_journals' => 'Wijzig deze transacties', - 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.', - 'also_delete_connections' => 'De enige transactie gelinkt met dit linktype zal deze verbinding verliezen. | Alle :count transacties met dit linktype zullen deze verbinding verliezen.', - 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.', - 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.', - 'bill_keep_transactions' => 'De transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.', - 'budget_keep_transactions' => 'De transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.', - 'category_keep_transactions' => 'De transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.', - 'tag_keep_transactions' => 'De transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.', - 'check_for_updates' => 'Op updates controleren', + 'amount' => 'Bedrag', + 'foreign_amount' => 'Bedrag in vreemde valuta', + 'existing_attachments' => 'Bestaande bijlagen', + 'date' => 'Datum', + 'interest_date' => 'Rentedatum', + 'book_date' => 'Boekdatum', + 'process_date' => 'Verwerkingsdatum', + 'category' => 'Categorie', + 'tags' => 'Tags', + 'deletePermanently' => 'Verwijderen', + 'cancel' => 'Annuleren', + 'targetdate' => 'Doeldatum', + 'startdate' => 'Startdatum', + 'tag' => 'Tag', + 'under' => 'Onder', + 'symbol' => 'Symbool', + 'code' => 'Code', + 'iban' => 'IBAN', + 'accountNumber' => 'Rekeningnummer', + 'creditCardNumber' => 'Creditcardnummer', + 'has_headers' => 'Kolomnamen op de eerste rij?', + 'date_format' => 'Datumformaat', + 'specifix' => 'Bank- or of bestandsspecifieke opties', + 'attachments[]' => 'Bijlagen', + 'store_new_withdrawal' => 'Nieuwe uitgave opslaan', + 'store_new_deposit' => 'Nieuwe inkomsten opslaan', + 'store_new_transfer' => 'Nieuwe overschrijving opslaan', + 'add_new_withdrawal' => 'Maak nieuwe uitgave', + 'add_new_deposit' => 'Maak nieuwe inkomsten', + 'add_new_transfer' => 'Maak nieuwe overschrijving', + 'title' => 'Titel', + 'notes' => 'Notities', + 'filename' => 'Bestandsnaam', + 'mime' => 'Bestandstype', + 'size' => 'Grootte', + 'trigger' => 'Trigger', + 'stop_processing' => 'Stop met verwerken', + 'start_date' => 'Start van bereik', + 'end_date' => 'Einde van bereik', + 'export_start_range' => 'Start van exportbereik', + 'export_end_range' => 'Einde van exportbereik', + 'export_format' => 'Bestandsformaat', + 'include_attachments' => 'Sla ook geüploade bijlagen op', + 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op', + 'accounts' => 'Exporteer boekingen van deze rekeningen', + 'delete_account' => 'Verwijder rekening ":name"', + 'delete_bill' => 'Verwijder contract ":name"', + 'delete_budget' => 'Verwijder budget ":name"', + 'delete_category' => 'Verwijder categorie ":name"', + 'delete_currency' => 'Verwijder valuta ":name"', + 'delete_journal' => 'Verwijder transactie met omschrijving ":description"', + 'delete_attachment' => 'Verwijder bijlage ":name"', + 'delete_rule' => 'Verwijder regel ":title"', + 'delete_rule_group' => 'Verwijder regelgroep ":title"', + 'delete_link_type' => 'Verwijder linktype ":name"', + 'delete_user' => 'Verwijder gebruiker ":email"', + 'delete_recurring' => 'Periodieke transactie ":title" verwijderen', + 'user_areYouSure' => 'Als je gebruiker ":email" verwijdert is alles weg. Je kan dit niet ongedaan maken of ont-verwijderen of wat dan ook. Als je jezelf verwijdert ben je ook je toegang tot deze installatie van Firefly III kwijt.', + 'attachment_areYouSure' => 'Weet je zeker dat je de bijlage met naam ":name" wilt verwijderen?', + 'account_areYouSure' => 'Weet je zeker dat je de rekening met naam ":name" wilt verwijderen?', + 'bill_areYouSure' => 'Weet je zeker dat je het contract met naam ":name" wilt verwijderen?', + 'rule_areYouSure' => 'Weet je zeker dat je regel ":title" wilt verwijderen?', + 'ruleGroup_areYouSure' => 'Weet je zeker dat je regelgroep ":title" wilt verwijderen?', + 'budget_areYouSure' => 'Weet je zeker dat je het budget met naam ":name" wilt verwijderen?', + 'category_areYouSure' => 'Weet je zeker dat je het category met naam ":name" wilt verwijderen?', + 'recurring_areYouSure' => 'Weet je zeker dat je periodieke transactie ":title" wilt verwijderen?', + 'currency_areYouSure' => 'Weet je zeker dat je de valuta met naam ":name" wilt verwijderen?', + 'piggyBank_areYouSure' => 'Weet je zeker dat je het spaarpotje met naam ":name" wilt verwijderen?', + 'journal_areYouSure' => 'Weet je zeker dat je de transactie met naam ":description" wilt verwijderen?', + 'mass_journal_are_you_sure' => 'Weet je zeker dat je al deze transacties wilt verwijderen?', + 'tag_areYouSure' => 'Weet je zeker dat je de tag met naam ":tag" wilt verwijderen?', + 'journal_link_areYouSure' => 'Weet je zeker dat je de koppeling tussen :source en :destination wilt verwijderen?', + 'linkType_areYouSure' => 'Weet je zeker dat je linktype ":name" (":inward" / ":outward") wilt verwijderen?', + 'permDeleteWarning' => 'Dingen verwijderen uit Firefly III is permanent en kan niet ongedaan gemaakt worden.', + 'mass_make_selection' => 'Je kan items alsnog redden van de ondergang door het vinkje weg te halen.', + 'delete_all_permanently' => 'Verwijder geselecteerde items permanent', + 'update_all_journals' => 'Wijzig deze transacties', + 'also_delete_transactions' => 'Ook de enige transactie verbonden aan deze rekening wordt verwijderd.|Ook alle :count transacties verbonden aan deze rekening worden verwijderd.', + 'also_delete_connections' => 'De enige transactie gelinkt met dit linktype zal deze verbinding verliezen. | Alle :count transacties met dit linktype zullen deze verbinding verliezen.', + 'also_delete_rules' => 'De enige regel in deze regelgroep wordt ook verwijderd.|Alle :count regels in deze regelgroep worden ook verwijderd.', + 'also_delete_piggyBanks' => 'Ook het spaarpotje verbonden aan deze rekening wordt verwijderd.|Ook alle :count spaarpotjes verbonden aan deze rekening worden verwijderd.', + 'bill_keep_transactions' => 'De enige transactie verbonden aan dit contract blijft bewaard.|De :count transacties verbonden aan dit contract blijven bewaard.', + 'budget_keep_transactions' => 'De enige transactie verbonden aan dit budget blijft bewaard.|De :count transacties verbonden aan dit budget blijven bewaard.', + 'category_keep_transactions' => 'De enige transactie verbonden aan deze categorie blijft bewaard.|De :count transacties verbonden aan deze categorie blijven bewaard.', + 'recurring_keep_transactions' => 'De enige transactie verbonden aan deze periode transactie blijft bewaard.|De :count transacties verbonden aan deze periode transactie blijven bewaard.', + 'tag_keep_transactions' => 'De enige transactie verbonden aan deze tag blijft bewaard.|De :count transacties verbonden aan deze tag blijven bewaard.', + 'check_for_updates' => 'Op updates controleren', 'email' => 'E-mailadres', 'password' => 'Wachtwoord', @@ -233,5 +236,6 @@ return [ 'repetition_end' => 'Stopt met herhalen', 'repetitions' => 'Herhalingen', 'calendar' => 'Kalender', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/nl_NL/import.php b/resources/lang/nl_NL/import.php index 89381f4bf5..b68492eeaf 100644 --- a/resources/lang/nl_NL/import.php +++ b/resources/lang/nl_NL/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'Je hebt geen rekeningen geselecteerd om uit te importeren.', 'imported_from_account' => 'Geïmporteerd uit ":account"', 'spectre_account_with_number' => 'Rekening :number', + 'job_config_spectre_apply_rules' => 'Regels toepassen', + 'job_config_spectre_apply_rules_text' => 'Standaard worden je regels toegepast op de transacties die je tijdens deze routine importeert. Als je wilt dat dat niet gebeurt, zet dan het vinkje uit.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq rekeningen', 'job_config_bunq_accounts_text' => 'Deze rekeningen zijn geassocieerd met je bunq-account. Kies de rekeningen waar je van wilt importeren, en geef aan waar de gegevens geïmporteerd moeten worden.', 'bunq_no_mapping' => 'Je hebt geen rekeningen geselecteerd om uit te importeren.', 'should_download_config' => 'Download het configuratiebestand voor deze import. Dit maakt toekomstige imports veel eenvoudiger.', 'share_config_file' => 'Als je gegevens hebt geimporteerd van een gewone bank, deel dan je configuratiebestand zodat het makkelijk is voor andere gebruikers om hun gegevens te importeren. Als je je bestand deelt deel je natuurlijk géén privé-gegevens.', - + 'job_config_bunq_apply_rules' => 'Regels toepassen', + 'job_config_bunq_apply_rules_text' => 'Standaard worden je regels toegepast op de transacties die je tijdens deze routine importeert. Als je wilt dat dat niet gebeurt, zet dan het vinkje uit.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index b796f36a22..06d856cd7b 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'De waarde van :attribute is onbekend', 'accepted' => ':attribute moet geaccepteerd zijn.', 'bic' => 'Dit is geen geldige BIC.', + 'base64' => 'Dit is geen geldige base64 gecodeerde data.', + 'model_id_invalid' => 'Dit ID past niet bij dit object.', 'more' => ':attribute moet groter zijn dan nul.', 'active_url' => ':attribute is geen geldige URL.', 'after' => ':attribute moet een datum na :date zijn.', diff --git a/resources/lang/pl_PL/config.php b/resources/lang/pl_PL/config.php index f4e8b2660f..d3fcf79ace 100644 --- a/resources/lang/pl_PL/config.php +++ b/resources/lang/pl_PL/config.php @@ -41,11 +41,11 @@ return [ 'week_in_year_js' => '[Tydzień] w. YYYY', 'year_js' => 'YYYY', 'half_year_js' => 'Q YYYY', - 'dow_1' => 'Monday', - 'dow_2' => 'Tuesday', - 'dow_3' => 'Wednesday', - 'dow_4' => 'Thursday', - 'dow_5' => 'Friday', - 'dow_6' => 'Saturday', - 'dow_7' => 'Sunday', + 'dow_1' => 'Poniedziałek', + 'dow_2' => 'Wtorek', + 'dow_3' => 'Środa', + 'dow_4' => 'Czwartek', + 'dow_5' => 'Piątek', + 'dow_6' => 'Sobota', + 'dow_7' => 'Niedziela', ]; diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 2b3e7cb7ad..528a1d2450 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -180,7 +180,7 @@ return [ 'authorization_request_intro' => ':client prosi o pozwolenie na dostęp do Twojej administracji finansowej. Czy chcesz pozwolić :client na dostęp do tych danych?', 'scopes_will_be_able' => 'Ta aplikacja będzie mogła:', 'button_authorize' => 'Autoryzuj', - 'none_in_select_list' => '(none)', + 'none_in_select_list' => '(żadne)', // check for updates: 'update_check_title' => 'Sprawdź aktualizacje', @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Zeskanuj kod QR za pomocą aplikacji w telefonie, takiej jak Authy lub Google Authenticator i wprowadź wygenerowany kod.', 'pref_two_factor_auth_reset_code' => 'Zresetuj kod weryfikacyjny', 'pref_two_factor_auth_disable_2fa' => 'Wyłącz weryfikację dwuetapową', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Zapisz ustawienia', 'saved_preferences' => 'Preferencje zostały zapisane!', 'preferences_general' => 'Ogólne', @@ -668,7 +669,7 @@ return [ 'bill_will_automatch' => 'Rachunek będzie automatycznie powiązany z pasującymi transakcjami', 'skips_over' => 'pomija', 'bill_store_error' => 'Wystąpił nieoczekiwany błąd podczas zapisywania nowego rachunku. Sprawdź pliki dziennika', - 'list_inactive_rule' => 'inactive rule', + 'list_inactive_rule' => 'nieaktywna reguła', // accounts: 'details_for_asset' => 'Szczegóły konta aktywów ":name"', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo na końcu okresu', 'splitByAccount' => 'Podziel według konta', 'coveredWithTags' => 'Objęte tagami', - 'leftUnbalanced' => 'Pozostawiono niewyważone', 'leftInBudget' => 'Pozostało w budżecie', 'sumOfSums' => 'Suma sum', 'noCategory' => '(bez kategorii)', @@ -1157,7 +1157,7 @@ return [ // Import page (general strings only) 'import_index_title' => 'Import transactions into Firefly III', 'import_data' => 'Importuj dane', - 'import_transactions' => 'Import transactions', + 'import_transactions' => 'Importuj transakcje', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Ta funkcja nie jest dostępna, gdy używasz Firefly III w środowisku Sandstorm.io.', @@ -1209,36 +1209,36 @@ return [ 'no_bills_create_default' => 'Utwórz rachunek', // recurring transactions - 'recurrences' => 'Recurring transactions', + 'recurrences' => 'Cykliczne transakcje', 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', - 'no_recurring_create_default' => 'Create a recurring transaction', - 'make_new_recurring' => 'Create a recurring transaction', - 'recurring_daily' => 'Every day', - 'recurring_weekly' => 'Every week on :weekday', - 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'no_recurring_create_default' => 'Utwórz cykliczną transakcję', + 'make_new_recurring' => 'Utwórz cykliczną transakcję', + 'recurring_daily' => 'Codziennie', + 'recurring_weekly' => 'Co tydzień w :weekday', + 'recurring_monthly' => 'Co miesiąc w :dayOfMonth dzień', 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', - 'recurring_yearly' => 'Every year on :date', + 'recurring_yearly' => 'Co rok w dniu :date', 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', - 'created_transactions' => 'Related transactions', - 'expected_Withdrawals' => 'Expected withdrawals', - 'expected_Deposits' => 'Expected deposits', - 'expected_Transfers' => 'Expected transfers', - 'created_Withdrawals' => 'Created withdrawals', - 'created_Deposits' => 'Created deposits', - 'created_Transfers' => 'Created transfers', + 'created_transactions' => 'Powiązane transakcje', + 'expected_Withdrawals' => 'Oczekiwane wypłaty', + 'expected_Deposits' => 'Oczekiwane wpłaty', + 'expected_Transfers' => 'Oczekiwane transfery', + 'created_Withdrawals' => 'Utworzone wypłaty', + 'created_Deposits' => 'Utworzone wpłaty', + 'created_Transfers' => 'Utworzone transfery', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', - 'recurring_meta_field_tags' => 'Tags', - 'recurring_meta_field_notes' => 'Notes', - 'recurring_meta_field_bill_id' => 'Bill', - 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', - 'create_new_recurrence' => 'Create new recurring transaction', + 'recurring_meta_field_tags' => 'Tagi', + 'recurring_meta_field_notes' => 'Notatki', + 'recurring_meta_field_bill_id' => 'Rachunek', + 'recurring_meta_field_piggy_bank_id' => 'Skarbonka', + 'create_new_recurrence' => 'Utwórz nową cykliczną transakcję', 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', - 'no_currency' => '(no currency)', + 'no_currency' => '(brak waluty)', 'mandatory_for_recurring' => 'Mandatory recurrence information', 'mandatory_for_transaction' => 'Mandatory transaction information', 'optional_for_recurring' => 'Optional recurrence information', @@ -1247,8 +1247,8 @@ return [ 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', 'repeat_forever' => 'Repeat forever', - 'repeat_until_date' => 'Repeat until date', - 'repeat_times' => 'Repeat a number of times', + 'repeat_until_date' => 'Powtarzaj aż do dnia', + 'repeat_times' => 'Powtarzaj określoną liczbę razy', 'recurring_skips_one' => 'Every other', 'recurring_skips_more' => 'Skips :count occurrences', 'store_new_recurrence' => 'Store recurring transaction', @@ -1260,4 +1260,15 @@ return [ 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index 72e31f8995..71f56f5348 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Konwertuj wpłatę', 'convert_Transfer' => 'Konwertuj transfer', - 'amount' => 'Kwota', - 'foreign_amount' => 'Kwota zagraniczna', - 'existing_attachments' => 'Istniejące załączniki', - 'date' => 'Data', - 'interest_date' => 'Data odsetek', - 'book_date' => 'Data księgowania', - 'process_date' => 'Data przetworzenia', - 'category' => 'Kategoria', - 'tags' => 'Tagi', - 'deletePermanently' => 'Usuń trwale', - 'cancel' => 'Anuluj', - 'targetdate' => 'Data docelowa', - 'startdate' => 'Data rozpoczęcia', - 'tag' => 'Tag', - 'under' => 'Poniżej', - 'symbol' => 'Symbol', - 'code' => 'Kod', - 'iban' => 'IBAN', - 'accountNumber' => 'Numer konta', - 'creditCardNumber' => 'Numer karty kredytowej', - 'has_headers' => 'Nagłówki', - 'date_format' => 'Format daty', - 'specifix' => 'Poprawki dla banku lub pliku', - 'attachments[]' => 'Załączniki', - 'store_new_withdrawal' => 'Zapisz nową wypłatę', - 'store_new_deposit' => 'Zapisz nową wpłatę', - 'store_new_transfer' => 'Zapisz nowy transfer', - 'add_new_withdrawal' => 'Dodaj nową wypłatę', - 'add_new_deposit' => 'Dodaj nową wpłatę', - 'add_new_transfer' => 'Dodaj nowy transfer', - 'title' => 'Tytuł', - 'notes' => 'Notatki', - 'filename' => 'Nazwa pliku', - 'mime' => 'Typ MIME', - 'size' => 'Rozmiar', - 'trigger' => 'Wyzwalacz', - 'stop_processing' => 'Zatrzymaj przetwarzanie', - 'start_date' => 'Początek zakresu', - 'end_date' => 'Koniec zakresu', - 'export_start_range' => 'Początek okresu eksportu', - 'export_end_range' => 'Koniec okresu eksportu', - 'export_format' => 'Format pliku', - 'include_attachments' => 'Uwzględnij dołączone załączniki', - 'include_old_uploads' => 'Dołącz zaimportowane dane', - 'accounts' => 'Eksportuj transakcje z tych kont', - 'delete_account' => 'Usuń konto ":name"', - 'delete_bill' => 'Usuń rachunek ":name"', - 'delete_budget' => 'Usuń budżet ":name"', - 'delete_category' => 'Usuń kategorię ":name"', - 'delete_currency' => 'Usuń walutę ":name"', - 'delete_journal' => 'Usuń transakcję z opisem ":description"', - 'delete_attachment' => 'Usuń załącznik ":name"', - 'delete_rule' => 'Usuń regułę ":title"', - 'delete_rule_group' => 'Usuń grupę reguł ":title"', - 'delete_link_type' => 'Usuń typ łącza ":name"', - 'delete_user' => 'Usuń użytkownika ":email"', - 'user_areYouSure' => 'Jeśli usuniesz użytkownika ":email", wszystko zniknie. Nie ma cofania, przywracania ani czegokolwiek. Jeśli usuniesz siebie, stracisz dostęp do tej instalacji Firefly III.', - 'attachment_areYouSure' => 'Czy na pewno chcesz usunąć załącznik o nazwie ":name"?', - 'account_areYouSure' => 'Czy na pewno chcesz usunąć konto o nazwie ":name"?', - 'bill_areYouSure' => 'Czy na pewno chcesz usunąć rachunek o nazwie ":name"?', - 'rule_areYouSure' => 'Czy na pewno chcesz usunąć regułę o nazwie ":name"?', - 'ruleGroup_areYouSure' => 'Czy na pewno chcesz usunąć grupę reguł o nazwie ":name"?', - 'budget_areYouSure' => 'Czy na pewno chcesz usunąć budżet o nazwie ":name"?', - 'category_areYouSure' => 'Czy na pewno chcesz usunąć kategorię o nazwie ":name"?', - 'currency_areYouSure' => 'Czy na pewno chcesz usunąć walutę o nazwie ":name"?', - 'piggyBank_areYouSure' => 'Czy na pewno chcesz usunąć skarbonkę o nazwie ":name"?', - 'journal_areYouSure' => 'Czy na pewno chcesz usunąć transakcję opisaną ":description"?', - 'mass_journal_are_you_sure' => 'Czy na pewno chcesz usunąć te transakcje?', - 'tag_areYouSure' => 'Czy na pewno chcesz usunąć tag ":tag"?', - 'journal_link_areYouSure' => 'Czy na pewno chcesz usunąć powiązanie między :source a :destination?', - 'linkType_areYouSure' => 'Czy na pewno chcesz usunąć typ łącza ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Usuwanie rzeczy z Firefly III jest trwałe i nie można tego cofnąć.', - 'mass_make_selection' => 'Nadal możesz zapobiec usunięciu elementów, odznaczając je.', - 'delete_all_permanently' => 'Trwale usuń zaznaczone', - 'update_all_journals' => 'Zmodyfikuj te transakcje', - 'also_delete_transactions' => 'Jedyna transakcja powiązana z tym kontem zostanie również usunięta.|Wszystkie transakcje (:count) powiązane z tym kontem zostaną również usunięta.', - 'also_delete_connections' => 'Jedyna transakcja połączona z tym typem łącza utraci to połączenie.|Wszystkie transakcje (:count) połączone tym typem łącza utracą swoje połączenie.', - 'also_delete_rules' => 'Jedyna reguła połączona z tą grupą reguł zostanie również usunięta.|Wszystkie reguły (:count) połączone tą grupą reguł zostaną również usunięte.', - 'also_delete_piggyBanks' => 'Jedyna skarbonka połączona z tym kontem zostanie również usunięta.|Wszystkie skarbonki (:count) połączone z tym kontem zostaną również usunięte.', - 'bill_keep_transactions' => 'Jedyna transakcja związana z tym rachunkiem nie zostanie usunięta. | Wszystkie :count transakcje związane z tym rachunkiem zostaną oszczędzone.', - 'budget_keep_transactions' => 'Jedyna transakcja związana z tym budżetem nie zostanie usunięta.|Wszystkie transakcje (:count) związane z tym budżetem zostaną oszczędzone.', - 'category_keep_transactions' => 'Jedyna transakcja związana z tą kategorią nie zostanie usunięta.|Wszystkie transakcje (:count) związane z tą kategorią zostaną oszczędzone.', - 'tag_keep_transactions' => 'Jedyna transakcja związana z tym tagiem nie zostanie usunięta.|Wszystkie transakcje (:count) związane z tym tagiem zostaną oszczędzone.', - 'check_for_updates' => 'Sprawdź aktualizacje', + 'amount' => 'Kwota', + 'foreign_amount' => 'Kwota zagraniczna', + 'existing_attachments' => 'Istniejące załączniki', + 'date' => 'Data', + 'interest_date' => 'Data odsetek', + 'book_date' => 'Data księgowania', + 'process_date' => 'Data przetworzenia', + 'category' => 'Kategoria', + 'tags' => 'Tagi', + 'deletePermanently' => 'Usuń trwale', + 'cancel' => 'Anuluj', + 'targetdate' => 'Data docelowa', + 'startdate' => 'Data rozpoczęcia', + 'tag' => 'Tag', + 'under' => 'Poniżej', + 'symbol' => 'Symbol', + 'code' => 'Kod', + 'iban' => 'IBAN', + 'accountNumber' => 'Numer konta', + 'creditCardNumber' => 'Numer karty kredytowej', + 'has_headers' => 'Nagłówki', + 'date_format' => 'Format daty', + 'specifix' => 'Poprawki dla banku lub pliku', + 'attachments[]' => 'Załączniki', + 'store_new_withdrawal' => 'Zapisz nową wypłatę', + 'store_new_deposit' => 'Zapisz nową wpłatę', + 'store_new_transfer' => 'Zapisz nowy transfer', + 'add_new_withdrawal' => 'Dodaj nową wypłatę', + 'add_new_deposit' => 'Dodaj nową wpłatę', + 'add_new_transfer' => 'Dodaj nowy transfer', + 'title' => 'Tytuł', + 'notes' => 'Notatki', + 'filename' => 'Nazwa pliku', + 'mime' => 'Typ MIME', + 'size' => 'Rozmiar', + 'trigger' => 'Wyzwalacz', + 'stop_processing' => 'Zatrzymaj przetwarzanie', + 'start_date' => 'Początek zakresu', + 'end_date' => 'Koniec zakresu', + 'export_start_range' => 'Początek okresu eksportu', + 'export_end_range' => 'Koniec okresu eksportu', + 'export_format' => 'Format pliku', + 'include_attachments' => 'Uwzględnij dołączone załączniki', + 'include_old_uploads' => 'Dołącz zaimportowane dane', + 'accounts' => 'Eksportuj transakcje z tych kont', + 'delete_account' => 'Usuń konto ":name"', + 'delete_bill' => 'Usuń rachunek ":name"', + 'delete_budget' => 'Usuń budżet ":name"', + 'delete_category' => 'Usuń kategorię ":name"', + 'delete_currency' => 'Usuń walutę ":name"', + 'delete_journal' => 'Usuń transakcję z opisem ":description"', + 'delete_attachment' => 'Usuń załącznik ":name"', + 'delete_rule' => 'Usuń regułę ":title"', + 'delete_rule_group' => 'Usuń grupę reguł ":title"', + 'delete_link_type' => 'Usuń typ łącza ":name"', + 'delete_user' => 'Usuń użytkownika ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Jeśli usuniesz użytkownika ":email", wszystko zniknie. Nie ma cofania, przywracania ani czegokolwiek. Jeśli usuniesz siebie, stracisz dostęp do tej instalacji Firefly III.', + 'attachment_areYouSure' => 'Czy na pewno chcesz usunąć załącznik o nazwie ":name"?', + 'account_areYouSure' => 'Czy na pewno chcesz usunąć konto o nazwie ":name"?', + 'bill_areYouSure' => 'Czy na pewno chcesz usunąć rachunek o nazwie ":name"?', + 'rule_areYouSure' => 'Czy na pewno chcesz usunąć regułę o nazwie ":name"?', + 'ruleGroup_areYouSure' => 'Czy na pewno chcesz usunąć grupę reguł o nazwie ":name"?', + 'budget_areYouSure' => 'Czy na pewno chcesz usunąć budżet o nazwie ":name"?', + 'category_areYouSure' => 'Czy na pewno chcesz usunąć kategorię o nazwie ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Czy na pewno chcesz usunąć walutę o nazwie ":name"?', + 'piggyBank_areYouSure' => 'Czy na pewno chcesz usunąć skarbonkę o nazwie ":name"?', + 'journal_areYouSure' => 'Czy na pewno chcesz usunąć transakcję opisaną ":description"?', + 'mass_journal_are_you_sure' => 'Czy na pewno chcesz usunąć te transakcje?', + 'tag_areYouSure' => 'Czy na pewno chcesz usunąć tag ":tag"?', + 'journal_link_areYouSure' => 'Czy na pewno chcesz usunąć powiązanie między :source a :destination?', + 'linkType_areYouSure' => 'Czy na pewno chcesz usunąć typ łącza ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Usuwanie rzeczy z Firefly III jest trwałe i nie można tego cofnąć.', + 'mass_make_selection' => 'Nadal możesz zapobiec usunięciu elementów, odznaczając je.', + 'delete_all_permanently' => 'Trwale usuń zaznaczone', + 'update_all_journals' => 'Zmodyfikuj te transakcje', + 'also_delete_transactions' => 'Jedyna transakcja powiązana z tym kontem zostanie również usunięta.|Wszystkie transakcje (:count) powiązane z tym kontem zostaną również usunięta.', + 'also_delete_connections' => 'Jedyna transakcja połączona z tym typem łącza utraci to połączenie.|Wszystkie transakcje (:count) połączone tym typem łącza utracą swoje połączenie.', + 'also_delete_rules' => 'Jedyna reguła połączona z tą grupą reguł zostanie również usunięta.|Wszystkie reguły (:count) połączone tą grupą reguł zostaną również usunięte.', + 'also_delete_piggyBanks' => 'Jedyna skarbonka połączona z tym kontem zostanie również usunięta.|Wszystkie skarbonki (:count) połączone z tym kontem zostaną również usunięte.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Sprawdź aktualizacje', 'email' => 'Adres email', 'password' => 'Hasło', @@ -186,10 +189,10 @@ return [ 'blocked_code' => 'Powód blokady', // import - 'apply_rules' => 'Apply rules', - 'artist' => 'Artist', + 'apply_rules' => 'Zastosuj reguły', + 'artist' => 'Artysta', 'album' => 'Album', - 'song' => 'Song', + 'song' => 'Piosenka', // admin @@ -223,15 +226,16 @@ return [ 'inward' => 'Opis wewnętrzny', 'outward' => 'Opis zewnętrzny', 'rule_group_id' => 'Grupa reguł', - 'transaction_description' => 'Transaction description', - 'first_date' => 'First date', - 'transaction_type' => 'Transaction type', - 'repeat_until' => 'Repeat until', - 'recurring_description' => 'Recurring transaction description', - 'repetition_type' => 'Type of repetition', - 'foreign_currency_id' => 'Foreign currency', - 'repetition_end' => 'Repetition ends', - 'repetitions' => 'Repetitions', - 'calendar' => 'Calendar', + 'transaction_description' => 'Opis transakcji', + 'first_date' => 'Data początkowa', + 'transaction_type' => 'Typ transakcji', + 'repeat_until' => 'Powtarzaj aż', + 'recurring_description' => 'Opis cyklicznej transakcji', + 'repetition_type' => 'Typ powtórzeń', + 'foreign_currency_id' => 'Zagraniczna waluta', + 'repetition_end' => 'Koniec powtórzeń', + 'repetitions' => 'Powtórzenia', + 'calendar' => 'Kalendarz', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/pl_PL/import.php b/resources/lang/pl_PL/import.php index 6b2aa6c068..8e2b109e85 100644 --- a/resources/lang/pl_PL/import.php +++ b/resources/lang/pl_PL/import.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Import data into Firefly III', + 'index_breadcrumb' => 'Importuj dane do Firefly III', 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', @@ -37,17 +37,17 @@ return [ 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', // import provider strings (index): 'button_fake' => 'Fake an import', - 'button_file' => 'Import a file', - 'button_bunq' => 'Import from bunq', - 'button_spectre' => 'Import using Spectre', - 'button_plaid' => 'Import using Plaid', - 'button_yodlee' => 'Import using Yodlee', - 'button_quovo' => 'Import using Quovo', + 'button_file' => 'Importuj plik', + 'button_bunq' => 'Importuj z bunq', + 'button_spectre' => 'Importuj za pomocą Spectre', + 'button_plaid' => 'Importuj za pomocą Plaid', + 'button_yodlee' => 'Importuj za pomocą Yodlee', + 'button_quovo' => 'Importuj za pomocą Quovo', // global config box (index) - 'global_config_title' => 'Global import configuration', + 'global_config_title' => 'Globalna konfiguracja importu', 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', // prerequisites box (index) - 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_title' => 'Wymagania importu', 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', 'do_prereq_fake' => 'Prerequisites for the fake provider', 'do_prereq_file' => 'Prerequisites for file imports', @@ -57,7 +57,7 @@ return [ 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', // provider config box (index) - 'can_config_title' => 'Import configuration', + 'can_config_title' => 'Importuj konfigurację', 'can_config_intro' => 'Some import methods can be configured to your liking. They have extra settings you can tweak.', 'do_config_fake' => 'Configuration for the fake provider', 'do_config_file' => 'Configuration for file imports', @@ -107,48 +107,51 @@ return [ 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', - 'job_config_uc_apply_rules_title' => 'Apply rules', + 'job_config_uc_apply_rules_title' => 'Zastosuj reguły', 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', 'job_config_uc_specifics_title' => 'Bank-specific options', 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', - 'job_config_uc_submit' => 'Continue', + 'job_config_uc_submit' => 'Kontynuuj', 'invalid_import_account' => 'You have selected an invalid account to import into.', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Choose your login', + 'job_config_spectre_login_title' => 'Wybierz swój login', 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', - 'spectre_login_status_active' => 'Active', - 'spectre_login_status_inactive' => 'Inactive', - 'spectre_login_status_disabled' => 'Disabled', + 'spectre_login_status_active' => 'Aktywny', + 'spectre_login_status_inactive' => 'Nieaktywny', + 'spectre_login_status_disabled' => 'Wyłączony', 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', 'job_config_spectre_accounts_title' => 'Select accounts to import from', 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', 'spectre_no_supported_accounts' => 'You cannot import from this account due to a currency mismatch.', - 'spectre_do_not_import' => '(do not import)', - 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', - 'imported_from_account' => 'Imported from ":account"', - 'spectre_account_with_number' => 'Account :number', + 'spectre_do_not_import' => '(nie importuj)', + 'spectre_no_mapping' => 'Wygląda na to, że nie wybrałeś żadnych kont z których można zaimportować dane.', + 'imported_from_account' => 'Zaimportowane z ":account"', + 'spectre_account_with_number' => ':number konta', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: - 'job_config_bunq_accounts_title' => 'bunq accounts', + 'job_config_bunq_accounts_title' => 'konta bunq', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', 'spectre_extra_key_status' => 'Status', - 'spectre_extra_key_card_type' => 'Card type', - 'spectre_extra_key_account_name' => 'Account name', - 'spectre_extra_key_client_name' => 'Client name', - 'spectre_extra_key_account_number' => 'Account number', - 'spectre_extra_key_blocked_amount' => 'Blocked amount', - 'spectre_extra_key_available_amount' => 'Available amount', - 'spectre_extra_key_credit_limit' => 'Credit limit', - 'spectre_extra_key_interest_rate' => 'Interest rate', + 'spectre_extra_key_card_type' => 'Typ karty', + 'spectre_extra_key_account_name' => 'Nazwa konta', + 'spectre_extra_key_client_name' => 'Nazwa klienta', + 'spectre_extra_key_account_number' => 'Numer konta', + 'spectre_extra_key_blocked_amount' => 'Zablokowana kwota', + 'spectre_extra_key_available_amount' => 'Dostępna kwota', + 'spectre_extra_key_credit_limit' => 'Limit kredytowy', + 'spectre_extra_key_interest_rate' => 'Oprocentowanie', 'spectre_extra_key_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_open_date' => 'Data otwarcia', + 'spectre_extra_key_current_time' => 'Aktualny czas', 'spectre_extra_key_current_date' => 'Current date', 'spectre_extra_key_cards' => 'Cards', 'spectre_extra_key_units' => 'Units', @@ -177,15 +180,15 @@ return [ 'job_config_roles_no_example' => 'No example data available', 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'job_config_roles_colum_count' => 'Column', + 'job_config_roles_colum_count' => 'Kolumna', // job config for the file provider (stage: mapping): 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - 'job_config_field_value' => 'Field value', - 'job_config_field_mapped' => 'Mapped to', + 'job_config_field_value' => 'Wartość pola', + 'job_config_field_mapped' => 'Zmapowane na', 'map_do_not_map' => '(nie mapuj)', - 'job_config_map_submit' => 'Start the import', + 'job_config_map_submit' => 'Rozpocznij import', // import status page: @@ -196,11 +199,11 @@ return [ 'status_job_running' => 'Please wait, running the import...', 'status_job_storing' => 'Please wait, storing data...', 'status_job_rules' => 'Please wait, running rules...', - 'status_fatal_title' => 'Fatal error', + 'status_fatal_title' => 'Błąd krytyczny', 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', - 'status_finished_title' => 'Import finished', - 'status_finished_text' => 'The import has finished.', + 'status_finished_title' => 'Import zakończony', + 'status_finished_text' => 'Importowanie zostało zakończone.', 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'unknown_import_result' => 'Unknown import result', 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index e8da734c85..f2a7bfedff 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -125,7 +125,7 @@ return [ 'bunq_payment_id' => 'bunq payment ID', 'repetitions' => 'Repetitions', 'title' => 'Title', - 'transaction_s' => 'Transaction(s)', - 'field' => 'Field', - 'value' => 'Value', + 'transaction_s' => 'Transakcja(e)', + 'field' => 'Pole', + 'value' => 'Wartość', ]; diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index c2af23c82d..aa51207f91 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'Wartość :attribute jest nieznana', 'accepted' => ':attribute musi zostać zaakceptowany.', 'bic' => 'To nie jest prawidłowy BIC.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute musi być większy od zera.', 'active_url' => ':attribute nie jest prawidłowym adresem URL.', 'after' => ':attribute musi być datą późniejszą od :date.', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index b998795ff1..f4f871629f 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scaneie o código QR com um aplicativo em seu telefone como Authy ou Google Authenticator e insira o código gerado.', 'pref_two_factor_auth_reset_code' => 'Redefinir o código de verificação', 'pref_two_factor_auth_disable_2fa' => 'Desativar verificação em duas etapas', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Salvar definições', 'saved_preferences' => 'Preferências salvas!', 'preferences_general' => 'Geral', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Saldo no final do período', 'splitByAccount' => 'Dividir por conta', 'coveredWithTags' => 'Coberto com tags', - 'leftUnbalanced' => 'Deixar desequilibrado', 'leftInBudget' => 'Deixou no orçamento', 'sumOfSums' => 'Soma dos montantes', 'noCategory' => '(sem categoria)', @@ -1260,4 +1260,15 @@ return [ 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index 385f8fe893..17dc33a4da 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Converter o depósito', 'convert_Transfer' => 'Converter a transferência', - 'amount' => 'Valor', - 'foreign_amount' => 'Montante em moeda estrangeira', - 'existing_attachments' => 'Anexos existentes', - 'date' => 'Data', - 'interest_date' => 'Data de interesse', - 'book_date' => 'Data reserva', - 'process_date' => 'Data de processamento', - 'category' => 'Categoria', - 'tags' => 'Etiquetas', - 'deletePermanently' => 'Apagar permanentemente', - 'cancel' => 'Cancelar', - 'targetdate' => 'Data Alvo', - 'startdate' => 'Data de Início', - 'tag' => 'Etiqueta', - 'under' => 'Debaixo', - 'symbol' => 'Símbolo', - 'code' => 'Código', - 'iban' => 'IBAN', - 'accountNumber' => 'Número de conta', - 'creditCardNumber' => 'Número do cartão de crédito', - 'has_headers' => 'Cabeçalhos', - 'date_format' => 'Formato da Data', - 'specifix' => 'Banco- ou arquivo específico corrigídos', - 'attachments[]' => 'Anexos', - 'store_new_withdrawal' => 'Armazenar nova retirada', - 'store_new_deposit' => 'Armazenar novo depósito', - 'store_new_transfer' => 'Armazenar nova transferência', - 'add_new_withdrawal' => 'Adicionar uma nova retirada', - 'add_new_deposit' => 'Adicionar um novo depósito', - 'add_new_transfer' => 'Adicionar uma nova transferência', - 'title' => 'Título', - 'notes' => 'Notas', - 'filename' => 'Nome do arquivo', - 'mime' => 'Tipo do Arquivo (MIME)', - 'size' => 'Tamanho', - 'trigger' => 'Disparo', - 'stop_processing' => 'Parar processamento', - 'start_date' => 'Início do intervalo', - 'end_date' => 'Final do intervalo', - 'export_start_range' => 'Início do intervalo de exportação', - 'export_end_range' => 'Fim do intervalo de exportação', - 'export_format' => 'Formato do arquivo', - 'include_attachments' => 'Incluir anexos enviados', - 'include_old_uploads' => 'Incluir dados importados', - 'accounts' => 'Exportar transações destas contas', - 'delete_account' => 'Apagar conta ":name"', - 'delete_bill' => 'Apagar fatura ":name"', - 'delete_budget' => 'Excluir o orçamento ":name"', - 'delete_category' => 'Excluir categoria ":name"', - 'delete_currency' => 'Excluir moeda ":name"', - 'delete_journal' => 'Excluir a transação com a descrição ":description"', - 'delete_attachment' => 'Apagar anexo ":name"', - 'delete_rule' => 'Excluir regra ":title"', - 'delete_rule_group' => 'Exclua o grupo de regras ":title"', - 'delete_link_type' => 'Excluir tipo de link ":name"', - 'delete_user' => 'Excluir o usuário ":email"', - 'user_areYouSure' => 'Se você excluir o usuário ":email", tudo desaparecerá. Não será possível desfazer a ação. Se excluir você mesmo, você perderá acesso total a essa instância do Firefly III.', - 'attachment_areYouSure' => 'Tem certeza que deseja excluir o anexo denominado ":name"?', - 'account_areYouSure' => 'Tem certeza que deseja excluir a conta denominada ":name"?', - 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?', - 'rule_areYouSure' => 'Tem certeza que deseja excluir a regra intitulada ":title"?', - 'ruleGroup_areYouSure' => 'Tem certeza que deseja excluir o grupo de regras intitulado ":title"?', - 'budget_areYouSure' => 'Tem certeza que deseja excluir o orçamento chamado ":name"?', - 'category_areYouSure' => 'Tem certeza que deseja excluir a categoria com o nome ":name"?', - 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?', - 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?', - 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?', - 'mass_journal_are_you_sure' => 'Tem a certeza que pretende apagar estas transações?', - 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', - 'journal_link_areYouSure' => 'Tem certeza que deseja excluir a ligação entre :source e :destination?', - 'linkType_areYouSure' => 'Tem certeza que deseja excluir o tipo de link ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Exclusão de dados do Firefly III são permanentes e não podem ser desfeitos.', - 'mass_make_selection' => 'Você ainda pode evitar que itens sejam excluídos, removendo a caixa de seleção.', - 'delete_all_permanently' => 'Exclua os selecionados permanentemente', - 'update_all_journals' => 'Atualizar essas transações', - 'also_delete_transactions' => 'A única transação ligada a essa conta será excluída também.|Todas as :count transações ligadas a esta conta serão excluídas também.', - 'also_delete_connections' => 'A única transação relacionada com este tipo de link vai perder a conexão. | Todas as transações de :count ligadas com este tipo de link vão perder sua conexão.', - 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluída também.|Todos as :count regras ligadas a este grupo de regras serão excluídas também.', - 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluído também.|Todos os :count cofrinhos conectados a esta conta serão excluídos também.', - 'bill_keep_transactions' => 'A única transação a esta conta não será excluída.|Todos as :count transações conectadas a esta fatura não serão excluídos.', - 'budget_keep_transactions' => 'A única transação conectada a este orçamento não será excluída.|Todos :count transações ligadas a este orçamento não serão excluídos.', - 'category_keep_transactions' => 'A única transação ligada a esta categoria não será excluída.|Todos :count transações ligadas a esta categoria não serão excluídos.', - 'tag_keep_transactions' => 'A única transação ligada a essa marca não será excluída.|Todos :count transações ligadas a essa marca não serão excluídos.', - 'check_for_updates' => 'Buscar atualizações', + 'amount' => 'Valor', + 'foreign_amount' => 'Montante em moeda estrangeira', + 'existing_attachments' => 'Anexos existentes', + 'date' => 'Data', + 'interest_date' => 'Data de interesse', + 'book_date' => 'Data reserva', + 'process_date' => 'Data de processamento', + 'category' => 'Categoria', + 'tags' => 'Etiquetas', + 'deletePermanently' => 'Apagar permanentemente', + 'cancel' => 'Cancelar', + 'targetdate' => 'Data Alvo', + 'startdate' => 'Data de Início', + 'tag' => 'Etiqueta', + 'under' => 'Debaixo', + 'symbol' => 'Símbolo', + 'code' => 'Código', + 'iban' => 'IBAN', + 'accountNumber' => 'Número de conta', + 'creditCardNumber' => 'Número do cartão de crédito', + 'has_headers' => 'Cabeçalhos', + 'date_format' => 'Formato da Data', + 'specifix' => 'Banco- ou arquivo específico corrigídos', + 'attachments[]' => 'Anexos', + 'store_new_withdrawal' => 'Armazenar nova retirada', + 'store_new_deposit' => 'Armazenar novo depósito', + 'store_new_transfer' => 'Armazenar nova transferência', + 'add_new_withdrawal' => 'Adicionar uma nova retirada', + 'add_new_deposit' => 'Adicionar um novo depósito', + 'add_new_transfer' => 'Adicionar uma nova transferência', + 'title' => 'Título', + 'notes' => 'Notas', + 'filename' => 'Nome do arquivo', + 'mime' => 'Tipo do Arquivo (MIME)', + 'size' => 'Tamanho', + 'trigger' => 'Disparo', + 'stop_processing' => 'Parar processamento', + 'start_date' => 'Início do intervalo', + 'end_date' => 'Final do intervalo', + 'export_start_range' => 'Início do intervalo de exportação', + 'export_end_range' => 'Fim do intervalo de exportação', + 'export_format' => 'Formato do arquivo', + 'include_attachments' => 'Incluir anexos enviados', + 'include_old_uploads' => 'Incluir dados importados', + 'accounts' => 'Exportar transações destas contas', + 'delete_account' => 'Apagar conta ":name"', + 'delete_bill' => 'Apagar fatura ":name"', + 'delete_budget' => 'Excluir o orçamento ":name"', + 'delete_category' => 'Excluir categoria ":name"', + 'delete_currency' => 'Excluir moeda ":name"', + 'delete_journal' => 'Excluir a transação com a descrição ":description"', + 'delete_attachment' => 'Apagar anexo ":name"', + 'delete_rule' => 'Excluir regra ":title"', + 'delete_rule_group' => 'Exclua o grupo de regras ":title"', + 'delete_link_type' => 'Excluir tipo de link ":name"', + 'delete_user' => 'Excluir o usuário ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Se você excluir o usuário ":email", tudo desaparecerá. Não será possível desfazer a ação. Se excluir você mesmo, você perderá acesso total a essa instância do Firefly III.', + 'attachment_areYouSure' => 'Tem certeza que deseja excluir o anexo denominado ":name"?', + 'account_areYouSure' => 'Tem certeza que deseja excluir a conta denominada ":name"?', + 'bill_areYouSure' => 'Você tem certeza que quer apagar a fatura ":name"?', + 'rule_areYouSure' => 'Tem certeza que deseja excluir a regra intitulada ":title"?', + 'ruleGroup_areYouSure' => 'Tem certeza que deseja excluir o grupo de regras intitulado ":title"?', + 'budget_areYouSure' => 'Tem certeza que deseja excluir o orçamento chamado ":name"?', + 'category_areYouSure' => 'Tem certeza que deseja excluir a categoria com o nome ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Tem certeza que deseja excluir a moeda chamada ":name"?', + 'piggyBank_areYouSure' => 'Tem certeza que deseja excluir o cofrinho chamado ":name"?', + 'journal_areYouSure' => 'Tem certeza que deseja excluir a transação descrita ":description"?', + 'mass_journal_are_you_sure' => 'Tem a certeza que pretende apagar estas transações?', + 'tag_areYouSure' => 'Você tem certeza que quer apagar a tag ":tag"?', + 'journal_link_areYouSure' => 'Tem certeza que deseja excluir a ligação entre :source e :destination?', + 'linkType_areYouSure' => 'Tem certeza que deseja excluir o tipo de link ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Exclusão de dados do Firefly III são permanentes e não podem ser desfeitos.', + 'mass_make_selection' => 'Você ainda pode evitar que itens sejam excluídos, removendo a caixa de seleção.', + 'delete_all_permanently' => 'Exclua os selecionados permanentemente', + 'update_all_journals' => 'Atualizar essas transações', + 'also_delete_transactions' => 'A única transação ligada a essa conta será excluída também.|Todas as :count transações ligadas a esta conta serão excluídas também.', + 'also_delete_connections' => 'A única transação relacionada com este tipo de link vai perder a conexão. | Todas as transações de :count ligadas com este tipo de link vão perder sua conexão.', + 'also_delete_rules' => 'A única regra que ligado a este grupo de regras será excluída também.|Todos as :count regras ligadas a este grupo de regras serão excluídas também.', + 'also_delete_piggyBanks' => 'O único cofrinho conectado a essa conta será excluído também.|Todos os :count cofrinhos conectados a esta conta serão excluídos também.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Buscar atualizações', 'email' => 'E-mail', 'password' => 'Senha', @@ -233,5 +236,6 @@ return [ 'repetition_end' => 'Repetition ends', 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/pt_BR/import.php b/resources/lang/pt_BR/import.php index 377665273f..5766b66116 100644 --- a/resources/lang/pt_BR/import.php +++ b/resources/lang/pt_BR/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index ce08655f94..727da9bb42 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'O valor de :attribute é desconhecido', 'accepted' => 'O campo :attribute deve ser aceito.', 'bic' => 'Este não é um BIC válido.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute deve ser maior que zero.', 'active_url' => 'O campo :attribute não contém um URL válido.', 'after' => 'O campo :attribute deverá conter uma data posterior a :date.', diff --git a/resources/lang/ru_RU/config.php b/resources/lang/ru_RU/config.php index c792be080b..58b0bfdd02 100644 --- a/resources/lang/ru_RU/config.php +++ b/resources/lang/ru_RU/config.php @@ -27,7 +27,7 @@ return [ 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', - 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_date_day' => '%A, %B %e %Y', 'month_and_day_no_year' => '%B %e', 'date_time' => '%e %B %Y, @ %T', 'specific_day' => '%e %B %Y', @@ -41,11 +41,11 @@ return [ 'week_in_year_js' => '[Неделя] w, YYYY', 'year_js' => 'YYYY', 'half_year_js' => 'Q YYYY', - 'dow_1' => 'Monday', - 'dow_2' => 'Tuesday', - 'dow_3' => 'Wednesday', - 'dow_4' => 'Thursday', - 'dow_5' => 'Friday', - 'dow_6' => 'Saturday', - 'dow_7' => 'Sunday', + 'dow_1' => 'Понедельник', + 'dow_2' => 'Вторник', + 'dow_3' => 'Среда', + 'dow_4' => 'Четверг', + 'dow_5' => 'Пятница', + 'dow_6' => 'Суббота', + 'dow_7' => 'Воскресенье', ]; diff --git a/resources/lang/ru_RU/demo.php b/resources/lang/ru_RU/demo.php index d419b6ebe7..8eb16e401b 100644 --- a/resources/lang/ru_RU/demo.php +++ b/resources/lang/ru_RU/demo.php @@ -34,6 +34,6 @@ return [ 'transactions-index' => 'Эти расходы, доходы и переводы не очень интересны. Они были созданы автоматически.', 'piggy-banks-index' => 'Как вы можете видеть, здесь есть три копилки. Используйте кнопки «плюс» и «минус», чтобы влиять на количество денег в каждой копилке. Нажмите название копилки, чтобы увидеть её настройки.', 'import-index' => 'В Firefly III можно импортировать любой CSV-файл. Также поддерживается импорт данных из bunq и Spectre. Другие банки и финансовые агрегаторы будут реализованы в будущем. Однако, как демо-пользователь, вы можете видеть только как работает «поддельный»-провайдер. Он будет генерировать некоторые случайные транзакции, чтобы показать вам, как работает этот процесс.', - 'recurring-index' => 'Please note that this feature is under active development and may not work as expected.', - 'recurring-create' => 'Please note that this feature is under active development and may not work as expected.', + 'recurring-index' => 'Пожалуйста, обратите внимание, что эта функция находится в активной разработке и может работать не совcем так, как вы ожидаете.', + 'recurring-create' => 'Пожалуйста, обратите внимание, что эта функция находится в активной разработке и может работать не совcем так, как вы ожидаете.', ]; diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index 2088a1d347..f64ee3b21f 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -465,6 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Отсканируйте QR-код с помощью приложения на телефоне, например Authy или Google Authenticator, и введите сгенерированный код.', 'pref_two_factor_auth_reset_code' => 'Сбросить код верификации', 'pref_two_factor_auth_disable_2fa' => 'Выключить 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Сохранить настройки', 'saved_preferences' => 'Настройки сохранены!', 'preferences_general' => 'Основные', @@ -820,7 +821,7 @@ return [ 'language' => 'Язык', 'new_savings_account' => 'сберегательный счёт в :bank_name', 'cash_wallet' => 'Кошелёк с наличными', - 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + 'currency_not_present' => 'Если ваша основная валюта ещё не указана, не волнуйтесь. Вы можете создать собственные валюты в разделе Параметры > Валюты.', // home page: 'yourAccounts' => 'Ваши счета', @@ -900,7 +901,6 @@ return [ 'balanceEnd' => 'Остаток на конец периода', 'splitByAccount' => 'Разделить по разным счетам', 'coveredWithTags' => 'Присвоены метки', - 'leftUnbalanced' => 'Осталось вне баланса', 'leftInBudget' => 'Осталось в бюджете', 'sumOfSums' => 'Сумма сумм', 'noCategory' => '(без категории)', @@ -1062,7 +1062,7 @@ return [ 'instance_configuration' => 'Конфигурация', 'firefly_instance_configuration' => 'Базовая конфигурация Firefly III', 'setting_single_user_mode' => 'Режим одного пользователя', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', + 'setting_single_user_mode_explain' => 'По умолчанию Firefly III работает только с одним пользователем (это вы). Это сделано с целью обеспечения безопасности, чтобы другие люди не могли использовать ваш Firefly без вашего разрешения. Регистрация других пользователей просто невозможна. Однако, если вы снимите этот флажок, другие смогут использовать ваш Firefly, при условии, что у них есть доступ к нему (например, он доступен через Интернет).', 'store_configuration' => 'Сохранить конфигурацию', 'single_user_administration' => 'Управление пользователем :email', 'edit_user' => 'Редактирование пользователя :email', @@ -1155,9 +1155,9 @@ return [ 'cannot_convert_split_journal' => 'Невозможно преобразовать раздельную транзакцию', // Import page (general strings only) - 'import_index_title' => 'Import transactions into Firefly III', + 'import_index_title' => 'Импорт транзакций в Firefly III', 'import_data' => 'Импорт данных', - 'import_transactions' => 'Import transactions', + 'import_transactions' => 'Импорт транзакций', // sandstorm.io errors and messages: 'sandstorm_not_available' => 'Эта функция недоступна, если вы используете Firefly III в среде Sandstorm.io.', @@ -1209,55 +1209,66 @@ return [ 'no_bills_create_default' => 'Создать счет к оплате', // recurring transactions - 'recurrences' => 'Recurring transactions', - 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'recurrences' => 'Повторяющиеся транзакции', + 'no_recurring_title_default' => 'Давайте создадим повторяющуюся транзакцию!', 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', - 'no_recurring_create_default' => 'Create a recurring transaction', - 'make_new_recurring' => 'Create a recurring transaction', - 'recurring_daily' => 'Every day', - 'recurring_weekly' => 'Every week on :weekday', - 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', - 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', - 'recurring_yearly' => 'Every year on :date', - 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'no_recurring_create_default' => 'Создать повторяющуюся транзакцию', + 'make_new_recurring' => 'Создать повторяющуюся транзакцию', + 'recurring_daily' => 'Каждый день', + 'recurring_weekly' => 'Каждую неделю в :weekday', + 'recurring_monthly' => 'Каждый месяц в :dayOfMonth(st/nd/rd/th) день', + 'recurring_ndom' => 'Каждый месяц в :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Каждый год на :date', + 'overview_for_recurrence' => 'Обзор повторяющейся транзакции ":title"', 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', - 'created_transactions' => 'Related transactions', - 'expected_Withdrawals' => 'Expected withdrawals', - 'expected_Deposits' => 'Expected deposits', - 'expected_Transfers' => 'Expected transfers', - 'created_Withdrawals' => 'Created withdrawals', - 'created_Deposits' => 'Created deposits', - 'created_Transfers' => 'Created transfers', - 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'created_transactions' => 'Связанные транзакции', + 'expected_Withdrawals' => 'Просроченные расходы', + 'expected_Deposits' => 'Просроченные доходы', + 'expected_Transfers' => 'Просроченные переводы', + 'created_Withdrawals' => 'Расходы созданы', + 'created_Deposits' => 'Доходы созданы', + 'created_Transfers' => 'Переводы созданы', + 'created_from_recurrence' => 'Создано из повторяющейся транзакции ":title" (#:id)', - 'recurring_meta_field_tags' => 'Tags', - 'recurring_meta_field_notes' => 'Notes', - 'recurring_meta_field_bill_id' => 'Bill', - 'recurring_meta_field_piggy_bank_id' => 'Piggy bank', - 'create_new_recurrence' => 'Create new recurring transaction', - 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', - 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', - 'no_currency' => '(no currency)', - 'mandatory_for_recurring' => 'Mandatory recurrence information', - 'mandatory_for_transaction' => 'Mandatory transaction information', - 'optional_for_recurring' => 'Optional recurrence information', - 'optional_for_transaction' => 'Optional transaction information', - 'change_date_other_options' => 'Change the "first date" to see more options.', + 'recurring_meta_field_tags' => 'Метки', + 'recurring_meta_field_notes' => 'Примечания', + 'recurring_meta_field_bill_id' => 'Счёт к оплате', + 'recurring_meta_field_piggy_bank_id' => 'Копилка', + 'create_new_recurrence' => 'Создать новую повторяющуюся транзакцию', + 'help_first_date' => 'Укажите первое ожидаемое повторение. Оно должно быть в будущем.', + 'help_first_date_no_past' => 'Укажите первое ожидаемое повторение. Firefly III не будет создавать транзакции ранее этой даты.', + 'no_currency' => '(нет валюты)', + 'mandatory_for_recurring' => 'Обязательные сведения о повторении', + 'mandatory_for_transaction' => 'Обязательные сведения о транзакции', + 'optional_for_recurring' => 'Опциональные сведения о повторении', + 'optional_for_transaction' => 'Опциональные сведения о транзакции', + 'change_date_other_options' => 'Измените "первую дату", чтобы увидеть больше опций.', 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', - 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', - 'repeat_forever' => 'Repeat forever', - 'repeat_until_date' => 'Repeat until date', - 'repeat_times' => 'Repeat a number of times', - 'recurring_skips_one' => 'Every other', - 'recurring_skips_more' => 'Skips :count occurrences', - 'store_new_recurrence' => 'Store recurring transaction', - 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', - 'edit_recurrence' => 'Edit recurring transaction ":title"', - 'recurring_repeats_until' => 'Repeats until :date', - 'recurring_repeats_forever' => 'Repeats forever', - 'recurring_repeats_x_times' => 'Repeats :count time(s)', - 'update_recurrence' => 'Update recurring transaction', - 'updated_recurrence' => 'Updated recurring transaction ":title"', - 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'click_for_calendar' => 'Щёлкните здесь, чтобы открыт календарь и указать, когда транзакция будет повторяться.', + 'repeat_forever' => 'Повторять всегда', + 'repeat_until_date' => 'Повторять до указанной даты', + 'repeat_times' => 'Повторять указанное число раз', + 'recurring_skips_one' => 'Другой период', + 'recurring_skips_more' => 'Пропустить :count повторов', + 'store_new_recurrence' => 'Сохранить повторяющуюся транзакцию', + 'stored_new_recurrence' => 'Повторяющаяся транзакция ":title" успешно сохранена.', + 'edit_recurrence' => 'Изменить повторяющуюся транзакцию ":title"', + 'recurring_repeats_until' => 'Повторять до :date', + 'recurring_repeats_forever' => 'Повторять всегда', + 'recurring_repeats_x_times' => 'Повторить :count раз(а)', + 'update_recurrence' => 'Обновить повторяющуюся транзакцию', + 'updated_recurrence' => 'Повторяющаяся транзакция ":title" обновлена', + 'recurrence_is_inactive' => 'Эта повторяющаяся транзакция не активна и не создаёт новые транзакции.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index 3cfb8a07ea..132c1f82e1 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Конвертировать доход', 'convert_Transfer' => 'Конвертировать перевод', - 'amount' => 'Сумма', - 'foreign_amount' => 'Сумму в иностранной валюте', - 'existing_attachments' => 'Существующие вложения', - 'date' => 'Дата', - 'interest_date' => 'Дата выплаты', - 'book_date' => 'Дата бронирования', - 'process_date' => 'Дата обработки', - 'category' => 'Категория', - 'tags' => 'Метки', - 'deletePermanently' => 'Удалить навсегда', - 'cancel' => 'Отмена', - 'targetdate' => 'Намеченная дата', - 'startdate' => 'Дата начала', - 'tag' => 'Тег', - 'under' => 'Под', - 'symbol' => 'Символ', - 'code' => 'Код', - 'iban' => 'IBAN', - 'accountNumber' => 'Номер счета', - 'creditCardNumber' => 'Номер кредитной карты', - 'has_headers' => 'Заголовки', - 'date_format' => 'Формат даты', - 'specifix' => 'Исправления, специфичные для банка или файла', - 'attachments[]' => 'Вложения', - 'store_new_withdrawal' => 'Сохранить новый расход', - 'store_new_deposit' => 'Сохранить новый доход', - 'store_new_transfer' => 'Сохранить новый перевод', - 'add_new_withdrawal' => 'Добавить новый расход', - 'add_new_deposit' => 'Добавить новый доход', - 'add_new_transfer' => 'Добавить новый перевод', - 'title' => 'Заголовок', - 'notes' => 'Заметки', - 'filename' => 'Имя файла', - 'mime' => 'Тип Mime', - 'size' => 'Размер', - 'trigger' => 'Триггер', - 'stop_processing' => 'Остановить обработку', - 'start_date' => 'Начало диапазона', - 'end_date' => 'Конец диапазона', - 'export_start_range' => 'Начало диапазона для экспорта', - 'export_end_range' => 'Конец диапазона для экспорта', - 'export_format' => 'Формат файла', - 'include_attachments' => 'Включить загруженные вложения', - 'include_old_uploads' => 'Включить импортированные данные', - 'accounts' => 'Экспорт транзакций с этих счетов', - 'delete_account' => 'Удалить счёт ":name"', - 'delete_bill' => 'Удаление счёта к оплате ":name"', - 'delete_budget' => 'Удалить бюджет ":name"', - 'delete_category' => 'Удалить категорию ":name"', - 'delete_currency' => 'Удалить валюту ":name"', - 'delete_journal' => 'Удалить транзакцию с описанием ":description"', - 'delete_attachment' => 'Удалить вложение ":name"', - 'delete_rule' => 'Удалить правило ":title"', - 'delete_rule_group' => 'Удалить группу правил ":title"', - 'delete_link_type' => 'Удалить тип ссылки ":name"', - 'delete_user' => 'Удалить пользователя ":email"', - 'user_areYouSure' => 'Если вы удалите пользователя ":email", все данные будут удалены. Это действие нельзя будет отменить. Если вы удалите себя, вы потеряете доступ к этому экземпляру Firefly III.', - 'attachment_areYouSure' => 'Вы действительно хотите удалить вложение с именем ":name"?', - 'account_areYouSure' => 'Вы действительно хотите удалить счёт с именем ":name"?', - 'bill_areYouSure' => 'Вы действительно хотите удалить счёт на оплату с именем ":name"?', - 'rule_areYouSure' => 'Вы действительно хотите удалить правило с названием ":title"?', - 'ruleGroup_areYouSure' => 'Вы действительно хотите удалить группу правил с названием ":title"?', - 'budget_areYouSure' => 'Вы действительно хотите удалить бюджет с именем ":name"?', - 'category_areYouSure' => 'Вы действительно хотите удалить категорию с именем ":name"?', - 'currency_areYouSure' => 'Вы уверены, что хотите удалить валюту ":name"?', - 'piggyBank_areYouSure' => 'Вы уверены, что хотите удалить копилку с именем ":name"?', - 'journal_areYouSure' => 'Вы действительно хотите удалить транзакцию с описанием ":description"?', - 'mass_journal_are_you_sure' => 'Вы действительно хотите удалить эти транзакции?', - 'tag_areYouSure' => 'Вы действительно хотите удалить метку ":tag"?', - 'journal_link_areYouSure' => 'Вы действительно хотите удалить связь между :source и :destination?', - 'linkType_areYouSure' => 'Вы уверены, что хотите удалить тип ссылки ":name" (":inward" / ":outward")?', - 'permDeleteWarning' => 'Удаление информации из Firefly III является постоянным и не может быть отменено.', - 'mass_make_selection' => 'Вы все же можете предотвратить удаление элементов, сняв флажок.', - 'delete_all_permanently' => 'Удалить выбранное навсегда', - 'update_all_journals' => 'Обновить эти транзакции', - 'also_delete_transactions' => 'Будет удалена только транзакция, связанная с этим счётом.|Будут удалены все :count транзакций, связанные с этим счётом.', - 'also_delete_connections' => 'Единственная транзакция, связанная с данным типом ссылки, потеряет это соединение. |Все :count транзакций, связанные с данным типом ссылки, потеряют свои соединения.', - 'also_delete_rules' => 'Единственное правило, связанное с данной группой правил, будет удалено. |Все :count правила, связанные с данной группой правил, будут удалены.', - 'also_delete_piggyBanks' => 'Единственная копилка, связанная с данным счётом, будет удалена.|Все :count копилки, связанные с данным счётом, будут удалены.', - 'bill_keep_transactions' => 'Единственная транзакция, связанная с данным счётом, не будет удалена. |Все :count транзакции, связанные с данным счётом, будут сохранены.', - 'budget_keep_transactions' => 'Единственная транзакция, связанная с данным бюджетом, не будет удалена.|Все :count транзакции, связанные с этим бюджетом, будут сохранены.', - 'category_keep_transactions' => 'Единственная транзакция, связанная с данной категорией, не будет удалена.|Все :count транзакции, связанные с этой категорией, будут сохранены.', - 'tag_keep_transactions' => 'Только транзакция, связанная с этой меткой, будет удалена.|Все :count транзакций, связанные с этой меткой, будут удалены.', - 'check_for_updates' => 'Проверить обновления', + 'amount' => 'Сумма', + 'foreign_amount' => 'Сумму в иностранной валюте', + 'existing_attachments' => 'Существующие вложения', + 'date' => 'Дата', + 'interest_date' => 'Дата выплаты', + 'book_date' => 'Дата бронирования', + 'process_date' => 'Дата обработки', + 'category' => 'Категория', + 'tags' => 'Метки', + 'deletePermanently' => 'Удалить навсегда', + 'cancel' => 'Отмена', + 'targetdate' => 'Намеченная дата', + 'startdate' => 'Дата начала', + 'tag' => 'Тег', + 'under' => 'Под', + 'symbol' => 'Символ', + 'code' => 'Код', + 'iban' => 'IBAN', + 'accountNumber' => 'Номер счета', + 'creditCardNumber' => 'Номер кредитной карты', + 'has_headers' => 'Заголовки', + 'date_format' => 'Формат даты', + 'specifix' => 'Исправления, специфичные для банка или файла', + 'attachments[]' => 'Вложения', + 'store_new_withdrawal' => 'Сохранить новый расход', + 'store_new_deposit' => 'Сохранить новый доход', + 'store_new_transfer' => 'Сохранить новый перевод', + 'add_new_withdrawal' => 'Добавить новый расход', + 'add_new_deposit' => 'Добавить новый доход', + 'add_new_transfer' => 'Добавить новый перевод', + 'title' => 'Заголовок', + 'notes' => 'Заметки', + 'filename' => 'Имя файла', + 'mime' => 'Тип Mime', + 'size' => 'Размер', + 'trigger' => 'Триггер', + 'stop_processing' => 'Остановить обработку', + 'start_date' => 'Начало диапазона', + 'end_date' => 'Конец диапазона', + 'export_start_range' => 'Начало диапазона для экспорта', + 'export_end_range' => 'Конец диапазона для экспорта', + 'export_format' => 'Формат файла', + 'include_attachments' => 'Включить загруженные вложения', + 'include_old_uploads' => 'Включить импортированные данные', + 'accounts' => 'Экспорт транзакций с этих счетов', + 'delete_account' => 'Удалить счёт ":name"', + 'delete_bill' => 'Удаление счёта к оплате ":name"', + 'delete_budget' => 'Удалить бюджет ":name"', + 'delete_category' => 'Удалить категорию ":name"', + 'delete_currency' => 'Удалить валюту ":name"', + 'delete_journal' => 'Удалить транзакцию с описанием ":description"', + 'delete_attachment' => 'Удалить вложение ":name"', + 'delete_rule' => 'Удалить правило ":title"', + 'delete_rule_group' => 'Удалить группу правил ":title"', + 'delete_link_type' => 'Удалить тип ссылки ":name"', + 'delete_user' => 'Удалить пользователя ":email"', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => 'Если вы удалите пользователя ":email", все данные будут удалены. Это действие нельзя будет отменить. Если вы удалите себя, вы потеряете доступ к этому экземпляру Firefly III.', + 'attachment_areYouSure' => 'Вы действительно хотите удалить вложение с именем ":name"?', + 'account_areYouSure' => 'Вы действительно хотите удалить счёт с именем ":name"?', + 'bill_areYouSure' => 'Вы действительно хотите удалить счёт на оплату с именем ":name"?', + 'rule_areYouSure' => 'Вы действительно хотите удалить правило с названием ":title"?', + 'ruleGroup_areYouSure' => 'Вы действительно хотите удалить группу правил с названием ":title"?', + 'budget_areYouSure' => 'Вы действительно хотите удалить бюджет с именем ":name"?', + 'category_areYouSure' => 'Вы действительно хотите удалить категорию с именем ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Вы уверены, что хотите удалить валюту ":name"?', + 'piggyBank_areYouSure' => 'Вы уверены, что хотите удалить копилку с именем ":name"?', + 'journal_areYouSure' => 'Вы действительно хотите удалить транзакцию с описанием ":description"?', + 'mass_journal_are_you_sure' => 'Вы действительно хотите удалить эти транзакции?', + 'tag_areYouSure' => 'Вы действительно хотите удалить метку ":tag"?', + 'journal_link_areYouSure' => 'Вы действительно хотите удалить связь между :source и :destination?', + 'linkType_areYouSure' => 'Вы уверены, что хотите удалить тип ссылки ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Удаление информации из Firefly III является постоянным и не может быть отменено.', + 'mass_make_selection' => 'Вы все же можете предотвратить удаление элементов, сняв флажок.', + 'delete_all_permanently' => 'Удалить выбранное навсегда', + 'update_all_journals' => 'Обновить эти транзакции', + 'also_delete_transactions' => 'Будет удалена только транзакция, связанная с этим счётом.|Будут удалены все :count транзакций, связанные с этим счётом.', + 'also_delete_connections' => 'Единственная транзакция, связанная с данным типом ссылки, потеряет это соединение. |Все :count транзакций, связанные с данным типом ссылки, потеряют свои соединения.', + 'also_delete_rules' => 'Единственное правило, связанное с данной группой правил, будет удалено. |Все :count правила, связанные с данной группой правил, будут удалены.', + 'also_delete_piggyBanks' => 'Единственная копилка, связанная с данным счётом, будет удалена.|Все :count копилки, связанные с данным счётом, будут удалены.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Проверить обновления', 'email' => 'Адрес электронной почты', 'password' => 'Пароль', @@ -223,15 +226,16 @@ return [ 'inward' => 'Внутреннее описание', 'outward' => 'Внешнее описание', 'rule_group_id' => 'Группа правил', - 'transaction_description' => 'Transaction description', - 'first_date' => 'First date', - 'transaction_type' => 'Transaction type', - 'repeat_until' => 'Repeat until', - 'recurring_description' => 'Recurring transaction description', - 'repetition_type' => 'Type of repetition', - 'foreign_currency_id' => 'Foreign currency', - 'repetition_end' => 'Repetition ends', - 'repetitions' => 'Repetitions', - 'calendar' => 'Calendar', + 'transaction_description' => 'Описание транзакции', + 'first_date' => 'Первая дата', + 'transaction_type' => 'Тип транзакции', + 'repeat_until' => 'Повторять до тех пор, пока', + 'recurring_description' => 'Описание повторяющейся транзакции', + 'repetition_type' => 'Тип повторения', + 'foreign_currency_id' => 'Иностранная валюта', + 'repetition_end' => 'Заканчивать повторение', + 'repetitions' => 'Повторения', + 'calendar' => 'Календарь', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/ru_RU/import.php b/resources/lang/ru_RU/import.php index 457e18aed2..3f572ed187 100644 --- a/resources/lang/ru_RU/import.php +++ b/resources/lang/ru_RU/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -169,23 +172,23 @@ return [ // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'job_config_roles_submit' => 'Continue', - 'job_config_roles_column_name' => 'Name of column', + 'job_config_roles_submit' => 'Продолжить', + 'job_config_roles_column_name' => 'Название столбца', 'job_config_roles_column_example' => 'Column example data', 'job_config_roles_column_role' => 'Column data meaning', 'job_config_roles_do_map_value' => 'Map these values', 'job_config_roles_no_example' => 'No example data available', 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', - 'job_config_roles_colum_count' => 'Column', + 'job_config_roles_colum_count' => 'Столбец', // job config for the file provider (stage: mapping): 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', - 'job_config_field_value' => 'Field value', - 'job_config_field_mapped' => 'Mapped to', + 'job_config_field_value' => 'Значение поля', + 'job_config_field_mapped' => 'Сопоставлено с', 'map_do_not_map' => '(не сопоставлено)', - 'job_config_map_submit' => 'Start the import', + 'job_config_map_submit' => 'Начать импорт', // import status page: @@ -196,11 +199,11 @@ return [ 'status_job_running' => 'Please wait, running the import...', 'status_job_storing' => 'Please wait, storing data...', 'status_job_rules' => 'Please wait, running rules...', - 'status_fatal_title' => 'Fatal error', + 'status_fatal_title' => 'Фатальная ошибка', 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', - 'status_finished_title' => 'Import finished', - 'status_finished_text' => 'The import has finished.', + 'status_finished_title' => 'Импорт завершён', + 'status_finished_text' => 'Импорт завершен!', 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', 'unknown_import_result' => 'Unknown import result', 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', diff --git a/resources/lang/ru_RU/intro.php b/resources/lang/ru_RU/intro.php index 8f21f6457e..0aaae3dcaf 100644 --- a/resources/lang/ru_RU/intro.php +++ b/resources/lang/ru_RU/intro.php @@ -111,7 +111,7 @@ return [ 'bills_create_skip_holder' => 'Если счёт выставляется каждые 2 недели, в поле "пропустить" нужно поставить "1", чтобы пропускать все прочие недели.', // rules index - 'rules_index_intro' => 'Firefly III позволяет вам использовать правилами, которые автоматически применяются к любой транзакции, которую вы создаёте или редактируете.', + 'rules_index_intro' => 'Firefly III позволяет вам использовать правила, автоматически применяющиеся к любой транзакции, которую вы создаёте или редактируете.', 'rules_index_new_rule_group' => 'Вы можете комбинировать правила в группы, чтобы упростить управление ими.', 'rules_index_new_rule' => 'Создайте столько правил, сколько захотите.', 'rules_index_prio_buttons' => 'Упорядочивайте их так, как вы считаете нужным.', diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index 162f8fe130..3d285ed97e 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -119,13 +119,13 @@ return [ 'file_type' => 'Тип файла', 'attached_to' => 'Прикреплено к', 'file_exists' => 'Файл существует', - 'spectre_bank' => 'Bank', - 'spectre_last_use' => 'Last login', - 'spectre_status' => 'Status', - 'bunq_payment_id' => 'bunq payment ID', - 'repetitions' => 'Repetitions', - 'title' => 'Title', - 'transaction_s' => 'Transaction(s)', - 'field' => 'Field', - 'value' => 'Value', + 'spectre_bank' => 'Банк', + 'spectre_last_use' => 'Последний вход', + 'spectre_status' => 'Статус', + 'bunq_payment_id' => 'ID платежа bunq', + 'repetitions' => 'Повторы', + 'title' => 'Название', + 'transaction_s' => 'Транзакции', + 'field' => 'Поле', + 'value' => 'Значение', ]; diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index 5d706d2353..b31a7ac8ec 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => 'Значение :attribute неизвестно', 'accepted' => 'Необходимо принять :attribute.', 'bic' => 'Это некорректный BIC.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute должен быть больше нуля.', 'active_url' => ':attribute не является допустимым URL-адресом.', 'after' => ':attribute должна быть позже :date.', @@ -110,9 +112,9 @@ return [ 'in_array' => 'Поле :attribute не существует в :other.', 'present' => 'Поле :attribute должно быть заполнено.', 'amount_zero' => 'Общее количество не может быть равно нулю', - 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', - 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', + 'unique_piggy_bank_for_user' => 'Название копилки должно быть уникальным.', + 'secure_password' => 'Это не безопасный пароль. Попробуйте еще раз. Подробнее можно узнать по ссылке http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Недопустимый тип для повторяющихся транзакций', 'attributes' => [ 'email' => '"Адрес электронной почты"', 'description' => '"Описание"', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 92f9c80f5a..b5bb107d15 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -466,6 +466,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'pref_two_factor_auth_code_help' => 'QR kodunu, telefonunuzda Authy veya Authenticator uygulamalarında bulun ve oluşturulan kodu girin.', 'pref_two_factor_auth_reset_code' => 'Doğrulama kodunu sıfırla', 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', 'pref_save_settings' => 'Ayarları kaydet', 'saved_preferences' => 'Tercihler kaydedildi!', 'preferences_general' => 'Genel', @@ -901,7 +902,6 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'balanceEnd' => 'Dönem Sonunda Bakiye', 'splitByAccount' => 'Hesaplara göre bölünmüş', 'coveredWithTags' => 'Etiketler kapatılmıştır', - 'leftUnbalanced' => 'Sol dengesiz', 'leftInBudget' => 'Bütçede bırakıldı', 'sumOfSums' => 'Hesap toplamı', 'noCategory' => '(Kategori yok)', @@ -1261,4 +1261,15 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'update_recurrence' => 'Update recurring transaction', 'updated_recurrence' => 'Updated recurring transaction ":title"', 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'New recurring transaction', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Just create the transaction', + 'skip_transaction' => 'Skip the occurence', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Except weekends', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', ]; diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index 9ec5758658..f467129a0f 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -94,90 +94,93 @@ return [ 'convert_Deposit' => 'Mevduata dönüştürün', 'convert_Transfer' => 'Aktarımı dönüştür', - 'amount' => 'Tutar', - 'foreign_amount' => 'Foreign amount', - 'existing_attachments' => 'Existing attachments', - 'date' => 'Tarih', - 'interest_date' => 'Faiz tarihi', - 'book_date' => 'Kitap Tarihi', - 'process_date' => 'İşlem tarihi', - 'category' => 'Kategori', - 'tags' => 'Etiketler', - 'deletePermanently' => 'Kalıcı olarak sil', - 'cancel' => 'İptal', - 'targetdate' => 'Hedeflenen tarih', - 'startdate' => 'Başlangıç Tarihi', - 'tag' => 'Etiket', - 'under' => 'Altında', - 'symbol' => 'Sembol', - 'code' => 'Kod', - 'iban' => 'IBAN numarası', - 'accountNumber' => 'Hesap numarası', - 'creditCardNumber' => 'Kredi Kartı Numarası', - 'has_headers' => 'Başlıklar', - 'date_format' => 'Tarih formatı', - 'specifix' => 'Banka veya dosyalara özel düzeltmeler', - 'attachments[]' => 'Ekler', - 'store_new_withdrawal' => 'Yeni para çekme oluştur', - 'store_new_deposit' => 'Yeni depozito oluştur', - 'store_new_transfer' => 'Yeni transfer oluştur', - 'add_new_withdrawal' => 'Yeni para çekme ekle', - 'add_new_deposit' => 'Yeni depozito ekle', - 'add_new_transfer' => 'Yeni bir transfer ekle', - 'title' => 'Başlık', - 'notes' => 'Notlar', - 'filename' => 'Dosya adı', - 'mime' => 'MIME türü', - 'size' => 'Boyut', - 'trigger' => 'Tetikleyici', - 'stop_processing' => 'İşlemeyi durdur', - 'start_date' => 'Sayfa başlangıcı', - 'end_date' => 'Kapsama alanı dışında', - 'export_start_range' => 'İhracatın başlangıcı', - 'export_end_range' => 'İhracatın sonu', - 'export_format' => 'Dosya biçimi', - 'include_attachments' => 'Yüklenen ekleri dahil et', - 'include_old_uploads' => 'İçe aktarılan verileri dahil et', - 'accounts' => 'Bu hesaptan işlemleri çıkarın', - 'delete_account' => '":name" adlı hesabı sil', - 'delete_bill' => 'Faturayı sil ":name"', - 'delete_budget' => '":name" bütçesini sil', - 'delete_category' => '":name" kategorisini sil', - 'delete_currency' => 'Para birimini sil ":name"', - 'delete_journal' => '":description" açıklamalı işlemi sil', - 'delete_attachment' => '":name" eklentisini sil', - 'delete_rule' => '\'":title" kuralını sil', - 'delete_rule_group' => '":title" kural grubunu sil', - 'delete_link_type' => '":name" bağlantı türünü sil', - 'delete_user' => '":email" kullanıcısını sil', - 'user_areYouSure' => '":email" kullanıcısını silerseniz her şey gitmiş olacak. Geriye alma, silmeyi iptal etme veya başka bir şey yoktur. Eğer kendinizi silerseniz, bu Firefly III\'e erişiminizi kaybedersiniz.', - 'attachment_areYouSure' => '":name" isimli eklentiyi silmek istediğinizden emin misiniz?', - 'account_areYouSure' => '":name" isimli hesabı silmek istediğinizden emin misiniz?', - 'bill_areYouSure' => '":name" isimli faturayı silmek istediğinizden emin misiniz?', - 'rule_areYouSure' => '":title" başlıklı kuralı silmek istediğinizden emin misiniz?', - 'ruleGroup_areYouSure' => '":title" başlıklı kural grubunu silmek istediğinizden emin misiniz?', - 'budget_areYouSure' => '":name" isimli bütçeyi silmek istediğinizden emin misiniz?', - 'category_areYouSure' => '":name" isimli kategoriyi silmek istediğinizden emin misiniz?', - 'currency_areYouSure' => '":name" isimli para birimini silmek istediğinizden emin misiniz?', - 'piggyBank_areYouSure' => '":name" isimli kumbarayı silmek istediğinizden emin misiniz?', - 'journal_areYouSure' => ':description" açıklamalı işlemi silmek istediğinizden emin misiniz?', - 'mass_journal_are_you_sure' => 'Bu işlemi silmek istediğinizden emin misiniz?', - 'tag_areYouSure' => '":tag" etiketini silmek istediğinizden emin misiniz?', - 'journal_link_areYouSure' => ':source and :destination arasındaki bağlantıyı silmek istediğinizden emin misiniz?', - 'linkType_areYouSure' => '":name" (":inward" / ":outward") bağlantı türünü silmek istediğinizden emin misiniz?', - 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', - 'mass_make_selection' => 'Onay kutusunu kaldırarak öğelerin silinmesini engelleyebilirsiniz.', - 'delete_all_permanently' => 'Seçilenleri kalıcı olarak sil', - 'update_all_journals' => 'Bu işlemleri güncelleyin', - 'also_delete_transactions' => 'Bu hesaba bağlı olan tek işlem de silinecektir. |Bu hesaba bağlı tüm :count işlemleri de silinecektir.', - 'also_delete_connections' => 'Bu bağlantıya bağlı tek işlem bağlantısını kaybedecek.| Bu bağlantı türüne bağlı tüm :count işlemleri bağlantılarını kaybedecek.', - 'also_delete_rules' => 'Bu hesaba bağlı olan tek kural grubu da silinecektir. |Bu hesaba bağlı tüm :count kuralları da silinecektir.', - 'also_delete_piggyBanks' => 'Bu hesaba bağlı olan tek kumbara da silinecektir. |Bu hesaba bağlı olan tüm :count kumbaraları da silinecektir.', - 'bill_keep_transactions' => 'Bu hesaba bağlı olan tek işlem de silinmeyecek. |Bu hesaba bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'budget_keep_transactions' => 'Bu bütçeye bağlı olan tek işlem silinmeyecek. |Bu bütçeye bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'category_keep_transactions' => 'Bu kategoriye bağlı olan tek işlem silinmeyecek. |Bu kategoriye bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'tag_keep_transactions' => 'Bu etikete bağlı olan tek işlem silinmeyecek. |Bu etikete bağlı tüm :count işlemleri de silinmeden muaf olacaklar.', - 'check_for_updates' => 'Check for updates', + 'amount' => 'Tutar', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Existing attachments', + 'date' => 'Tarih', + 'interest_date' => 'Faiz tarihi', + 'book_date' => 'Kitap Tarihi', + 'process_date' => 'İşlem tarihi', + 'category' => 'Kategori', + 'tags' => 'Etiketler', + 'deletePermanently' => 'Kalıcı olarak sil', + 'cancel' => 'İptal', + 'targetdate' => 'Hedeflenen tarih', + 'startdate' => 'Başlangıç Tarihi', + 'tag' => 'Etiket', + 'under' => 'Altında', + 'symbol' => 'Sembol', + 'code' => 'Kod', + 'iban' => 'IBAN numarası', + 'accountNumber' => 'Hesap numarası', + 'creditCardNumber' => 'Kredi Kartı Numarası', + 'has_headers' => 'Başlıklar', + 'date_format' => 'Tarih formatı', + 'specifix' => 'Banka veya dosyalara özel düzeltmeler', + 'attachments[]' => 'Ekler', + 'store_new_withdrawal' => 'Yeni para çekme oluştur', + 'store_new_deposit' => 'Yeni depozito oluştur', + 'store_new_transfer' => 'Yeni transfer oluştur', + 'add_new_withdrawal' => 'Yeni para çekme ekle', + 'add_new_deposit' => 'Yeni depozito ekle', + 'add_new_transfer' => 'Yeni bir transfer ekle', + 'title' => 'Başlık', + 'notes' => 'Notlar', + 'filename' => 'Dosya adı', + 'mime' => 'MIME türü', + 'size' => 'Boyut', + 'trigger' => 'Tetikleyici', + 'stop_processing' => 'İşlemeyi durdur', + 'start_date' => 'Sayfa başlangıcı', + 'end_date' => 'Kapsama alanı dışında', + 'export_start_range' => 'İhracatın başlangıcı', + 'export_end_range' => 'İhracatın sonu', + 'export_format' => 'Dosya biçimi', + 'include_attachments' => 'Yüklenen ekleri dahil et', + 'include_old_uploads' => 'İçe aktarılan verileri dahil et', + 'accounts' => 'Bu hesaptan işlemleri çıkarın', + 'delete_account' => '":name" adlı hesabı sil', + 'delete_bill' => 'Faturayı sil ":name"', + 'delete_budget' => '":name" bütçesini sil', + 'delete_category' => '":name" kategorisini sil', + 'delete_currency' => 'Para birimini sil ":name"', + 'delete_journal' => '":description" açıklamalı işlemi sil', + 'delete_attachment' => '":name" eklentisini sil', + 'delete_rule' => '\'":title" kuralını sil', + 'delete_rule_group' => '":title" kural grubunu sil', + 'delete_link_type' => '":name" bağlantı türünü sil', + 'delete_user' => '":email" kullanıcısını sil', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'user_areYouSure' => '":email" kullanıcısını silerseniz her şey gitmiş olacak. Geriye alma, silmeyi iptal etme veya başka bir şey yoktur. Eğer kendinizi silerseniz, bu Firefly III\'e erişiminizi kaybedersiniz.', + 'attachment_areYouSure' => '":name" isimli eklentiyi silmek istediğinizden emin misiniz?', + 'account_areYouSure' => '":name" isimli hesabı silmek istediğinizden emin misiniz?', + 'bill_areYouSure' => '":name" isimli faturayı silmek istediğinizden emin misiniz?', + 'rule_areYouSure' => '":title" başlıklı kuralı silmek istediğinizden emin misiniz?', + 'ruleGroup_areYouSure' => '":title" başlıklı kural grubunu silmek istediğinizden emin misiniz?', + 'budget_areYouSure' => '":name" isimli bütçeyi silmek istediğinizden emin misiniz?', + 'category_areYouSure' => '":name" isimli kategoriyi silmek istediğinizden emin misiniz?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => '":name" isimli para birimini silmek istediğinizden emin misiniz?', + 'piggyBank_areYouSure' => '":name" isimli kumbarayı silmek istediğinizden emin misiniz?', + 'journal_areYouSure' => ':description" açıklamalı işlemi silmek istediğinizden emin misiniz?', + 'mass_journal_are_you_sure' => 'Bu işlemi silmek istediğinizden emin misiniz?', + 'tag_areYouSure' => '":tag" etiketini silmek istediğinizden emin misiniz?', + 'journal_link_areYouSure' => ':source and :destination arasındaki bağlantıyı silmek istediğinizden emin misiniz?', + 'linkType_areYouSure' => '":name" (":inward" / ":outward") bağlantı türünü silmek istediğinizden emin misiniz?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'Onay kutusunu kaldırarak öğelerin silinmesini engelleyebilirsiniz.', + 'delete_all_permanently' => 'Seçilenleri kalıcı olarak sil', + 'update_all_journals' => 'Bu işlemleri güncelleyin', + 'also_delete_transactions' => 'Bu hesaba bağlı olan tek işlem de silinecektir. |Bu hesaba bağlı tüm :count işlemleri de silinecektir.', + 'also_delete_connections' => 'Bu bağlantıya bağlı tek işlem bağlantısını kaybedecek.| Bu bağlantı türüne bağlı tüm :count işlemleri bağlantılarını kaybedecek.', + 'also_delete_rules' => 'Bu hesaba bağlı olan tek kural grubu da silinecektir. |Bu hesaba bağlı tüm :count kuralları da silinecektir.', + 'also_delete_piggyBanks' => 'Bu hesaba bağlı olan tek kumbara da silinecektir. |Bu hesaba bağlı olan tüm :count kumbaraları da silinecektir.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Check for updates', 'email' => 'E-posta adresi', 'password' => 'Şifre', @@ -233,5 +236,6 @@ return [ 'repetition_end' => 'Repetition ends', 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', + 'weekend' => 'Weekend', ]; diff --git a/resources/lang/tr_TR/import.php b/resources/lang/tr_TR/import.php index dd1ecba61d..d5b413b0c1 100644 --- a/resources/lang/tr_TR/import.php +++ b/resources/lang/tr_TR/import.php @@ -127,13 +127,16 @@ return [ 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', 'imported_from_account' => 'Imported from ":account"', 'spectre_account_with_number' => 'Account :number', + 'job_config_spectre_apply_rules' => 'Apply rules', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq accounts', 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', 'bunq_no_mapping' => 'It seems you have not selected any accounts.', 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', - + 'job_config_bunq_apply_rules' => 'Apply rules', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index 151560afb2..e4ba5f403c 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -44,6 +44,8 @@ return [ 'belongs_to_user' => ':attribute\'nin değeri bilinmiyor', 'accepted' => ':attribute kabul edilmek zorunda.', 'bic' => 'Bu BIC geçerli değilrdir.', + 'base64' => 'This is not valid base64 encoded data.', + 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute sıfırdan büyük olmak zorundadır.', 'active_url' => ':attribute geçerli bir URL değil.', 'after' => ':attribute :date tarihinden sonrası için tarihlendirilmelidir.', From 57cf7f6f0d3cc40263d8cc5557e449d00a470d89 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 06:31:31 +0200 Subject: [PATCH 074/134] Update some things for recurring transactions. --- app/Console/Kernel.php | 3 --- app/Events/RequestedReportOnJournals.php | 3 ++- app/Jobs/CreateRecurringTransactions.php | 16 ++++++++++------ app/Mail/ReportNewJournalsMail.php | 8 +++++++- .../Recurring/RecurringRepository.php | 19 +++++++++++++++++-- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a9d5b137c3..bda9202d93 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -25,9 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Console; use Carbon\Carbon; -use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Jobs\CreateRecurringTransactions; -use FireflyIII\User; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -62,7 +60,6 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { - // create recurring transactions. $schedule->job(new CreateRecurringTransactions(new Carbon))->daily(); } } diff --git a/app/Events/RequestedReportOnJournals.php b/app/Events/RequestedReportOnJournals.php index 9956ee62fd..d4bdf0dbb7 100644 --- a/app/Events/RequestedReportOnJournals.php +++ b/app/Events/RequestedReportOnJournals.php @@ -24,7 +24,8 @@ class RequestedReportOnJournals /** * Create a new event instance. * - * @return void + * @param int $userId + * @param Collection $journals */ public function __construct(int $userId, Collection $journals) { diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 37319dc958..05b036b5ed 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -51,7 +51,7 @@ class CreateRecurringTransactions implements ShouldQueue */ public function handle(): void { - Log::debug('Now at start of CreateRecurringTransactions() job.'); + Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y'))); $recurrences = $this->repository->getAll(); Log::debug(sprintf('Count of collection is %d', $recurrences->count())); @@ -125,9 +125,6 @@ class CreateRecurringTransactions implements ShouldQueue $startDate = clone $recurrence->first_date; if (null !== $recurrence->latest_date && $recurrence->latest_date->gte($startDate)) { $startDate = clone $recurrence->latest_date; - // jump to a day later. - $startDate->addDay(); - } return $startDate; @@ -250,12 +247,19 @@ class CreateRecurringTransactions implements ShouldQueue ); // start looping from $startDate to today perhaps we have a hit? - $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $this->date); + // add two days to $this->date so we always include the weekend. + $includeWeekend = clone $this->date; + $includeWeekend->addDays(2); + $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $includeWeekend); Log::debug( sprintf( - 'Calculated %d occurrences between %s and %s', \count($occurrences), $recurrence->first_date->format('Y-m-d'), $this->date->format('Y-m-d') + 'Calculated %d occurrences between %s and %s', + \count($occurrences), + $recurrence->first_date->format('Y-m-d'), + $includeWeekend->format('Y-m-d') ), $this->debugArray($occurrences) ); + unset($includeWeekend); $result = $this->handleOccurrences($recurrence, $occurrences); $collection = $collection->merge($result); diff --git a/app/Mail/ReportNewJournalsMail.php b/app/Mail/ReportNewJournalsMail.php index d1f1d2445e..a483625c1a 100644 --- a/app/Mail/ReportNewJournalsMail.php +++ b/app/Mail/ReportNewJournalsMail.php @@ -65,7 +65,13 @@ class ReportNewJournalsMail extends Mailable */ public function build(): self { + $subject = $this->journals->count() === 1 + ? 'Firefly III has created a new transaction' + : sprintf( + 'Firefly III has created new %d transactions', $this->journals->count() + ); + return $this->view('emails.report-new-journals-html')->text('emails.report-new-journals-text') - ->subject('Firefly III has created new transactions'); + ->subject($subject); } } diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 8776c8d2de..1046fb41f3 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -547,6 +547,8 @@ class RecurringRepository implements RecurringRepositoryInterface protected function filterWeekends(RecurrenceRepetition $repetition, array $dates): array { if ($repetition->weekend === RecurrenceRepetition::WEEKEND_DO_NOTHING) { + Log::debug('Repetition will not be filtered on weekend days.'); + return $dates; } $return = []; @@ -555,29 +557,42 @@ class RecurringRepository implements RecurringRepositoryInterface $isWeekend = $date->isWeekend(); if (!$isWeekend) { $return[] = clone $date; + Log::debug(sprintf('Date is %s, not a weekend date.', $date->format('D d M Y'))); continue; } // is weekend and must set back to Friday? - if ($isWeekend && $repetition->weekend === RecurrenceRepetition::WEEKEND_TO_FRIDAY) { + if ($repetition->weekend === RecurrenceRepetition::WEEKEND_TO_FRIDAY) { $clone = clone $date; $clone->addDays(5 - $date->dayOfWeekIso); + Log::debug( + sprintf('Date is %s, and this is in the weekend, so corrected to %s (Friday).', $date->format('D d M Y'), $clone->format('D d M Y')) + ); $return[] = clone $clone; + continue; } // postpone to Monday? - if ($isWeekend && $repetition->weekend === RecurrenceRepetition::WEEKEND_TO_MONDAY) { + if ($repetition->weekend === RecurrenceRepetition::WEEKEND_TO_MONDAY) { $clone = clone $date; $clone->addDays(8 - $date->dayOfWeekIso); + Log::debug( + sprintf('Date is %s, and this is in the weekend, so corrected to %s (Monday).', $date->format('D d M Y'), $clone->format('D d M Y')) + ); $return[] = $clone; + continue; } + Log::debug(sprintf('Date is %s, removed from final result', $date->format('D d M Y'))); } // filter unique dates + Log::debug(sprintf('Count before filtering: %d', \count($dates))); $collection = new Collection($return); $filtered = $collection->unique(); $return = $filtered->toArray(); + Log::debug(sprintf('Count after filtering: %d', \count($return))); + return $return; } } \ No newline at end of file From e906fa365399a211fdb4c508e4b3ce43e61292b2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 06:34:36 +0200 Subject: [PATCH 075/134] Bot cannot close bug tickets [skip ci] --- .github/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/stale.yml b/.github/stale.yml index 5acff1e12e..8f9da31fcd 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -12,6 +12,7 @@ daysUntilClose: 7 exemptLabels: - enhancement - feature + - bug # Set to true to ignore issues in a project (defaults to false) exemptProjects: false From 7bdf20fee5de7f806a5556f5ebfa7ad44ce05ee0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 06:40:04 +0200 Subject: [PATCH 076/134] Fix #1446 --- resources/views/list/piggy-bank-events.twig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/views/list/piggy-bank-events.twig b/resources/views/list/piggy-bank-events.twig index 43c815b54a..d5f76e195b 100644 --- a/resources/views/list/piggy-bank-events.twig +++ b/resources/views/list/piggy-bank-events.twig @@ -23,10 +23,11 @@ + {% if event.amount < 0 %} - {{ trans('firefly.removed_amount', {amount: (event.amount)|formatAmountPlain})|raw }} + {{ trans('firefly.removed_amount', {amount: formatAmountByAccount(event.piggyBank.account, event.amount, false)})|raw }} {% else %} - {{ trans('firefly.added_amount', {amount: (event.amount)|formatAmountPlain})|raw }} + {{ trans('firefly.added_amount', {amount: formatAmountByAccount(event.piggyBank.account, event.amount, false)})|raw }} {% endif %} From cfd98a33fe89211286b4a7ed3ba78895aae12b95 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 06:43:59 +0200 Subject: [PATCH 077/134] Fix #1463 --- app/Support/ExpandedForm.php | 44 +++++++++++++++++-- config/twigbridge.php | 2 +- .../views/import/file/configure-upload.twig | 2 +- resources/views/piggy-banks/create.twig | 2 +- resources/views/recurring/create.twig | 4 +- resources/views/transactions/convert.twig | 4 +- .../views/transactions/single/create.twig | 4 +- resources/views/transactions/split/edit.twig | 4 +- 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index a5c5ee8a3f..b6a46453b1 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -45,6 +45,45 @@ use Session; */ class ExpandedForm { + /** + * @param string $name + * @param null $options + * + * @return string + * @throws \Throwable + */ + public function activeAssetAccountList(string $name, $value = null, array $options = []): string + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + + $assetAccounts = $repository->getActiveAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($assetAccounts as $account) { + $balance = app('steam')->balance($account, new Carbon); + $currencyId = (int)$account->getMeta('currency_id'); + $currency = $currencyRepos->findNull($currencyId); + $role = $account->getMeta('accountRole'); + if ('' === $role) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + if (null === $currency) { + $currency = $defaultCurrency; + } + + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; + } + + return $this->select($name, $grouped, $value, $options); + } + /** * @param string $name * @param null $value @@ -101,7 +140,6 @@ class ExpandedForm /** * @param string $name - * @param $selected * @param null $options * * @return string @@ -263,7 +301,7 @@ class ExpandedForm // get all currencies: $list = $currencyRepos->get(); $array = [ - 0 => trans('firefly.no_currency') + 0 => trans('firefly.no_currency'), ]; /** @var TransactionCurrency $currency */ foreach ($list as $currency) { @@ -515,7 +553,7 @@ class ExpandedForm */ public function optionsList(string $type, string $name): string { - $html = view('form.options', compact('type', 'name'))->render(); + $html = view('form.options', compact('type', 'name'))->render(); return $html; } diff --git a/config/twigbridge.php b/config/twigbridge.php index 484eafd88a..f6a6e22ae3 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -189,7 +189,7 @@ return [ 'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', 'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount', 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList','ruleGroupListWithEmpty', - 'piggyBankList','currencyListEmpty' + 'piggyBankList','currencyListEmpty','activeAssetAccountList' ], ], 'Form' => [ diff --git a/resources/views/import/file/configure-upload.twig b/resources/views/import/file/configure-upload.twig index 59de0c8a74..4e1e31c46c 100644 --- a/resources/views/import/file/configure-upload.twig +++ b/resources/views/import/file/configure-upload.twig @@ -38,7 +38,7 @@ {{ ExpandedForm.checkbox('has_headers',1,importJob.configuration['has-headers'],{helpText: trans('import.job_config_uc_header_help')}) }} {{ ExpandedForm.text('date_format',importJob.configuration['date-format'],{helpText: trans('import.job_config_uc_date_help', {dateExample: phpdate('Ymd')}) }) }} {{ ExpandedForm.select('csv_delimiter', data.delimiters, importJob.configuration['delimiter'], {helpText: trans('import.job_config_uc_delimiter_help') } ) }} - {{ ExpandedForm.assetAccountList('csv_import_account', importJob.configuration['import-account'], {helpText: trans('import.job_config_uc_account_help')}) }} + {{ ExpandedForm.activeAssetAccountList('csv_import_account', importJob.configuration['import-account'], {helpText: trans('import.job_config_uc_account_help')}) }}

    {{ 'optionalFields'|_ }}

    diff --git a/resources/views/piggy-banks/create.twig b/resources/views/piggy-banks/create.twig index adae7f9b34..332d8fc041 100644 --- a/resources/views/piggy-banks/create.twig +++ b/resources/views/piggy-banks/create.twig @@ -18,7 +18,7 @@
    {{ ExpandedForm.text('name') }} - {{ ExpandedForm.assetAccountList('account_id', null, {label: 'saveOnAccount'|_ }) }} + {{ ExpandedForm.activeAssetAccountList('account_id', null, {label: 'saveOnAccount'|_ }) }} {{ ExpandedForm.amountNoCurrency('targetamount') }}
    diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 7af6a7082f..d88455cfaa 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -89,13 +89,13 @@ {{ ExpandedForm.amountNoCurrency('amount', []) }} {# source account if withdrawal, or if transfer: #} - {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.activeAssetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} {# source account name for deposits: #} {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} {# destination if deposit or transfer: #} - {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.activeAssetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} {# destination account name for withdrawals #} {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} diff --git a/resources/views/transactions/convert.twig b/resources/views/transactions/convert.twig index 8ab8cedb31..2406a5ebe6 100644 --- a/resources/views/transactions/convert.twig +++ b/resources/views/transactions/convert.twig @@ -92,7 +92,7 @@

    - {{ ExpandedForm.assetAccountList('destination_account_asset', null) }} + {{ ExpandedForm.activeAssetAccountList('destination_account_asset', null) }} {% endif %} @@ -145,7 +145,7 @@

    - {{ ExpandedForm.assetAccountList('source_account_asset', null) }} + {{ ExpandedForm.activeAssetAccountList('source_account_asset', null) }} {% endif %} {# FIVE #} diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index f6d8e7b02a..63fa0fc6cc 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -33,7 +33,7 @@ {{ ExpandedForm.text('description') }} {# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #} - {{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.activeAssetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} {# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #} {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} @@ -42,7 +42,7 @@ {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} {# SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS #} - {{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.activeAssetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} {# ALWAYS SHOW AMOUNT #} {{ ExpandedForm.amount('amount') }} diff --git a/resources/views/transactions/split/edit.twig b/resources/views/transactions/split/edit.twig index b4dd2ad247..898b872907 100644 --- a/resources/views/transactions/split/edit.twig +++ b/resources/views/transactions/split/edit.twig @@ -42,12 +42,12 @@ {# show source if withdrawal or transfer #} {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} - {{ ExpandedForm.assetAccountList('journal_source_account_id', preFilled.journal_source_account_id) }} + {{ ExpandedForm.activeAssetAccountList('journal_source_account_id', preFilled.journal_source_account_id) }} {% endif %} {# show destination account id, if deposit (is asset): #} {% if preFilled.what == 'deposit' or preFilled.what == 'transfer' %} - {{ ExpandedForm.assetAccountList('journal_destination_account_id', preFilled.journal_destination_account_id) }} + {{ ExpandedForm.activeAssetAccountList('journal_destination_account_id', preFilled.journal_destination_account_id) }} {% endif %} {# show amount and some helper text when making splits: #} From f55d4e32c0e84d47731877fe0e52d1221ba36890 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 07:32:58 +0200 Subject: [PATCH 078/134] Implement currency exchange rate API. --- app/Api/V1/Controllers/BudgetController.php | 4 +- .../V1/Controllers/BudgetLimitController.php | 6 +- app/Api/V1/Controllers/CategoryController.php | 10 +- .../Controllers/ConfigurationController.php | 1 + .../CurrencyExchangeRateController.php | 115 ++++++++++++++++++ .../Controllers/Json/ExchangeController.php | 2 +- app/Models/CurrencyExchangeRate.php | 9 ++ app/Models/TransactionCurrency.php | 1 + .../Currency/CurrencyRepository.php | 10 +- .../Currency/CurrencyRepositoryInterface.php | 6 +- .../CurrencyExchangeRateTransformer.php | 99 +++++++++++++++ routes/api.php | 9 ++ 12 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 app/Api/V1/Controllers/CurrencyExchangeRateController.php create mode 100644 app/Transformers/CurrencyExchangeRateTransformer.php diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php index 5b153f5027..d2a2ba2041 100644 --- a/app/Api/V1/Controllers/BudgetController.php +++ b/app/Api/V1/Controllers/BudgetController.php @@ -162,12 +162,12 @@ class BudgetController extends Controller public function update(BudgetRequest $request, Budget $budget): JsonResponse { $data = $request->getAll(); - $bill = $this->repository->update($budget, $data); + $budget = $this->repository->update($budget, $data); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - $resource = new Item($bill, new BudgetTransformer($this->parameters), 'budgets'); + $resource = new Item($budget, new BudgetTransformer($this->parameters), 'budgets'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php index 70c452157d..891ae9f098 100644 --- a/app/Api/V1/Controllers/BudgetLimitController.php +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -191,9 +191,9 @@ class BudgetLimitController extends Controller throw new FireflyException('Unknown budget.'); } $data['budget'] = $budget; - $budgetLimit = $this->repository->storeBudgetLimit($data); - $manager = new Manager; - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $budgetLimit = $this->repository->storeBudgetLimit($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); $resource = new Item($budgetLimit, new BudgetLimitTransformer($this->parameters), 'budget_limits'); diff --git a/app/Api/V1/Controllers/CategoryController.php b/app/Api/V1/Controllers/CategoryController.php index 672b044ea0..fc33a6fe16 100644 --- a/app/Api/V1/Controllers/CategoryController.php +++ b/app/Api/V1/Controllers/CategoryController.php @@ -161,13 +161,13 @@ class CategoryController extends Controller */ public function update(CategoryRequest $request, Category $category): JsonResponse { - $data = $request->getAll(); - $bill = $this->repository->update($category, $data); - $manager = new Manager(); - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $data = $request->getAll(); + $category = $this->repository->update($category, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - $resource = new Item($bill, new CategoryTransformer($this->parameters), 'categories'); + $resource = new Item($category, new CategoryTransformer($this->parameters), 'categories'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); diff --git a/app/Api/V1/Controllers/ConfigurationController.php b/app/Api/V1/Controllers/ConfigurationController.php index 40c019f943..9bedd0a459 100644 --- a/app/Api/V1/Controllers/ConfigurationController.php +++ b/app/Api/V1/Controllers/ConfigurationController.php @@ -50,6 +50,7 @@ class ConfigurationController extends Controller /** * @param Request $request * + * @return JsonResponse * @throws FireflyException */ public function update(Request $request): JsonResponse diff --git a/app/Api/V1/Controllers/CurrencyExchangeRateController.php b/app/Api/V1/Controllers/CurrencyExchangeRateController.php new file mode 100644 index 0000000000..54d8672b0e --- /dev/null +++ b/app/Api/V1/Controllers/CurrencyExchangeRateController.php @@ -0,0 +1,115 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Services\Currency\ExchangeRateInterface; +use FireflyIII\Transformers\CurrencyExchangeRateTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use InvalidArgumentException; +use League\Fractal\Manager; +use League\Fractal\Resource\Item; +use Log; + +/** + * + * Class CurrencyExchangeRateController + */ +class CurrencyExchangeRateController extends Controller +{ + /** @var CurrencyRepositoryInterface */ + private $repository; + + /** + * CurrencyExchangeRateController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + + } + + /** + * @param Request $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // currencies + $fromCurrency = $this->repository->findByCodeNull($request->get('from') ?? 'EUR'); + $toCurrency = $this->repository->findByCodeNull($request->get('to') ?? 'USD'); + + if (null === $fromCurrency) { + throw new FireflyException('Unknown source currency.'); + } + if (null === $toCurrency) { + throw new FireflyException('Unknown destination currency.'); + } + + $dateObj = new Carbon; + try { + $dateObj = Carbon::createFromFormat('Y-m-d', $request->get('date') ?? date('Y-m-d')); + } catch (InvalidArgumentException $e) { + Log::debug($e->getMessage()); + } + + + $this->parameters->set('from', $fromCurrency->code); + $this->parameters->set('to', $toCurrency->code); + $this->parameters->set('date', $dateObj->format('Y-m-d')); + + // get the exchange rate. + $rate = $this->repository->getExchangeRate($fromCurrency, $toCurrency, $dateObj); + if (null === $rate) { + // create service: + /** @var ExchangeRateInterface $service */ + $service = app(ExchangeRateInterface::class); + $service->setUser(auth()->user()); + + // get rate: + $rate = $service->getRate($fromCurrency, $toCurrency, $dateObj); + } + + $resource = new Item($rate, new CurrencyExchangeRateTransformer($this->parameters), 'currency_exchange_rates'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Json/ExchangeController.php b/app/Http/Controllers/Json/ExchangeController.php index e4846f43f7..34b8eeddd6 100644 --- a/app/Http/Controllers/Json/ExchangeController.php +++ b/app/Http/Controllers/Json/ExchangeController.php @@ -50,7 +50,7 @@ class ExchangeController extends Controller $rate = $repository->getExchangeRate($fromCurrency, $toCurrency, $date); - if (null === $rate->id) { + if (null === $rate) { Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); // create service: diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index 7701cb9fff..6f06cc8862 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -22,12 +22,21 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class CurrencyExchange. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property TransactionCurrency $fromCurrency + * @property TransactionCurrency $toCurrency + * @property float $rate + * */ class CurrencyExchangeRate extends Model { diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index e54c7513a6..5948f38811 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -32,6 +32,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $code * @property string $symbol * @property int $decimal_places + * @property int $id * */ class TransactionCurrency extends Model diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 4258432124..f754c1bf0a 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -265,13 +265,15 @@ class CurrencyRepository implements CurrencyRepositoryInterface } /** + * Get currency exchange rate. + * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency * @param Carbon $date * - * @return CurrencyExchangeRate + * @return CurrencyExchangeRate|null */ - public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate { if ($fromCurrency->id === $toCurrency->id) { $rate = new CurrencyExchangeRate; @@ -280,7 +282,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $rate; } - + /** @var CurrencyExchangeRate $rate */ $rate = $this->user->currencyExchangeRates() ->where('from_currency_id', $fromCurrency->id) ->where('to_currency_id', $toCurrency->id) @@ -291,7 +293,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $rate; } - return new CurrencyExchangeRate; + return null; } /** diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 6f1be847d7..72f12a0188 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -154,13 +154,15 @@ interface CurrencyRepositoryInterface public function getCurrencyByPreference(Preference $preference): TransactionCurrency; /** + * Get currency exchange rate. + * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency * @param Carbon $date * - * @return CurrencyExchangeRate + * @return CurrencyExchangeRate|null */ - public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate; + public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): ?CurrencyExchangeRate; /** * @param User $user diff --git a/app/Transformers/CurrencyExchangeRateTransformer.php b/app/Transformers/CurrencyExchangeRateTransformer.php new file mode 100644 index 0000000000..228ef4dbbb --- /dev/null +++ b/app/Transformers/CurrencyExchangeRateTransformer.php @@ -0,0 +1,99 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\CurrencyExchangeRate; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class CurrencyExchangeRateTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['from_currency', 'to_currency']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['from_currency', 'to_currency']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * PiggyBankEventTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param CurrencyExchangeRate $rate + * + * @return Item + */ + public function includeFromCurrency(CurrencyExchangeRate $rate): Item + { + return $this->item($rate->fromCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + /** + * @param CurrencyExchangeRate $rate + * + * @return \League\Fractal\Resource\Item + */ + public function includeToCurrency(CurrencyExchangeRate $rate): Item + { + return $this->item($rate->toCurrency, new CurrencyTransformer($this->parameters), 'transaction_currencies'); + } + + public function transform(CurrencyExchangeRate $rate): array + { + $data = [ + 'id' => (int)$rate->id, + 'updated_at' => $rate->updated_at->toAtomString(), + 'created_at' => $rate->created_at->toAtomString(), + 'rate' => (float)$rate->rate, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/currency_exchange_rates/' . $rate->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 24a9fab273..fb49221135 100644 --- a/routes/api.php +++ b/routes/api.php @@ -134,6 +134,15 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'cer', 'as' => 'api.v1.cer.'], + function () { + + // Currency Exchange Rate API routes: + Route::get('', ['uses' => 'CurrencyExchangeRateController@index', 'as' => 'index']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () { From 7749fb1a0b437f8f93ea226c3305e00092566ec3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 17:02:13 +0200 Subject: [PATCH 079/134] Expand API for journal links. --- .../V1/Controllers/JournalLinkController.php | 209 ++++++++++++++++++ app/Api/V1/Requests/CurrencyRequest.php | 2 +- app/Api/V1/Requests/JournalLinkRequest.php | 68 ++++++ app/Models/LinkType.php | 11 +- app/Models/Note.php | 2 + app/Models/TransactionJournalLink.php | 12 + .../LinkType/LinkTypeRepository.php | 122 +++++++++- .../LinkType/LinkTypeRepositoryInterface.php | 44 +++- app/Transformers/JournalLinkTransformer.php | 146 ++++++++++++ app/Transformers/LinkTypeTransformer.php | 89 ++++++++ resources/lang/en_US/firefly.php | 2 + resources/views/admin/link/show.twig | 4 +- routes/api.php | 13 ++ 13 files changed, 707 insertions(+), 17 deletions(-) create mode 100644 app/Api/V1/Controllers/JournalLinkController.php create mode 100644 app/Api/V1/Requests/JournalLinkRequest.php create mode 100644 app/Transformers/JournalLinkTransformer.php create mode 100644 app/Transformers/LinkTypeTransformer.php diff --git a/app/Api/V1/Controllers/JournalLinkController.php b/app/Api/V1/Controllers/JournalLinkController.php new file mode 100644 index 0000000000..c54b42484e --- /dev/null +++ b/app/Api/V1/Controllers/JournalLinkController.php @@ -0,0 +1,209 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\JournalLinkRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionJournalLink; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Transformers\JournalLinkTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +class JournalLinkController extends Controller +{ + /** @var JournalRepositoryInterface */ + private $journalRepository; + /** @var LinkTypeRepositoryInterface */ + private $repository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + $this->repository = app(LinkTypeRepositoryInterface::class); + $this->journalRepository = app(JournalRepositoryInterface::class); + + $this->repository->setUser($user); + $this->journalRepository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + + + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // read type from URI + $name = $request->get('name') ?? null; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + $linkType = $this->repository->findByName($name); + + // get list of accounts. Count it and split it. + $collection = $this->repository->getJournalLinks($linkType); + $count = $collection->count(); + $journalLinks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($journalLinks, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.journal_links.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($journalLinks, new JournalLinkTransformer($this->parameters), 'journal_links'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param TransactionJournalLink $journalLink + * + * @return JsonResponse + */ + public function show(Request $request, TransactionJournalLink $journalLink): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($journalLink, new JournalLinkTransformer($this->parameters), 'journal_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param JournalLinkRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(JournalLinkRequest $request): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $data = $request->getAll(); + $inward = $this->journalRepository->findNull($data['inward_id'] ?? 0); + $outward = $this->journalRepository->findNull($data['outward_id'] ?? 0); + if (null === $inward || null === $outward) { + throw new FireflyException('Source or destination is NULL.'); + } + $data['direction'] = 'inward'; + + $journalLink = $this->repository->storeLink($data, $inward, $outward); + + $resource = new Item($journalLink, new JournalLinkTransformer($this->parameters), 'journal_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param JournalLinkRequest $request + * @param TransactionJournalLink $journalLink + * + * @return JsonResponse + * @throws FireflyException + */ + public function update(JournalLinkRequest $request, TransactionJournalLink $journalLink): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + + $data = $request->getAll(); + $data['inward'] = $this->journalRepository->findNull($data['inward_id'] ?? 0); + $data['outward'] = $this->journalRepository->findNull($data['outward_id'] ?? 0); + if (null === $data['inward'] || null === $data['outward']) { + throw new FireflyException('Source or destination is NULL.'); + } + $data['direction'] = 'inward'; + $journalLink = $this->repository->updateLink($journalLink, $data); + + $resource = new Item($journalLink, new JournalLinkTransformer($this->parameters), 'journal_links'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/CurrencyRequest.php b/app/Api/V1/Requests/CurrencyRequest.php index 8e2edfeda9..6c63c83ddc 100644 --- a/app/Api/V1/Requests/CurrencyRequest.php +++ b/app/Api/V1/Requests/CurrencyRequest.php @@ -41,7 +41,7 @@ class CurrencyRequest extends Request /** * @return array */ - public function getAll() + public function getAll(): array { return [ 'name' => $this->string('name'), diff --git a/app/Api/V1/Requests/JournalLinkRequest.php b/app/Api/V1/Requests/JournalLinkRequest.php new file mode 100644 index 0000000000..e52d5b7484 --- /dev/null +++ b/app/Api/V1/Requests/JournalLinkRequest.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + + +/** + * + * Class JournalLinkRequest + */ +class JournalLinkRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'link_type_id' => $this->integer('link_type_id'), + 'inward_id' => $this->integer('inward_id'), + 'outward_id' => $this->integer('outward_id'), + 'notes' => $this->string('notes'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'link_type_id' => 'required|exists:link_types,id', + 'inward_id' => 'required|belongsToUser:transaction_journals,id', + 'outward_id' => 'required|belongsToUser:transaction_journals,id', + 'notes' => 'between:0,65000', + ]; + } + +} \ No newline at end of file diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index 04dd71f3aa..a39fc05041 100644 --- a/app/Models/LinkType.php +++ b/app/Models/LinkType.php @@ -22,12 +22,21 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** - * @property int $journalCount + * @property int $journalCount + * @property string $inward + * @property string $outward + * @property string $name + * @property bool $editable + * @property Carbon $created_at + * @property Carbon $updated_at + * @property int $id * Class LinkType + * */ class LinkType extends Model { diff --git a/app/Models/Note.php b/app/Models/Note.php index 316b65089e..81ebc4a9ff 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; /** * Class Note. @@ -36,6 +37,7 @@ use Illuminate\Database\Eloquent\Model; */ class Note extends Model { + use SoftDeletes; /** * The attributes that should be casted to native types. * diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php index af47153390..aa5146b3e7 100644 --- a/app/Models/TransactionJournalLink.php +++ b/app/Models/TransactionJournalLink.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -29,6 +30,17 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class TransactionJournalLink. + * + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $comment + * @property TransactionJournal $source + * @property TransactionJournal $destination + * @property LinkType $linkType + * @property int $link_type_id + * @property int $source_id + * @property int $destination_id */ class TransactionJournalLink extends Model { diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index a4eb99cff6..76fa74d748 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\LinkType; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\LinkType; use FireflyIII\Models\Note; @@ -94,6 +95,20 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $linkType; } + /** + * @param string|null $name + * + * @return LinkType|null + */ + public function findByName(string $name = null): ?LinkType + { + if (null === $name) { + return null; + } + + return LinkType::where('name', $name)->first(); + } + /** * Check if link exists between journals. * @@ -110,6 +125,24 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $count + $opposingCount > 0; } + /** + * See if such a link already exists (and get it). + * + * @param LinkType $linkType + * @param TransactionJournal $inward + * @param TransactionJournal $outward + * + * @return TransactionJournalLink|null + */ + public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink + { + return TransactionJournalLink + ::where('link_type_id', $linkType->id) + ->where('source_id', $inward->id) + ->where('destination_id', $outward->id)->first(); + + } + /** * @return Collection */ @@ -118,6 +151,30 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return LinkType::orderBy('name', 'ASC')->get(); } + /** + * Returns all the journal links (of a specific type). + * + * @param $linkType + * + * @return Collection + */ + public function getJournalLinks(LinkType $linkType = null): Collection + { + $query = TransactionJournalLink + ::leftJoin('transaction_journals as source_journals', 'journal_links.source_id', '=', 'source_journals.id') + ->leftJoin('transaction_journals as dest_journals', 'journal_links.destination_id', '=', 'dest_journals.id') + ->where('source_journals.user_id', $this->user->id) + ->where('dest_journals.user_id', $this->user->id) + ->whereNull('source_journals.deleted_at') + ->whereNull('dest_journals.deleted_at'); + + if (null !== $linkType) { + $query->where('journal_links.link_type_id', $linkType->id); + } + + return $query->get(['journal_links.*']); + } + /** * Return list of existing connections. * @@ -169,35 +226,42 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface * Store link between two journals. * * @param array $information - * @param TransactionJournal $left - * @param TransactionJournal $right + * @param TransactionJournal $inward + * @param TransactionJournal $outward * * @return mixed * @throws FireflyException */ - public function storeLink(array $information, TransactionJournal $left, TransactionJournal $right): TransactionJournalLink + public function storeLink(array $information, TransactionJournal $inward, TransactionJournal $outward): TransactionJournalLink { $linkType = $this->find((int)($information['link_type_id'] ?? 0)); if (null === $linkType->id) { throw new FireflyException(sprintf('Link type #%d cannot be resolved to an actual link type', $information['link_type_id'] ?? 0)); } + + // might exist already: + $existing = $this->findSpecificLink($linkType, $inward, $outward); + if (null !== $existing) { + return $existing; + } + $link = new TransactionJournalLink; $link->linkType()->associate($linkType); if ('inward' === $information['direction']) { - Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $left->id, $right->id)); - $link->source()->associate($left); - $link->destination()->associate($right); + Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $inward->id, $outward->id)); + $link->source()->associate($inward); + $link->destination()->associate($outward); } if ('outward' === $information['direction']) { - Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $right->id, $left->id)); - $link->source()->associate($right); - $link->destination()->associate($left); + Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $outward->id, $inward->id)); + $link->source()->associate($outward); + $link->destination()->associate($inward); } $link->save(); // make note in noteable: - if (\strlen($information['notes']) > 0) { + if (\strlen((string)$information['notes']) > 0) { $dbNote = $link->notes()->first(); if (null === $dbNote) { $dbNote = new Note(); @@ -240,4 +304,42 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $linkType; } + + /** + * Update an existing transaction journal link. + * + * @param TransactionJournalLink $journalLink + * @param array $data + * + * @return TransactionJournalLink + */ + public function updateLink(TransactionJournalLink $journalLink, array $data): TransactionJournalLink + { + $journalLink->source_id = $data['inward']->id; + $journalLink->destination_id = $data['outward']->id; + $journalLink->link_type_id = $data['link_type_id']; + $journalLink->save(); + /** @var Note $note */ + $note = $journalLink->notes()->first(); + // delete note: + if (null !== $note && '' === $data['notes']) { + try { + $note->delete(); + } catch (Exception $e) { + Log::debug(sprintf('Could not delete note for journal link: %s', $e->getMessage())); + } + } + // create note: + if (null === $note && '' !== $data['notes']) { + $note = new Note; + $note->noteable()->associate($journalLink); + } + // update note + if ('' !== $data['notes']) { + $note->text = $data['notes']; + $note->save(); + } + + return $journalLink; + } } diff --git a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php index 19ae1c8846..a0c8d2c636 100644 --- a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php +++ b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php @@ -57,10 +57,20 @@ interface LinkTypeRepositoryInterface /** * @param int $id * + * @deprecated * @return LinkType */ public function find(int $id): LinkType; + /** + * Find link type by name. + * + * @param string|null $name + * + * @return LinkType|null + */ + public function findByName(string $name = null): ?LinkType; + /** * Check if link exists between journals. * @@ -71,11 +81,29 @@ interface LinkTypeRepositoryInterface */ public function findLink(TransactionJournal $one, TransactionJournal $two): bool; + /** + * See if such a link already exists (and get it). + * + * @param LinkType $linkType + * @param TransactionJournal $inward + * @param TransactionJournal $outward + * + * @return TransactionJournalLink|null + */ + public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink; + /** * @return Collection */ public function get(): Collection; + /** + * @param LinkType|null $linkType + * + * @return Collection + */ + public function getJournalLinks(LinkType $linkType = null): Collection; + /** * Return list of existing connections. * @@ -96,12 +124,12 @@ interface LinkTypeRepositoryInterface * Store link between two journals. * * @param array $information - * @param TransactionJournal $left - * @param TransactionJournal $right + * @param TransactionJournal $inward + * @param TransactionJournal $outward * * @return mixed */ - public function storeLink(array $information, TransactionJournal $left, TransactionJournal $right): TransactionJournalLink; + public function storeLink(array $information, TransactionJournal $inward, TransactionJournal $outward): TransactionJournalLink; /** * @param TransactionJournalLink $link @@ -117,4 +145,14 @@ interface LinkTypeRepositoryInterface * @return LinkType */ public function update(LinkType $linkType, array $data): LinkType; + + /** + * Update an existing transaction journal link. + * + * @param TransactionJournalLink $journalLink + * @param array $data + * + * @return TransactionJournalLink + */ + public function updateLink(TransactionJournalLink $journalLink, array $data): TransactionJournalLink; } diff --git a/app/Transformers/JournalLinkTransformer.php b/app/Transformers/JournalLinkTransformer.php new file mode 100644 index 0000000000..944c9f9b20 --- /dev/null +++ b/app/Transformers/JournalLinkTransformer.php @@ -0,0 +1,146 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Models\Note; +use FireflyIII\Models\TransactionJournalLink; +use Illuminate\Support\Collection; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class JournalLinkTransformer + */ +class JournalLinkTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = ['inward', 'outward', 'link_type']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = ['inward', 'outward', 'link_type']; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param TransactionJournalLink $link + * + * @return Item + */ + public function includeInward(TransactionJournalLink $link): Item + { + // need to use the collector to get the transaction :( + // journals always use collector and limited using URL parameters. + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($link->source->user); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setJournals(new Collection([$link->source])); + $transactions = $collector->getJournals(); + + return $this->item($transactions->first(), new TransactionTransformer($this->parameters), 'transactions'); + } + + /** + * @param TransactionJournalLink $link + * + * @return Item + */ + public function includeLinkType(TransactionJournalLink $link): Item + { + return $this->item($link->linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + } + + /** + * @param TransactionJournalLink $link + * + * @return Item + */ + public function includeOutward(TransactionJournalLink $link): Item + { + // need to use the collector to get the transaction :( + // journals always use collector and limited using URL parameters. + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($link->source->user); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setJournals(new Collection([$link->destination])); + $transactions = $collector->getJournals(); + + return $this->item($transactions->first(), new TransactionTransformer($this->parameters), 'transactions'); + } + + /** + * @param TransactionJournalLink $link + * + * @return array + */ + public function transform(TransactionJournalLink $link): array + { + $notes = ''; + /** @var Note $note */ + $note = $link->notes()->first(); + if (null !== $note) { + $notes = $note->text; + } + + $data = [ + 'id' => (int)$link->id, + 'updated_at' => $link->updated_at->toAtomString(), + 'created_at' => $link->created_at->toAtomString(), + 'notes' => $notes, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/journal_links/' . $link->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/app/Transformers/LinkTypeTransformer.php b/app/Transformers/LinkTypeTransformer.php new file mode 100644 index 0000000000..625d390c09 --- /dev/null +++ b/app/Transformers/LinkTypeTransformer.php @@ -0,0 +1,89 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\LinkType; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class LinkTypeTransformer extends TransformerAbstract +{ + + /** + * List of resources possible to include + * + * @var array + */ + protected $availableIncludes = []; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + + /** @var ParameterBag */ + protected $parameters; + + /** + * CurrencyTransformer constructor. + * + * @codeCoverageIgnore + * + * @param ParameterBag $parameters + */ + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the currency. + * + * @param LinkType $linkType + * + * @return array + */ + public function transform(LinkType $linkType): array + { + $data = [ + 'id' => (int)$linkType->id, + 'updated_at' => $linkType->updated_at->toAtomString(), + 'created_at' => $linkType->created_at->toAtomString(), + 'name' => $linkType->name, + 'inward' => $linkType->inward, + 'outward' => $linkType->outward, + 'editable' => (int)$linkType->editable, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/link_types/' . $linkType->id, + ], + ], + ]; + + return $data; + } +} \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 2fae93b868..ad47b99796 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'is (partially) refunded by', 'is (partially) paid for by_inward' => 'is (partially) paid for by', 'is (partially) reimbursed by_inward' => 'is (partially) reimbursed by', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'relates to', '(partially) refunds_outward' => '(partially) refunds', '(partially) pays for_outward' => '(partially) pays for', diff --git a/resources/views/admin/link/show.twig b/resources/views/admin/link/show.twig index 3d452df573..b9639b981c 100644 --- a/resources/views/admin/link/show.twig +++ b/resources/views/admin/link/show.twig @@ -15,10 +15,10 @@   - {{ trans('firefly.source_transaction') }} + {{ trans('firefly.inward_transaction') }}   {{ trans('firefly.link_description') }} - {{ trans('firefly.destination_transaction') }} + {{ trans('firefly.outward_transaction') }}   diff --git a/routes/api.php b/routes/api.php index fb49221135..bdf9a3637e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -143,6 +143,19 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'journal_links', 'as' => 'api.v1.journal_links.'], + function () { + + // Journal Link API routes: + Route::get('', ['uses' => 'JournalLinkController@index', 'as' => 'index']); + Route::post('', ['uses' => 'JournalLinkController@store', 'as' => 'store']); + Route::get('{journalLink}', ['uses' => 'JournalLinkController@show', 'as' => 'show']); + Route::put('{journalLink}', ['uses' => 'JournalLinkController@update', 'as' => 'update']); + Route::delete('{journalLink}', ['uses' => 'JournalLinkController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () { From 234e3f4ca5950e1a6021131e68debd2784b7fbc9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 22:14:50 +0200 Subject: [PATCH 080/134] Implement link type controller. --- app/Api/V1/Controllers/LinkTypeController.php | 197 ++++++++++++++++++ app/Http/Controllers/Admin/LinkController.php | 2 +- app/Models/LinkType.php | 4 +- .../LinkType/LinkTypeRepository.php | 14 +- .../LinkType/LinkTypeRepositoryInterface.php | 9 +- routes/api.php | 26 +++ 6 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 app/Api/V1/Controllers/LinkTypeController.php diff --git a/app/Api/V1/Controllers/LinkTypeController.php b/app/Api/V1/Controllers/LinkTypeController.php new file mode 100644 index 0000000000..31215cd5a3 --- /dev/null +++ b/app/Api/V1/Controllers/LinkTypeController.php @@ -0,0 +1,197 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Api\V1\Requests\LinkTypeRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\LinkType; +use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Transformers\LinkTypeTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; + +/** + * + * Class LinkTypeController + */ +class LinkTypeController extends Controller +{ + /** @var LinkTypeRepositoryInterface */ + private $repository; + + /** @var UserRepositoryInterface */ + private $userRepository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + $this->repository = app(LinkTypeRepositoryInterface::class); + $this->userRepository = app(UserRepositoryInterface::class); + $this->repository->setUser($user); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param LinkType $linkType + * + * @return JsonResponse + * @throws FireflyException + */ + public function delete(LinkType $linkType): JsonResponse + { + if ($linkType->editable === false) { + throw new FireflyException(sprintf('You cannot delete this link type (#%d, "%s")', $linkType->id, $linkType->name)); + } + $this->repository->destroy($linkType, null); + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of accounts. Count it and split it. + $collection = $this->repository->get(); + $count = $collection->count(); + $linkTypes = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($linkTypes, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.link_types.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($linkTypes, new LinkTypeTransformer($this->parameters), 'link_types'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param LinkType $linkType + * + * @return JsonResponse + */ + public function show(Request $request, LinkType $linkType): JsonResponse + { + $manager = new Manager; + + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Store new object. + * + * @param LinkTypeRequest $request + * + * @return JsonResponse + * @throws FireflyException + */ + public function store(LinkTypeRequest $request): JsonResponse + { + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + throw new FireflyException('You need the "owner"-role to do this.'); + } + $data = $request->getAll(); + // if currency ID is 0, find the currency by the code: + $linkType = $this->repository->store($data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param LinkTypeRequest $request + * @param LinkType $linkType + * + * @return JsonResponse + * @throws FireflyException + */ + public function update(LinkTypeRequest $request, LinkType $linkType): JsonResponse + { + if ($linkType->editable === false) { + throw new FireflyException(sprintf('You cannot edit this link type (#%d, "%s")', $linkType->id, $linkType->name)); + } + if (!$this->userRepository->hasRole(auth()->user(), 'owner')) { + throw new FireflyException('You need the "owner"-role to do this.'); + } + + $data = $request->getAll(); + $this->repository->update($linkType, $data); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($linkType, new LinkTypeTransformer($this->parameters), 'link_types'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/LinkController.php b/app/Http/Controllers/Admin/LinkController.php index be3be8fdb4..5884596fa0 100644 --- a/app/Http/Controllers/Admin/LinkController.php +++ b/app/Http/Controllers/Admin/LinkController.php @@ -112,7 +112,7 @@ class LinkController extends Controller public function destroy(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType) { $name = $linkType->name; - $moveTo = $repository->find((int)$request->get('move_link_type_before_delete')); + $moveTo = $repository->findNull((int)$request->get('move_link_type_before_delete')); $repository->destroy($linkType, $moveTo); $request->session()->flash('success', (string)trans('firefly.deleted_link_type', ['name' => $name])); diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index a39fc05041..b9caaf05b7 100644 --- a/app/Models/LinkType.php +++ b/app/Models/LinkType.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -34,12 +35,13 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool $editable * @property Carbon $created_at * @property Carbon $updated_at - * @property int $id + * @property int $id * Class LinkType * */ class LinkType extends Model { + use SoftDeletes; /** * The attributes that should be casted to native types. * diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index 76fa74d748..a7c79b891a 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -57,9 +57,9 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface * @return bool * @throws \Exception */ - public function destroy(LinkType $linkType, LinkType $moveTo): bool + public function destroy(LinkType $linkType, LinkType $moveTo = null): bool { - if (null !== $moveTo->id) { + if (null !== $moveTo) { TransactionJournalLink::where('link_type_id', $linkType->id)->update(['link_type_id' => $moveTo->id]); } $linkType->delete(); @@ -125,6 +125,16 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface return $count + $opposingCount > 0; } + /** + * @param int $id + * + * @return LinkType|null + */ + public function findNull(int $id): ?LinkType + { + return LinkType::find($id); + } + /** * See if such a link already exists (and get it). * diff --git a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php index a0c8d2c636..9aa8c03757 100644 --- a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php +++ b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php @@ -45,7 +45,7 @@ interface LinkTypeRepositoryInterface * * @return bool */ - public function destroy(LinkType $linkType, LinkType $moveTo): bool; + public function destroy(LinkType $linkType, LinkType $moveTo = null): bool; /** * @param TransactionJournalLink $link @@ -62,6 +62,13 @@ interface LinkTypeRepositoryInterface */ public function find(int $id): LinkType; + /** + * @param int $id + * + * @return LinkType|null + */ + public function findNull(int $id): ?LinkType; + /** * Find link type by name. * diff --git a/routes/api.php b/routes/api.php index bdf9a3637e..ba748dbc0e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -156,6 +156,32 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'link_types', 'as' => 'api.v1.link_types.'], + function () { + + // Link Type API routes: + Route::get('', ['uses' => 'LinkTypeController@index', 'as' => 'index']); + Route::post('', ['uses' => 'LinkTypeController@store', 'as' => 'store']); + Route::get('{linkType}', ['uses' => 'LinkTypeController@show', 'as' => 'show']); + Route::put('{linkType}', ['uses' => 'LinkTypeController@update', 'as' => 'update']); + Route::delete('{linkType}', ['uses' => 'LinkTypeController@delete', 'as' => 'delete']); + } +); + +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'piggy_banks', 'as' => 'api.v1.piggy_banks.'], + function () { + + // Piggy Bank API routes: + Route::get('', ['uses' => 'PiggyBankController@index', 'as' => 'index']); + Route::post('', ['uses' => 'PiggyBankController@store', 'as' => 'store']); + Route::get('{piggyBank}', ['uses' => 'PiggyBankController@show', 'as' => 'show']); + Route::put('{piggyBank}', ['uses' => 'PiggyBankController@update', 'as' => 'update']); + Route::delete('{piggyBank}', ['uses' => 'PiggyBankController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () { From c8de1d3372b9c9125b70deed9fa3925488fc1169 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 28 Jun 2018 22:15:22 +0200 Subject: [PATCH 081/134] New (empty) API controllers. --- .../V1/Controllers/PiggyBankController.php | 145 ++++++++++++++++++ .../V1/Controllers/PreferenceController.php | 112 ++++++++++++++ .../V1/Controllers/RecurrenceController.php | 112 ++++++++++++++ app/Api/V1/Controllers/RuleController.php | 112 ++++++++++++++ .../V1/Controllers/RuleGroupController.php | 112 ++++++++++++++ app/Api/V1/Controllers/TagController.php | 112 ++++++++++++++ app/Api/V1/Requests/LinkTypeRequest.php | 86 +++++++++++ 7 files changed, 791 insertions(+) create mode 100644 app/Api/V1/Controllers/PiggyBankController.php create mode 100644 app/Api/V1/Controllers/PreferenceController.php create mode 100644 app/Api/V1/Controllers/RecurrenceController.php create mode 100644 app/Api/V1/Controllers/RuleController.php create mode 100644 app/Api/V1/Controllers/RuleGroupController.php create mode 100644 app/Api/V1/Controllers/TagController.php create mode 100644 app/Api/V1/Requests/LinkTypeRequest.php diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php new file mode 100644 index 0000000000..01eb9608dc --- /dev/null +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -0,0 +1,145 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Transformers\BudgetTransformer; +use FireflyIII\Transformers\PiggyBankTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Serializer\JsonApiSerializer; + +class PiggyBankController extends Controller +{ + + /** @var PiggyBankRepositoryInterface */ + private $repository; + + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + $this->repository = app(PiggyBankRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getPiggyBanks(); + $count = $collection->count(); + $piggyBanks= $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.piggy_banks.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($piggyBanks, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * List single resource. + * + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function show(Request $request, string $object): JsonResponse + { + // todo implement me. + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + // todo replace code and replace request object. + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(Request $request, string $object): JsonResponse + { + // todo replace code and replace request object. + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php new file mode 100644 index 0000000000..01b4ae40e4 --- /dev/null +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -0,0 +1,112 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + + +class PreferenceController extends Controller +{ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + // todo add local repositories. + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // todo implement. + + } + + /** + * List single resource. + * + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function show(Request $request, string $object): JsonResponse + { + // todo implement me. + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + // todo replace code and replace request object. + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(Request $request, string $object): JsonResponse + { + // todo replace code and replace request object. + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php new file mode 100644 index 0000000000..519ffd65f8 --- /dev/null +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -0,0 +1,112 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + + +class RecurrenceController +{ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + // todo add local repositories. + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // todo implement. + + } + + /** + * List single resource. + * + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function show(Request $request, string $object): JsonResponse + { + // todo implement me. + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + // todo replace code and replace request object. + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(Request $request, string $object): JsonResponse + { + // todo replace code and replace request object. + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/RuleController.php b/app/Api/V1/Controllers/RuleController.php new file mode 100644 index 0000000000..e3d6fedb78 --- /dev/null +++ b/app/Api/V1/Controllers/RuleController.php @@ -0,0 +1,112 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + + +class RuleController extends Controller +{ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + // todo add local repositories. + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // todo implement. + + } + + /** + * List single resource. + * + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function show(Request $request, string $object): JsonResponse + { + // todo implement me. + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + // todo replace code and replace request object. + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(Request $request, string $object): JsonResponse + { + // todo replace code and replace request object. + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php new file mode 100644 index 0000000000..3c1cec83e4 --- /dev/null +++ b/app/Api/V1/Controllers/RuleGroupController.php @@ -0,0 +1,112 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + + +class RuleGroupController extends Controller +{ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + // todo add local repositories. + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // todo implement. + + } + + /** + * List single resource. + * + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function show(Request $request, string $object): JsonResponse + { + // todo implement me. + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + // todo replace code and replace request object. + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(Request $request, string $object): JsonResponse + { + // todo replace code and replace request object. + + } +} \ No newline at end of file diff --git a/app/Api/V1/Controllers/TagController.php b/app/Api/V1/Controllers/TagController.php new file mode 100644 index 0000000000..8aa1b6acb0 --- /dev/null +++ b/app/Api/V1/Controllers/TagController.php @@ -0,0 +1,112 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Controllers; + +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + + +class TagController extends Controller +{ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + /** @var User $user */ + $user = auth()->user(); + + // todo add local repositories. + return $next($request); + } + ); + } + + /** + * Delete the resource. + * + * @param string $object + * + * @return JsonResponse + */ + public function delete(string $object): JsonResponse + { + // todo delete object. + + return response()->json([], 204); + } + + /** + * List all of them. + * + * @param Request $request + * + * @return JsonResponse] + */ + public function index(Request $request): JsonResponse + { + // todo implement. + + } + + /** + * List single resource. + * + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function show(Request $request, string $object): JsonResponse + { + // todo implement me. + + } + + /** + * Store new object. + * + * @param Request $request + * + * @return JsonResponse + */ + public function store(Request $request): JsonResponse + { + // todo replace code and replace request object. + + } + + /** + * @param Request $request + * @param string $object + * + * @return JsonResponse + */ + public function update(Request $request, string $object): JsonResponse + { + // todo replace code and replace request object. + + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/LinkTypeRequest.php b/app/Api/V1/Requests/LinkTypeRequest.php new file mode 100644 index 0000000000..c44dbcb145 --- /dev/null +++ b/app/Api/V1/Requests/LinkTypeRequest.php @@ -0,0 +1,86 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\LinkType; +use Illuminate\Validation\Rule; + +/** + * + * Class LinkTypeRequest + */ +class LinkTypeRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'outward' => $this->string('outward'), + 'inward' => $this->string('inward'), + ]; + + + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|unique:link_types,name|min:1', + 'outward' => 'required|unique:link_types,outward|min:1|different:inward', + 'inward' => 'required|unique:link_types,inward|min:1|different:outward', + ]; + // Rule::unique('users')->ignore($user->id), + + + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var LinkType $linkType */ + $linkType = $this->route()->parameter('linkType'); + $rules['name'] = ['required', Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1']; + $rules['outward'] = ['required', 'different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1']; + $rules['inward'] = ['required', 'different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1']; + break; + } + + return $rules; + } +} \ No newline at end of file From 12a84572e20da93ba9d236bafc4b334f65c22054 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Jun 2018 06:43:44 +0200 Subject: [PATCH 082/134] First code for the piggy bank API. --- .../V1/Controllers/PiggyBankController.php | 52 ++++++++--- app/Api/V1/Requests/PiggyBankRequest.php | 90 +++++++++++++++++++ app/Models/PiggyBank.php | 15 ++-- app/Models/PiggyBankRepetition.php | 2 + app/Providers/EventServiceProvider.php | 2 +- .../PiggyBank/PiggyBankRepository.php | 29 ++++-- app/Rules/IsAssetAccountId.php | 46 ++++++++++ app/Transformers/PiggyBankTransformer.php | 6 +- 8 files changed, 214 insertions(+), 28 deletions(-) create mode 100644 app/Api/V1/Requests/PiggyBankRequest.php create mode 100644 app/Rules/IsAssetAccountId.php diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php index 01eb9608dc..080aa676f2 100644 --- a/app/Api/V1/Controllers/PiggyBankController.php +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -23,8 +23,10 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use FireflyIII\Api\V1\Requests\PiggyBankRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\PiggyBank; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; -use FireflyIII\Transformers\BudgetTransformer; use FireflyIII\Transformers\PiggyBankTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -33,8 +35,13 @@ use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; +/** + * TODO order up and down. + * Class PiggyBankController + */ class PiggyBankController extends Controller { @@ -59,13 +66,13 @@ class PiggyBankController extends Controller /** * Delete the resource. * - * @param string $object + * @param PiggyBank $piggyBank * * @return JsonResponse */ - public function delete(string $object): JsonResponse + public function delete(PiggyBank $piggyBank): JsonResponse { - // todo delete object. + $this->repository->destroy($piggyBank); return response()->json([], 204); } @@ -89,7 +96,7 @@ class PiggyBankController extends Controller // get list of budgets. Count it and split it. $collection = $this->repository->getPiggyBanks(); $count = $collection->count(); - $piggyBanks= $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + $piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); // make paginator: $paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page')); @@ -107,27 +114,48 @@ class PiggyBankController extends Controller /** * List single resource. * - * @param Request $request - * @param string $object + * @param Request $request + * @param PiggyBank $piggyBank * * @return JsonResponse */ - public function show(Request $request, string $object): JsonResponse + public function show(Request $request, PiggyBank $piggyBank): JsonResponse { - // todo implement me. + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($piggyBank, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** * Store new object. * - * @param Request $request + * @param PiggyBankRequest $request * * @return JsonResponse + * @throws FireflyException */ - public function store(Request $request): JsonResponse + public function store(PiggyBankRequest $request): JsonResponse { - // todo replace code and replace request object. + $piggyBank = $this->repository->store($request->getAll()); + if (null !== $piggyBank) { + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($piggyBank, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + throw new FireflyException('Could not store new piggy bank.'); // @codeCoverageIgnore } diff --git a/app/Api/V1/Requests/PiggyBankRequest.php b/app/Api/V1/Requests/PiggyBankRequest.php new file mode 100644 index 0000000000..15a41bda01 --- /dev/null +++ b/app/Api/V1/Requests/PiggyBankRequest.php @@ -0,0 +1,90 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\PiggyBank; +use FireflyIII\Rules\IsAssetAccountId; + +/** + * + * Class PiggyBankRequest + */ +class PiggyBankRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + return [ + 'name' => $this->string('name'), + 'account_id' => $this->integer('account_id'), + 'targetamount' => $this->string('target_amount'), + 'current_amount' => $this->string('current_amount'), + 'start_date' => $this->date('start_date'), + 'target_date' => $this->date('target_date'), + 'note' => $this->string('notes'), + ]; + } + + /** + * @return array + */ + public function rules(): array + { + $rules = [ + 'name' => 'required|between:1,255|uniquePiggyBankForUser', + 'account_id' => ['required', 'belongsToUser:accounts', new IsAssetAccountId], + 'target_amount' => 'required|numeric|more:0', + 'current_amount' => 'numeric|more:0|lte:target_amount', + 'start_date' => 'date|nullable', + 'target_date' => 'date|nullable', + 'notes' => 'max:65000', + ]; + + switch ($this->method()) { + default: + break; + case 'PUT': + case 'PATCH': + /** @var PiggyBank $piggyBank */ + $piggyBank = $this->route()->parameter('piggyBank'); + $rules['name'] = 'required|between:1,255|uniquePiggyBankForUser:' . $piggyBank->id; + break; + } + + + return $rules; + } + +} \ No newline at end of file diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 82721a243a..e6cc263d3e 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -33,12 +33,17 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class PiggyBank. * - * @property Carbon $targetdate - * @property Carbon $startdate - * @property string $targetamount - * @property int $id - * @property string $name + * @property Carbon $targetdate + * @property Carbon $startdate + * @property string $targetamount + * @property int $id + * @property string $name * @property Account $account + * @property Carbon $updated_at + * @property Carbon $created_at + * @property int $order + * @property bool $active + * @property int $account_id * */ class PiggyBank extends Model diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 42494f1f9e..470d9b7a11 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -30,6 +30,8 @@ use Illuminate\Database\Eloquent\Model; * Class PiggyBankRepetition. * * @property string $currentamount + * @property Carbon $startdate + * @property Carbon $targetdate */ class PiggyBankRepetition extends Model { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index ec5891f6f3..2ffb23d88b 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -117,7 +117,7 @@ class EventServiceProvider extends ServiceProvider */ protected function registerCreateEvents(): void { - // move this routine to a filter + // todo move this routine to a filter // in case of repeated piggy banks and/or other problems. PiggyBank::created( function (PiggyBank $piggyBank) { diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 99ef2b7f28..f2043a11a6 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -49,7 +49,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function addAmount(PiggyBank $piggyBank, string $amount): bool { - $repetition = $piggyBank->currentRelevantRep(); + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { + return false; + } $currentAmount = $repetition->currentamount ?? '0'; $repetition->currentamount = bcadd($currentAmount, $amount); $repetition->save(); @@ -99,7 +102,11 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function canRemoveAmount(PiggyBank $piggyBank, string $amount): bool { - $savedSoFar = $piggyBank->currentRelevantRep()->currentamount; + $repetition = $this->getRepetition($piggyBank); + if (null === $repetition) { + return false; + } + $savedSoFar = $repetition->currentamount; return bccomp($amount, $savedSoFar) <= 0; } @@ -171,6 +178,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param int $piggyBankid * + * @deprecated * @return PiggyBank */ public function find(int $piggyBankid): PiggyBank @@ -268,7 +276,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); // if piggy account matches source account, the amount is positive - if (\in_array($piggyBank->account_id, $sources)) { + if (\in_array($piggyBank->account_id, $sources, true)) { $amount = bcmul($amount, '-1'); Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); } @@ -438,8 +446,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * set id of piggy bank. * - * @param int $piggyBankId - * @param int $order + * @param PiggyBank $piggyBank + * @param int $order * * @return bool */ @@ -454,7 +462,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param User $user */ - public function setUser(User $user) + public function setUser(User $user): void { $this->user = $user; } @@ -472,6 +480,13 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface $this->updateNote($piggyBank, $data['note']); + // repetition is auto created. + $repetition = $this->getRepetition($piggyBank); + if (null !== $repetition && isset($data['current_amount'])) { + $repetition->currentamount = $data['current_amount']; + $repetition->save(); + } + return $piggyBank; } @@ -495,7 +510,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface // if the piggy bank is now smaller than the current relevant rep, // remove money from the rep. - $repetition = $piggyBank->currentRelevantRep(); + $repetition = $this->getRepetition($piggyBank); if ($repetition->currentamount > $piggyBank->targetamount) { $diff = bcsub($piggyBank->targetamount, $repetition->currentamount); $this->createEvent($piggyBank, $diff); diff --git a/app/Rules/IsAssetAccountId.php b/app/Rules/IsAssetAccountId.php new file mode 100644 index 0000000000..7ffa717037 --- /dev/null +++ b/app/Rules/IsAssetAccountId.php @@ -0,0 +1,46 @@ +find($accountId); + if (null === $account) { + return false; + } + if ($account->accountType->type !== AccountType::ASSET && $account->accountType->type !== AccountType::DEFAULT) { + return false; + } + + return true; + } +} diff --git a/app/Transformers/PiggyBankTransformer.php b/app/Transformers/PiggyBankTransformer.php index 5afe13a6ca..0816fb0f63 100644 --- a/app/Transformers/PiggyBankTransformer.php +++ b/app/Transformers/PiggyBankTransformer.php @@ -166,9 +166,9 @@ class PiggyBankTransformer extends TransformerAbstract 'percentage' => $percentage, 'current_amount' => $currentAmount, 'left_to_save' => round($leftToSave, $decimalPlaces), - 'save_per_month' => $piggyRepos->getSuggestedMonthlyAmount($piggyBank), - 'startdate' => $startDate, - 'targetdate' => $targetDate, + 'save_per_month' => round($piggyRepos->getSuggestedMonthlyAmount($piggyBank), $decimalPlaces), + 'start_date' => $startDate, + 'target_date' => $targetDate, 'order' => (int)$piggyBank->order, 'active' => (int)$piggyBank->active === 1, 'notes' => null, From f048e943f8523fea871b10a1a41d4ee43d19c508 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Jun 2018 08:06:17 +0200 Subject: [PATCH 083/134] Implement piggy bank API. --- app/Api/V1/Controllers/PiggyBankController.php | 16 ++++++++++++---- app/Api/V1/Requests/PiggyBankRequest.php | 4 ++-- .../PiggyBank/PiggyBankRepository.php | 8 ++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php index 080aa676f2..a79f0da7ae 100644 --- a/app/Api/V1/Controllers/PiggyBankController.php +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -160,14 +160,22 @@ class PiggyBankController extends Controller } /** - * @param Request $request - * @param string $object + * @param PiggyBankRequest $request + * @param PiggyBank $piggyBank * * @return JsonResponse */ - public function update(Request $request, string $object): JsonResponse + public function update(PiggyBankRequest $request, PiggyBank $piggyBank): JsonResponse { - // todo replace code and replace request object. + $data = $request->getAll(); + $budget = $this->repository->update($piggyBank, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($budget, new PiggyBankTransformer($this->parameters), 'piggy_banks'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } } \ No newline at end of file diff --git a/app/Api/V1/Requests/PiggyBankRequest.php b/app/Api/V1/Requests/PiggyBankRequest.php index 15a41bda01..0e87661073 100644 --- a/app/Api/V1/Requests/PiggyBankRequest.php +++ b/app/Api/V1/Requests/PiggyBankRequest.php @@ -78,8 +78,8 @@ class PiggyBankRequest extends Request case 'PUT': case 'PATCH': /** @var PiggyBank $piggyBank */ - $piggyBank = $this->route()->parameter('piggyBank'); - $rules['name'] = 'required|between:1,255|uniquePiggyBankForUser:' . $piggyBank->id; + $piggyBank = $this->route()->parameter('piggyBank'); + $rules['name'] = 'required|between:1,255|uniquePiggyBankForUser:' . $piggyBank->id; break; } diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index f2043a11a6..cacf70be61 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -478,7 +478,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** @var PiggyBank $piggyBank */ $piggyBank = PiggyBank::create($data); - $this->updateNote($piggyBank, $data['note']); + $this->updateNote($piggyBank, $data['note']); // todo rename to 'notes' // repetition is auto created. $repetition = $this->getRepetition($piggyBank); @@ -500,9 +500,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface { $piggyBank->name = $data['name']; $piggyBank->account_id = (int)$data['account_id']; - $piggyBank->targetamount = round($data['targetamount'], 2); - $piggyBank->targetdate = $data['targetdate']; - $piggyBank->startdate = $data['startdate']; + $piggyBank->targetamount = $data['targetamount']; + $piggyBank->targetdate = $data['targetdate'] ?? $piggyBank->targetdate; + $piggyBank->startdate = $data['startdate'] ?? $piggyBank->startdate; $piggyBank->save(); From 8c28c4b5ac9345551ddc1cbfed63aea11444d15b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Jun 2018 12:11:44 +0200 Subject: [PATCH 084/134] New translations and routes. --- .../V1/Controllers/PreferenceController.php | 53 ++++++++++++-- app/Models/Preference.php | 24 +++++++ app/Support/Preferences.php | 6 +- app/Transformers/PreferenceTransformer.php | 72 +++++++++++++++++++ config/firefly.php | 1 + resources/lang/de_DE/firefly.php | 26 +++---- resources/lang/de_DE/form.php | 16 ++--- resources/lang/de_DE/import.php | 8 +-- resources/lang/de_DE/validation.php | 4 +- resources/lang/es_ES/firefly.php | 2 + resources/lang/fr_FR/firefly.php | 2 + resources/lang/id_ID/firefly.php | 2 + resources/lang/it_IT/firefly.php | 26 +++---- resources/lang/it_IT/form.php | 16 ++--- resources/lang/it_IT/import.php | 8 +-- resources/lang/it_IT/validation.php | 4 +- resources/lang/nl_NL/firefly.php | 2 + resources/lang/pl_PL/firefly.php | 2 + resources/lang/pt_BR/firefly.php | 2 + resources/lang/ru_RU/firefly.php | 2 + resources/lang/tr_TR/firefly.php | 2 + routes/api.php | 14 ++++ 22 files changed, 234 insertions(+), 60 deletions(-) create mode 100644 app/Transformers/PreferenceTransformer.php diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php index 01b4ae40e4..b7b7e8bee4 100644 --- a/app/Api/V1/Controllers/PreferenceController.php +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -23,11 +23,22 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use FireflyIII\Models\Preference; +use FireflyIII\Transformers\PreferenceTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Collection; +use League\Fractal\Manager; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; +use League\Fractal\Serializer\JsonApiSerializer; +use Preferences; - +/** + * + * Class PreferenceController + */ class PreferenceController extends Controller { public function __construct() @@ -67,7 +78,31 @@ class PreferenceController extends Controller */ public function index(Request $request): JsonResponse { - // todo implement. + /** @var User $user */ + $user = auth()->user(); + $available = [ + 'language', 'customFiscalYear', 'fiscalYearStart', 'currencyPreference', + 'transaction_journal_optional_fields', 'frontPageAccounts', 'viewRange', + 'listPageSize, twoFactorAuthEnabled', + ]; + $preferences = new Collection; + foreach ($available as $name) { + $pref = Preferences::getForUser($user, $name); + if (null !== $pref) { + $preferences->push($pref); + } + } + + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($preferences, new PreferenceTransformer($this->parameters), 'preferences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } @@ -75,13 +110,21 @@ class PreferenceController extends Controller * List single resource. * * @param Request $request - * @param string $object + * @param Preference $preference * * @return JsonResponse */ - public function show(Request $request, string $object): JsonResponse + public function show(Request $request, Preference $preference): JsonResponse { - // todo implement me. + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($preference, new PreferenceTransformer($this->parameters), 'preferences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 7182c9728e..2aae7b4a6e 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Carbon\Carbon; use Crypt; use Exception; use FireflyIII\Exceptions\FireflyException; @@ -29,12 +30,16 @@ use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Model; use Log; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Preference. * * @property mixed $data * @property string $name + * @property Carbon $updated_at + * @property Carbon $created_at + * @property int $id */ class Preference extends Model { @@ -52,6 +57,25 @@ class Preference extends Model /** @var array */ protected $fillable = ['user_id', 'data', 'name']; + /** + * @param string $value + * + * @return Account + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value): Preference + { + if (auth()->check()) { + $preferenceId = (int)$value; + $preference = auth()->user()->preferences()->find($preferenceId); + if (null !== $preference) { + return $preference; + } + } + throw new NotFoundHttpException; + } + + /** * @param $value * diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 9dc524af8b..8774c29d6e 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -125,14 +125,14 @@ class Preferences * * @return \FireflyIII\Models\Preference|null */ - public function getForUser(User $user, $name, $default = null) + public function getForUser(User $user, $name, $default = null): ?Preference { $fullName = sprintf('preference%s%s', $user->id, $name); if (Cache::has($fullName)) { return Cache::get($fullName); } - $preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data']); + $preference = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $preference && null === $preference->data) { try { $preference->delete(); @@ -219,7 +219,7 @@ class Preferences { $fullName = sprintf('preference%s%s', $user->id, $name); Cache::forget($fullName); - $pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data']); + $pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $pref) { $pref->data = $value; diff --git a/app/Transformers/PreferenceTransformer.php b/app/Transformers/PreferenceTransformer.php new file mode 100644 index 0000000000..76a9d253cb --- /dev/null +++ b/app/Transformers/PreferenceTransformer.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use FireflyIII\Models\Preference; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +class PreferenceTransformer extends TransformerAbstract +{ + /** + * List of resources possible to include. + * + * @var array + */ + protected $availableIncludes = ['user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + /** @var ParameterBag */ + protected $parameters; + + public function __construct(ParameterBag $parameters) + { + $this->parameters = $parameters; + } + + /** + * Transform the preference + * + * @param Preference $preference + * + * @return array + */ + public function transform(Preference $preference): array + { + return [ + 'id' => (int)$preference->id, + 'updated_at' => $preference->updated_at->toAtomString(), + 'created_at' => $preference->created_at->toAtomString(), + 'name' => $preference->name, + 'data' => $preference->data, + ]; + + } + +} \ No newline at end of file diff --git a/config/firefly.php b/config/firefly.php index 0878d882da..8d18fe1024 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -270,6 +270,7 @@ return [ 'journalLink' => \FireflyIII\Models\TransactionJournalLink::class, 'currency' => \FireflyIII\Models\TransactionCurrency::class, 'piggyBank' => \FireflyIII\Models\PiggyBank::class, + 'preference' => \FireflyIII\Models\Preference::class, 'tj' => \FireflyIII\Models\TransactionJournal::class, 'tag' => \FireflyIII\Models\Tag::class, 'recurrence' => \FireflyIII\Models\Recurrence::class, diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 7f1bec46e4..d3b3d80507 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -465,7 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Scannen Sie den QR-Code mit einer Anwendung wie Authy oder Google Authenticator auf ihrem Handy und geben Sie den generierten Code ein.', 'pref_two_factor_auth_reset_code' => 'Verifizierungscode zurücksetzen', 'pref_two_factor_auth_disable_2fa' => '2FA deaktivieren', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_use_secret_instead' => 'Wenn Sie den QR-Code nicht scannen können, verwenden Sie stattdessen das Geheimnis: :secret.', 'pref_save_settings' => 'Einstellungen speichern', 'saved_preferences' => 'Einstellungen gespeichert!', 'preferences_general' => 'Allgemein', @@ -1135,6 +1135,8 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'is (partially) refunded by_inward' => 'wird (teilweise) erstattet durch', 'is (partially) paid for by_inward' => 'wird (teilweise) bezahlt von', 'is (partially) reimbursed by_inward' => 'wird (teilweise) erstattet durch', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'bezieht sich auf', '(partially) refunds_outward' => '(Teil-)Erstattungen', '(partially) pays for_outward' => '(teilweise) bezahlt für', @@ -1261,15 +1263,15 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'update_recurrence' => 'Dauerauftrag aktualisieren', 'updated_recurrence' => 'Dauerauftrag ":title" aktualisiert', 'recurrence_is_inactive' => 'Dieser Dauerauftrag ist nicht aktiv und erzeugt keine neuen Buchungen.', - 'delete_recurring' => 'Delete recurring transaction ":title"', - 'new_recurring_transaction' => 'New recurring transaction', - 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', - 'do_nothing' => 'Just create the transaction', - 'skip_transaction' => 'Skip the occurence', - 'jump_to_friday' => 'Create the transaction on the previous Friday instead', - 'jump_to_monday' => 'Create the transaction on the next Monday instead', - 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', - 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', - 'except_weekends' => 'Except weekends', - 'recurrence_deleted' => 'Recurring transaction ":title" deleted', + 'delete_recurring' => 'Dauerauftrag „:title” löschen', + 'new_recurring_transaction' => 'Neue Dauerauftrag', + 'help_weekend' => 'Was sollte Firefly III tun, wenn der Dauerauftrag auf einen Samstag oder Sonntag fällt?', + 'do_nothing' => 'Einfach die Buchung anlegen', + 'skip_transaction' => 'Vorkommen überspringen', + 'jump_to_friday' => 'Die Buchung stattdessen am vorhergehenden Freitag ausführen', + 'jump_to_monday' => 'Die Buchung stattdessen am darauffolgenden Montag ausführen', + 'will_jump_friday' => 'Wird am Freitag statt am Wochenende ausgeführt.', + 'will_jump_monday' => 'Wird am Montag statt am Wochenende ausgeführt.', + 'except_weekends' => 'Außer an Wochenenden', + 'recurrence_deleted' => 'Dauerauftrag „:title” gelöscht', ]; diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index b7e269b13e..cc05840685 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -150,7 +150,7 @@ return [ 'delete_rule_group' => 'Lösche Regelgruppe ":title"', 'delete_link_type' => 'Verknüpfungstyp „:name” löschen', 'delete_user' => 'Benutzer ":email" löschen', - 'delete_recurring' => 'Delete recurring transaction ":title"', + 'delete_recurring' => 'Dauerauftrag „:title” löschen', 'user_areYouSure' => 'Wenn Sie den Benutzer ":email" löschen, ist alles weg. Es gibt keine Sicherung, Wiederherstellung oder ähnliches. Wenn Sie sich selbst löschen, verlieren Sie den Zugriff auf diese Instanz von Firefly III.', 'attachment_areYouSure' => 'Möchten Sie den Anhang „:name” wirklich löschen?', 'account_areYouSure' => 'Möchten Sie das Konto „:name” wirklich löschen?', @@ -159,7 +159,7 @@ return [ 'ruleGroup_areYouSure' => 'Sind Sie sicher, dass sie die Regelgruppe ":title" löschen möchten?', 'budget_areYouSure' => 'Möchten Sie den Kostenrahmen „:name” wirklich löschen?', 'category_areYouSure' => 'Möchten Sie die Kategorie „:name” wirklich löschen?', - 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'recurring_areYouSure' => 'Möchten Sie den Dauerauftrag „:title” wirklich löschen?', 'currency_areYouSure' => 'Möchten Sie die Währung „:name” wirklich löschen?', 'piggyBank_areYouSure' => 'Möchten Sie das Sparschwein „:name” wirklich löschen?', 'journal_areYouSure' => 'Sind Sie sicher, dass Sie die Überweisung mit dem Namen ":description" löschen möchten?', @@ -175,11 +175,11 @@ return [ 'also_delete_connections' => 'Die einzige Transaktion, die mit diesem Verknüpfungstyp verknüpft ist, verliert diese Verbindung. • Alle :count Buchungen, die mit diesem Verknüpfungstyp verknüpft sind, verlieren ihre Verbindung.', 'also_delete_rules' => 'Die einzige Regel, die mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Regeln, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', 'also_delete_piggyBanks' => 'Das einzige Sparschwein, das mit diesem Konto verknüpft ist, wird ebenfalls gelöscht. | Alle :count Sparschweine, die mit diesem Konto verknüpft sind, werden ebenfalls gelöscht.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', - 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'bill_keep_transactions' => 'Die einzige mit dieser Rechnung verbundene Buchung wird nicht gelöscht. | Alle :count Buchungen, die mit dieser Rechnung verbunden sind, werden nicht gelöscht.', + 'budget_keep_transactions' => 'Die einzige mit diesem Kostenrahmen verbundene Buchung wird nicht gelöscht. | Alle :count Buchungen, die mit diesem Kostenrahmen verbunden sind, werden nicht gelöscht.', + 'category_keep_transactions' => 'Die einzige Buchung, die mit dieser Kategorie verbunden ist, wird nicht gelöscht. | Alle :count Buchungen, die mit dieser Kategorie verbunden sind, werden nicht gelöscht.', + 'recurring_keep_transactions' => 'Die einzige Buchung, die durch diesen Dauerauftrag erstellt wurde, wird nicht gelöscht. | Alle :count Buchungen, die durch diesen Dauerauftrag erstellt wurden, werden nicht gelöscht.', + 'tag_keep_transactions' => 'Das einzige mit dieser Rechnung verbundene Schlagwort wird nicht gelöscht. | Alle :count Schlagwörter, die mit dieser Rechnung verbunden sind, werden nicht gelöscht.', 'check_for_updates' => 'Nach Updates suchen', 'email' => 'E-Mail Adresse', @@ -236,6 +236,6 @@ return [ 'repetition_end' => 'Wiederholung endet', 'repetitions' => 'Wiederholungen', 'calendar' => 'Kalender', - 'weekend' => 'Weekend', + 'weekend' => 'Wochenende', ]; diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index c4a3c14ceb..e19c46222c 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -127,16 +127,16 @@ return [ 'spectre_no_mapping' => 'Es scheint, dass Sie keine Konten zum Importieren ausgewählt haben.', 'imported_from_account' => 'Von „:account” importiert', 'spectre_account_with_number' => 'Konto :number', - 'job_config_spectre_apply_rules' => 'Apply rules', - 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'job_config_spectre_apply_rules' => 'Regeln übernehmen', + 'job_config_spectre_apply_rules_text' => 'Standardmäßig werden Ihre Regeln auf die Buchungen angewendet, die während dieser Importfunktion erstellt wurden. Wenn Sie dies nicht wünschen, entfernen Sie die Markierung dieses Kontrollkästchens.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'bunq-Konten', 'job_config_bunq_accounts_text' => 'Dies sind jene Konten, die mit Ihrem „bunq”-Konto verknüpft sind. Bitte wählen Sie die Konten aus, von denen Sie importieren möchten, und in welches Konto die Buchungen importiert werden sollen.', 'bunq_no_mapping' => 'Es scheint, dass Sie keine Konten ausgewählt haben.', 'should_download_config' => 'Sie sollten die Konfigurationsdatei für diesen Aufgabe herunterladen. Dies wird zukünftige Importe erheblich erleichtern.', 'share_config_file' => 'Wenn Sie Daten von einer öffentlichen Bank importiert haben, sollten Sie Ihre Konfigurationsdatei freigeben, damit es für andere Benutzer einfach ist, ihre Daten zu importieren. Wenn Sie Ihre Konfigurationsdatei freigeben, werden Ihre finanziellen Daten nicht preisgegeben.', - 'job_config_bunq_apply_rules' => 'Apply rules', - 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'job_config_bunq_apply_rules' => 'Regeln übernehmen', + 'job_config_bunq_apply_rules_text' => 'Standardmäßig werden Ihre Regeln auf die Buchungen angewendet, die während dieser Importfunktion erstellt wurden. Wenn Sie dies nicht wünschen, entfernen Sie die Markierung dieses Kontrollkästchens.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT Code', diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index c2d2d34059..0ed794bba3 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -44,8 +44,8 @@ return [ 'belongs_to_user' => 'Der Wert von :attribute ist nicht bekannt', 'accepted' => ':attribute muss akzeptiert werden.', 'bic' => 'Dies ist kein gültiger BIC.', - 'base64' => 'This is not valid base64 encoded data.', - 'model_id_invalid' => 'The given ID seems invalid for this model.', + 'base64' => 'Dies sind keine gültigen base64-kodierten Daten.', + 'model_id_invalid' => 'Die angegebene ID scheint für dieses Modell ungültig zu sein.', 'more' => ':attribute muss größer als Null sein.', 'active_url' => ':attribute ist keine gültige URL.', 'after' => ':attribute muss ein Datum nach :date sein.', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 20dc9fa099..4d83113a79 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -1135,6 +1135,8 @@ return [ 'is (partially) refunded by_inward' => 'es (parcialmente) es devuelto por', 'is (partially) paid for by_inward' => 'es(parcialmente) pagado por', 'is (partially) reimbursed by_inward' => 'es(parcialmente) reembolsado por', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'relacionado con', '(partially) refunds_outward' => '(parcialmente) reembolso', '(partially) pays for_outward' => '(parcialmente) paga por', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 7cd9b4d937..f082d0631f 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'est (partiellement) remboursé par', 'is (partially) paid for by_inward' => 'est (partiellement) payé par', 'is (partially) reimbursed by_inward' => 'est (partiellement) remboursé par', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'se rapporte à', '(partially) refunds_outward' => 'rembourse (partiellement)', '(partially) pays for_outward' => 'paye (partiellement) pour', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index a4b8c09d66..d5a358e8a8 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => '(sebagian) dikembalikan oleh', 'is (partially) paid for by_inward' => 'adalah (sebagian) dibayar oleh', 'is (partially) reimbursed by_inward' => '(sebagian) diganti oleh', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'berhubungan dengan', '(partially) refunds_outward' => '(sebagian) pengembalian uang', '(partially) pays for_outward' => '(sebagian) membayar', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index cc270a8c5f..eb29183d4c 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -465,7 +465,7 @@ return [ 'pref_two_factor_auth_code_help' => 'Esegui la scansione del codice QR con un\'applicazione sul tuo telefono come Authy o Google Authenticator e inserisci il codice generato.', 'pref_two_factor_auth_reset_code' => 'Reimposta il codice di verifica', 'pref_two_factor_auth_disable_2fa' => 'Disattiva 2FA', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_use_secret_instead' => 'Se non puoi scansionare il codice QR puoi utilizzare il segreto: :secret.', 'pref_save_settings' => 'Salva le impostazioni', 'saved_preferences' => 'Preferenze salvate!', 'preferences_general' => 'Generale', @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsata da', 'is (partially) paid for by_inward' => 'è (parzialmente) pagata da', 'is (partially) reimbursed by_inward' => 'è (parzialmente) rimborsata da', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'inerente a', '(partially) refunds_outward' => '(parzialmente) rimborsa', '(partially) pays for_outward' => '(parzialmente) paga per', @@ -1260,15 +1262,15 @@ return [ 'update_recurrence' => 'Aggiorna transazione ricorrente', 'updated_recurrence' => 'Transazione ricorrente ":title" aggiornata', 'recurrence_is_inactive' => 'Questa transazione ricorrente non è attiva e non genererà nuove transazioni.', - 'delete_recurring' => 'Delete recurring transaction ":title"', - 'new_recurring_transaction' => 'New recurring transaction', - 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', - 'do_nothing' => 'Just create the transaction', - 'skip_transaction' => 'Skip the occurence', - 'jump_to_friday' => 'Create the transaction on the previous Friday instead', - 'jump_to_monday' => 'Create the transaction on the next Monday instead', - 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', - 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', - 'except_weekends' => 'Except weekends', - 'recurrence_deleted' => 'Recurring transaction ":title" deleted', + 'delete_recurring' => 'Elimina transazione ricorrente ":title"', + 'new_recurring_transaction' => 'Nuova transazione ricorrente', + 'help_weekend' => 'Cosa vuoi che Firefly III faccia quando la transazione ricorrente cade di sabato o domenica?', + 'do_nothing' => 'Crea la transazione', + 'skip_transaction' => 'Salta l\'occorrenza', + 'jump_to_friday' => 'Crea la transazione il venerdì precedente', + 'jump_to_monday' => 'Crea la transazione il lunedì successivo', + 'will_jump_friday' => 'Verrà creata il venerdì anziché nel fine settimana.', + 'will_jump_monday' => 'Verrà creata il lunedì anziché il fine settimana.', + 'except_weekends' => 'Tranne il fine settimana', + 'recurrence_deleted' => 'La transazione ricorrente ":title" è stata eliminata', ]; diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index e9ebdf5f50..dd8c9b46ce 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -150,7 +150,7 @@ return [ 'delete_rule_group' => 'Elimina gruppo regole":title"', 'delete_link_type' => 'Elimina tipo collegamento ":name"', 'delete_user' => 'Elimina utente ":email"', - 'delete_recurring' => 'Delete recurring transaction ":title"', + 'delete_recurring' => 'Elimina transazione ricorrente ":title"', 'user_areYouSure' => 'Se cancelli l\'utente ":email", verrà eliminato tutto. Non sarà più possibile recuperare i dati eliminati. Se cancelli te stesso, perderai l\'accesso a questa istanza di Firefly III.', 'attachment_areYouSure' => 'Sei sicuro di voler eliminare l\'allegato ":name"?', 'account_areYouSure' => 'Sei sicuro di voler eliminare il conto ":name"?', @@ -159,7 +159,7 @@ return [ 'ruleGroup_areYouSure' => 'Sei sicuro di voler eliminare il gruppo regole ":title"?', 'budget_areYouSure' => 'Sei sicuro di voler eliminare il budget ":name"?', 'category_areYouSure' => 'Sei sicuro di voler eliminare categoria ":name"?', - 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'recurring_areYouSure' => 'Sei sicuro di voler eliminare la transazione ricorrente ":title"?', 'currency_areYouSure' => 'Sei sicuro di voler eliminare la valuta ":name"?', 'piggyBank_areYouSure' => 'Sei sicuro di voler eliminare il salvadanaio ":name"?', 'journal_areYouSure' => 'Sei sicuro di voler eliminare la transazione ":description"?', @@ -175,11 +175,11 @@ return [ 'also_delete_connections' => 'L\'unica transazione collegata a questo tipo di collegamento perderà questa connessione. | Tutto :count le transazioni di conteggio collegate a questo tipo di collegamento perderanno la connessione.', 'also_delete_rules' => 'Anche l\'unica regola collegata a questo gruppo di regole verrà eliminata. | Tutto :count verranno eliminate anche le regole di conteggio collegate a questo gruppo di regole.', 'also_delete_piggyBanks' => 'Verrà eliminato anche l\'unico salvadanaio collegato a questo conto. | Tutti :count il conteggio del salvadanaio collegato a questo conto verrà eliminato.', - 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', - 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', - 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', - 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', - 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'bill_keep_transactions' => 'L\'unica transazione connessa a questa bolletta non verrà eliminata.|Tutte le :count transazioni del conto collegate a questa bolletta non verranno cancellate.', + 'budget_keep_transactions' => 'L\'unica transazione collegata a questo budget non verrà eliminata.|Tutte le :count transazioni del conto collegate a questo budget non verranno cancellate.', + 'category_keep_transactions' => 'L\'unica transazione collegata a questa categoria non verrà eliminata.|Tutte le :count transazioni del conto collegate a questa categoria non verranno cancellate.', + 'recurring_keep_transactions' => 'L\'unica transazione creata da questa transazione ricorrente non verrà eliminata.|Tutte le :count transazioni create da questa transazione ricorrente non verranno eliminate.', + 'tag_keep_transactions' => 'L\'unica transazione connessa a questa etichetta non verrà eliminata.|Tutte le :count transazioni del conto collegate a questa etichetta non verranno eliminate.', 'check_for_updates' => 'Controlla gli aggiornamenti', 'email' => 'Indirizzo email', @@ -236,6 +236,6 @@ return [ 'repetition_end' => 'La ripetizione termina il', 'repetitions' => 'Ripetizioni', 'calendar' => 'Calendario', - 'weekend' => 'Weekend', + 'weekend' => 'Fine settimana', ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index d8e5d65400..5fd4aa332c 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -127,16 +127,16 @@ return [ 'spectre_no_mapping' => 'Sembra che tu non abbia selezionato nessun account da cui importare.', 'imported_from_account' => 'Importato da ":account"', 'spectre_account_with_number' => 'Conto :number', - 'job_config_spectre_apply_rules' => 'Apply rules', - 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'job_config_spectre_apply_rules' => 'Applica regole', + 'job_config_spectre_apply_rules_text' => 'Per impostazione predefinita le tue regole verranno applicate alle transazioni create durante questa procedura di importazione. Se non vuoi che questo accada, deseleziona questa casella di controllo.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'Account bunq', 'job_config_bunq_accounts_text' => 'Questi sono i conti associati al tuo account bunq. Seleziona i conti dai quali vuoi effettuare l\'importazione e in quale conto devono essere importate le transazioni.', 'bunq_no_mapping' => 'Sembra che tu non abbia selezionato alcun conto.', 'should_download_config' => 'Ti consigliamo di scaricare il file di configurazione per questa operazione. Ciò renderà le importazioni future più facili.', 'share_config_file' => 'Se hai importato dati da una banca pubblica, dovresti condividere il tuo file di configurazione così da rendere più facile per gli altri utenti importare i loro dati. La condivisione del file di configurazione non espone i tuoi dettagli finanziari.', - 'job_config_bunq_apply_rules' => 'Apply rules', - 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'job_config_bunq_apply_rules' => 'Applica regole', + 'job_config_bunq_apply_rules_text' => 'Per impostazione predefinita le tue regole verranno applicate alle transazioni create durante questa procedura di importazione. Se non vuoi che questo accada, deseleziona questa casella di controllo.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index 3232c022c7..5f7a29b645 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -44,8 +44,8 @@ return [ 'belongs_to_user' => 'Il valore di :attribute è sconosciuto', 'accepted' => 'L\' :attribute deve essere accettato.', 'bic' => 'Questo non è un BIC valido.', - 'base64' => 'This is not valid base64 encoded data.', - 'model_id_invalid' => 'The given ID seems invalid for this model.', + 'base64' => 'Questi non sono dati codificati in base64 validi.', + 'model_id_invalid' => 'L\'ID fornito sembra non essere valido per questo modello.', 'more' => ':attribute deve essere maggiore di zero.', 'active_url' => ':attribute non è un URL valido.', 'after' => ':attribute deve essere una data dopo :date.', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 4973ef5577..e3f2297966 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'wordt (deels) terugbetaald door', 'is (partially) paid for by_inward' => 'wordt (deels) betaald door', 'is (partially) reimbursed by_inward' => 'wordt (deels) vergoed door', + 'inward_transaction' => 'Actieve transactie', + 'outward_transaction' => 'Passieve transactie', 'relates to_outward' => 'gerelateerd aan', '(partially) refunds_outward' => 'is een (gedeeltelijke) terugbetaling voor', '(partially) pays for_outward' => 'betaalt (deels) voor', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 528a1d2450..c23c3c6557 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'jest (częściowo) zwracane przez', 'is (partially) paid for by_inward' => 'jest (częściowo) opłacane przez', 'is (partially) reimbursed by_inward' => 'jest (częściowo) refundowany przez', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'odnosi się do', '(partially) refunds_outward' => '(częściowo) refundowany', '(partially) pays for_outward' => '(częściowo) płaci za', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index f4f871629f..41ad6e78ff 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => 'é (parcialmente) devolvido por', 'is (partially) paid for by_inward' => 'é (parcialmente) pago por', 'is (partially) reimbursed by_inward' => 'é (parcialmente) reembolsado por', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'relacionado a', '(partially) refunds_outward' => 'reembolsos (parcialmente)', '(partially) pays for_outward' => 'paga (parcialmente) por', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index f64ee3b21f..c18e2716da 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -1134,6 +1134,8 @@ return [ 'is (partially) refunded by_inward' => '(частично) возвращён', 'is (partially) paid for by_inward' => '(частично) оплачен', 'is (partially) reimbursed by_inward' => '(частично) возмещён', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'относится к', '(partially) refunds_outward' => '(частично) возвращены', '(partially) pays for_outward' => '(частично) оплачены', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index b5bb107d15..ec3ebb0c00 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -1135,6 +1135,8 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'is (partially) refunded by_inward' => 'tarafından (kısmen) geri ödendi', 'is (partially) paid for by_inward' => 'tarafından (kısmen) ödendi', 'is (partially) reimbursed by_inward' => 'tarafından (kısmen) iade edildi', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', 'relates to_outward' => 'ile ilişkili', '(partially) refunds_outward' => '(kısmen) geri ödeme', '(partially) pays for_outward' => 'için (kısmen) öder', diff --git a/routes/api.php b/routes/api.php index ba748dbc0e..d5e2de9214 100644 --- a/routes/api.php +++ b/routes/api.php @@ -182,6 +182,20 @@ Route::group( } ); +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'preferences', 'as' => 'api.v1.preferences.'], + function () { + + // Piggy Bank API routes: + Route::get('', ['uses' => 'PreferenceController@index', 'as' => 'index']); + Route::get('{preference}', ['uses' => 'PreferenceController@show', 'as' => 'show']); + // Route::post('', ['uses' => 'PiggyBankController@store', 'as' => 'store']); + + // Route::put('{piggyBank}', ['uses' => 'PiggyBankController@update', 'as' => 'update']); + // Route::delete('{piggyBank}', ['uses' => 'PiggyBankController@delete', 'as' => 'delete']); + } +); + Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', 'as' => 'api.v1.currencies.'], function () { From e287e76db53bbdac26cac9bff5e031cadcdad8ed Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Jun 2018 12:28:29 +0200 Subject: [PATCH 085/134] Complete preferences. --- .../V1/Controllers/PreferenceController.php | 48 ++++++++++------ app/Api/V1/Requests/PreferenceRequest.php | 57 +++++++++++++++++++ routes/api.php | 3 +- 3 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 app/Api/V1/Requests/PreferenceRequest.php diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php index b7b7e8bee4..44363133d4 100644 --- a/app/Api/V1/Controllers/PreferenceController.php +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use FireflyIII\Api\V1\Requests\PreferenceRequest; use FireflyIII\Models\Preference; use FireflyIII\Transformers\PreferenceTransformer; use FireflyIII\User; @@ -109,7 +110,7 @@ class PreferenceController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param Preference $preference * * @return JsonResponse @@ -129,27 +130,42 @@ class PreferenceController extends Controller } /** - * Store new object. - * - * @param Request $request + * @param PreferenceRequest $request + * @param Preference $preference * * @return JsonResponse */ - public function store(Request $request): JsonResponse + public function update(PreferenceRequest $request, Preference $preference): JsonResponse { - // todo replace code and replace request object. - } + $data = $request->getAll(); + $newValue = $data['data']; + switch ($preference->name) { + default: + break; + case 'transaction_journal_optional_fields': + case 'frontPageAccounts': + $newValue = explode(',', $data['data']); + break; + case 'listPageSize': + $newValue = (int)$data['data']; + break; + case 'customFiscalYear': + case 'twoFactorAuthEnabled': + $newValue = (int)$data['data'] === 1; + break; + } + $result = Preferences::set($preference->name, $newValue); - /** - * @param Request $request - * @param string $object - * - * @return JsonResponse - */ - public function update(Request $request, string $object): JsonResponse - { - // todo replace code and replace request object. + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($result, new PreferenceTransformer($this->parameters), 'preferences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } } \ No newline at end of file diff --git a/app/Api/V1/Requests/PreferenceRequest.php b/app/Api/V1/Requests/PreferenceRequest.php new file mode 100644 index 0000000000..38c3a39e11 --- /dev/null +++ b/app/Api/V1/Requests/PreferenceRequest.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +/** + * + * Class PreferenceRequest + */ +class PreferenceRequest extends Request +{ + + + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + public function getAll(): array + { + return [ + 'data' => $this->get('data'), + ]; + } + + public function rules(): array + { + return [ + 'data' => 'required|between:1,65000', + ]; + } + +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index d5e2de9214..3212017d89 100644 --- a/routes/api.php +++ b/routes/api.php @@ -189,9 +189,8 @@ Route::group( // Piggy Bank API routes: Route::get('', ['uses' => 'PreferenceController@index', 'as' => 'index']); Route::get('{preference}', ['uses' => 'PreferenceController@show', 'as' => 'show']); + Route::put('{preference}', ['uses' => 'PreferenceController@update', 'as' => 'update']); // Route::post('', ['uses' => 'PiggyBankController@store', 'as' => 'store']); - - // Route::put('{piggyBank}', ['uses' => 'PiggyBankController@update', 'as' => 'update']); // Route::delete('{piggyBank}', ['uses' => 'PiggyBankController@delete', 'as' => 'delete']); } ); From d0db1117f73f17a924c045dd7081e3e37a60623b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Jun 2018 14:07:33 +0200 Subject: [PATCH 086/134] Recurrence first start and API routes. --- .../V1/Controllers/RecurrenceController.php | 46 +++++++++++++++++-- routes/api.php | 17 +++++-- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index 519ffd65f8..e901c1a66d 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -23,13 +23,27 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Transformers\PiggyBankTransformer; +use FireflyIII\Transformers\RecurrenceTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use League\Fractal\Manager; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Serializer\JsonApiSerializer; - -class RecurrenceController +/** + * + * Class RecurrenceController + */ +class RecurrenceController extends Controller { + /** @var RecurringRepositoryInterface */ + private $repository; + public function __construct() { parent::__construct(); @@ -38,7 +52,10 @@ class RecurrenceController /** @var User $user */ $user = auth()->user(); - // todo add local repositories. + /** @var RecurringRepositoryInterface repository */ + $this->repository = app(RecurringRepositoryInterface::class); + $this->repository->setUser($user); + return $next($request); } ); @@ -67,7 +84,28 @@ class RecurrenceController */ public function index(Request $request): JsonResponse { - // todo implement. + // create some objects: + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + + // types to get, page size: + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + + // get list of budgets. Count it and split it. + $collection = $this->repository->getAll(); + $count = $collection->count(); + $piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + + // make paginator: + $paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.recurrences.index') . $this->buildParams()); + + // present to user. + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new FractalCollection($piggyBanks, new RecurrenceTransformer($this->parameters), 'recurrences'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } diff --git a/routes/api.php b/routes/api.php index 3212017d89..e9beddb78a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -186,12 +186,23 @@ Route::group( ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'preferences', 'as' => 'api.v1.preferences.'], function () { - // Piggy Bank API routes: + // Preference API routes: Route::get('', ['uses' => 'PreferenceController@index', 'as' => 'index']); Route::get('{preference}', ['uses' => 'PreferenceController@show', 'as' => 'show']); Route::put('{preference}', ['uses' => 'PreferenceController@update', 'as' => 'update']); - // Route::post('', ['uses' => 'PiggyBankController@store', 'as' => 'store']); - // Route::delete('{piggyBank}', ['uses' => 'PiggyBankController@delete', 'as' => 'delete']); + } +); + +Route::group( + ['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'recurrences', 'as' => 'api.v1.recurrences.'], + function () { + + // Recurrence API routes: + Route::get('', ['uses' => 'RecurrenceController@index', 'as' => 'index']); + Route::post('', ['uses' => 'RecurrenceController@store', 'as' => 'store']); + Route::get('{recurrence}', ['uses' => 'RecurrenceController@show', 'as' => 'show']); + Route::put('{recurrence}', ['uses' => 'RecurrenceController@update', 'as' => 'update']); + Route::delete('{recurrence}', ['uses' => 'RecurrenceController@delete', 'as' => 'delete']); } ); From 2d7b7c2f3f8739cd0e0380b557a55bc3d9f252c4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 29 Jun 2018 19:27:07 +0200 Subject: [PATCH 087/134] Expand recurring transactions API --- .../V1/Controllers/PreferenceController.php | 14 - .../V1/Controllers/RecurrenceController.php | 38 +- app/Api/V1/Requests/RecurrenceRequest.php | 480 ++++++++++++++++++ app/Api/V1/Requests/TransactionRequest.php | 10 +- app/Factory/RecurrenceFactory.php | 12 +- app/Jobs/CreateRecurringTransactions.php | 4 +- app/Models/RecurrenceTransaction.php | 6 +- app/Models/RecurrenceTransactionMeta.php | 2 +- .../Recurring/RecurringRepository.php | 1 - .../RecurringRepositoryInterface.php | 1 - .../Destroy/RecurrenceDestroyService.php | 14 + .../Support/RecurringTransactionTrait.php | 22 +- .../2018_06_08_200526_changes_for_v475.php | 8 +- public/js/ff/recurring/create.js | 36 +- public/js/ff/recurring/edit.js | 32 +- resources/lang/en_US/form.php | 120 ++--- resources/lang/en_US/validation.php | 4 + resources/views/recurring/create.twig | 8 +- resources/views/recurring/edit.twig | 8 +- resources/views/recurring/index.twig | 4 +- resources/views/recurring/show.twig | 8 +- 21 files changed, 670 insertions(+), 162 deletions(-) create mode 100644 app/Api/V1/Requests/RecurrenceRequest.php diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php index 44363133d4..3efe8fa35a 100644 --- a/app/Api/V1/Controllers/PreferenceController.php +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -56,20 +56,6 @@ class PreferenceController extends Controller ); } - /** - * Delete the resource. - * - * @param string $object - * - * @return JsonResponse - */ - public function delete(string $object): JsonResponse - { - // todo delete object. - - return response()->json([], 204); - } - /** * List all of them. * diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index e901c1a66d..84af2a235a 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use FireflyIII\Api\V1\Requests\RecurrenceRequest; +use FireflyIII\Models\Recurrence; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; -use FireflyIII\Transformers\PiggyBankTransformer; use FireflyIII\Transformers\RecurrenceTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -33,6 +34,7 @@ use Illuminate\Pagination\LengthAwarePaginator; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; /** @@ -68,9 +70,9 @@ class RecurrenceController extends Controller * * @return JsonResponse */ - public function delete(string $object): JsonResponse + public function delete(Recurrence $recurrence): JsonResponse { - // todo delete object. + $this->repository->destroy($recurrence); return response()->json([], 204); } @@ -112,28 +114,44 @@ class RecurrenceController extends Controller /** * List single resource. * - * @param Request $request - * @param string $object + * @param Request $request + * @param Recurrence $recurrence * * @return JsonResponse */ - public function show(Request $request, string $object): JsonResponse + public function show(Request $request, Recurrence $recurrence): JsonResponse { - // todo implement me. + $manager = new Manager(); + // add include parameter: + $include = $request->get('include') ?? ''; + $manager->parseIncludes($include); + + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + $resource = new Item($recurrence, new RecurrenceTransformer($this->parameters), 'recurrences'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } /** * Store new object. * - * @param Request $request + * @param RecurrenceRequest $request * * @return JsonResponse */ - public function store(Request $request): JsonResponse + public function store(RecurrenceRequest $request): JsonResponse { - // todo replace code and replace request object. + $recurrence = $this->repository->store($request->getAll()); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + $resource = new Item($recurrence, new RecurrenceTransformer($this->parameters), 'recurrences'); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php new file mode 100644 index 0000000000..6c7925fed6 --- /dev/null +++ b/app/Api/V1/Requests/RecurrenceRequest.php @@ -0,0 +1,480 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Rules\BelongsUser; +use Illuminate\Validation\Validator; +use InvalidArgumentException; + +/** + * Class RecurrenceRequest + */ +class RecurrenceRequest extends Request +{ + /** + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getAll(): array + { + $return = [ + 'recurrence' => [ + 'type' => $this->string('type'), + 'title' => $this->string('title'), + 'description' => $this->string('description'), + 'first_date' => $this->date('first_date'), + 'repeat_until' => $this->date('repeat_until'), + 'repetitions' => $this->integer('nr_of_repetitions'), + 'apply_rules' => $this->boolean('apply_rules'), + 'active' => $this->boolean('active'), + ], + 'meta' => [ + 'piggy_bank_id' => $this->integer('piggy_bank_id'), + 'piggy_bank_name' => $this->string('piggy_bank_name'), + 'tags' => explode(',', $this->string('tags')), + ], + 'transactions' => [], + 'repetitions' => [], + ]; + + // repetition data: + /** @var array $repetitions */ + $repetitions = $this->get('repetitions'); + /** @var array $repetition */ + foreach ($repetitions as $repetition) { + $return['repetitions'][] = [ + 'type' => $repetition['type'], + 'moment' => $repetition['moment'], + 'skip' => (int)$repetition['skip'], + 'weekend' => (int)$repetition['weekend'], + ]; + } + // transaction data: + /** @var array $transactions */ + $transactions = $this->get('transactions'); + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return['transactions'][] = [ + 'amount' => $transaction['amount'], + + 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, + 'currency_code' => $transaction['currency_code'] ?? null, + + 'foreign_amount' => $transaction['foreign_amount'] ?? null, + 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, + 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, + + 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, + 'budget_name' => $transaction['budget_name'] ?? null, + 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, + 'category_name' => $transaction['category_name'] ?? null, + + 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, + 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, + 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, + 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, + + 'description' => $transaction['description'], + ]; + } + + return $return; + } + + /** + * @return array + */ + public function rules(): array + { + $today = new Carbon; + $today->addDay(); + + return [ + 'type' => 'required|in:withdrawal,transfer,deposit', + 'title' => 'required|between:1,255', + 'description' => 'between:1,65000', + 'first_date' => sprintf('required|date|after:%s', $today->format('Y-m-d')), + 'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')), + 'nr_of_repetitions' => 'numeric|between:1,31', + 'apply_rules' => 'required|boolean', + 'active' => 'required|boolean', + + // rules for meta values: + 'tags' => 'between:1,64000', + 'piggy_bank_id' => 'numeric', + + // rules for repetitions. + 'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly', + 'repetitions.*.moment' => 'between:0,10', + 'repetitions.*.skip' => 'required|between:0,31', + 'repetitions.*.weekend' => 'required|between:1,4', + + // rules for transactions. + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code', + 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id', + 'transactions.*.foreign_amount' => 'numeric|more:0', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.source_name' => 'between:1,255|nullable', + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.destination_name' => 'between:1,255|nullable', + 'transactions.*.amount' => 'required|numeric|more:0', + 'transactions.*.description' => 'required|between:1,255', + ]; + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $this->atLeastOneTransaction($validator); + $this->atLeastOneRepetition($validator); + $this->validRepeatsUntil($validator); + $this->validRepetitionMoment($validator); + $this->foreignCurrencyInformation($validator); + $this->validateAccountInformation($validator); + } + ); + } + + /** + * Throws an error when this asset account is invalid. + * + * @noinspection MoreThanThreeArgumentsInspection + * + * @param Validator $validator + * @param int|null $accountId + * @param null|string $accountName + * @param string $idField + * @param string $nameField + * + * @return null|Account + */ + protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): ?Account + { + $accountId = (int)$accountId; + $accountName = (string)$accountName; + // both empty? hard exit. + if ($accountId < 1 && '' === $accountName) { + $validator->errors()->add($idField, trans('validation.filled', ['attribute' => $idField])); + + return null; + } + // ID belongs to user and is asset account: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $set = $repository->getAccountsById([$accountId]); + if ($set->count() === 1) { + /** @var Account $first */ + $first = $set->first(); + if ($first->accountType->type !== AccountType::ASSET) { + $validator->errors()->add($idField, trans('validation.belongs_user')); + + return null; + } + + // we ignore the account name at this point. + return $first; + } + + $account = $repository->findByName($accountName, [AccountType::ASSET]); + if (null === $account) { + $validator->errors()->add($nameField, trans('validation.belongs_user')); + + return null; + } + + return $account; + } + + /** + * Adds an error to the validator when there are no repetitions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneRepetition(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['repetitions'] ?? []; + // need at least one transaction + if (\count($repetitions) === 0) { + $validator->errors()->add('description', trans('validation.at_least_one_repetition')); + } + } + + /** + * Adds an error to the validator when there are no transactions in the array of data. + * + * @param Validator $validator + */ + protected function atLeastOneTransaction(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + // need at least one transaction + if (\count($transactions) === 0) { + $validator->errors()->add('description', trans('validation.at_least_one_transaction')); + } + } + + /** + * TODO can be made a rule? + * If the transactions contain foreign amounts, there must also be foreign currency information. + * + * @param Validator $validator + */ + protected function foreignCurrencyInformation(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + foreach ($transactions as $index => $transaction) { + // must have currency info. + if (isset($transaction['foreign_amount']) + && !(isset($transaction['foreign_currency_id']) + || isset($transaction['foreign_currency_code']))) { + $validator->errors()->add( + 'transactions.' . $index . '.foreign_amount', + trans('validation.require_currency_info') + ); + } + } + } + + /** + * Throws an error when the given opposing account (of type $type) is invalid. + * Empty data is allowed, system will default to cash. + * + * @noinspection MoreThanThreeArgumentsInspection + * + * @param Validator $validator + * @param string $type + * @param int|null $accountId + * @param null|string $accountName + * @param string $idField + * + * @return null|Account + */ + protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): ?Account + { + $accountId = (int)$accountId; + $accountName = (string)$accountName; + // both empty? done! + if ($accountId < 1 && \strlen($accountName) === 0) { + return null; + } + if ($accountId !== 0) { + // ID belongs to user and is $type account: + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser(auth()->user()); + $set = $repository->getAccountsById([$accountId]); + if ($set->count() === 1) { + /** @var Account $first */ + $first = $set->first(); + if ($first->accountType->type !== $type) { + $validator->errors()->add($idField, trans('validation.belongs_user')); + + return null; + } + + // we ignore the account name at this point. + return $first; + } + } + + // not having an opposing account by this name is NOT a problem. + return null; + } + + /** + * TODO can be a rule? + * + * Validates the given account information. Switches on given transaction type. + * + * @param Validator $validator + */ + protected function validateAccountInformation(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + $idField = 'description'; + $transactionType = $data['type'] ?? 'false'; + foreach ($transactions as $index => $transaction) { + $sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null; + $sourceName = $transaction['source_name'] ?? null; + $destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null; + $destinationName = $transaction['destination_name'] ?? null; + $sourceAccount = null; + $destinationAccount = null; + switch ($transactionType) { + case 'withdrawal': + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; + $sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); + $idField = 'transactions.' . $index . '.destination_id'; + $destinationAccount = $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField); + break; + case 'deposit': + $idField = 'transactions.' . $index . '.source_id'; + $sourceAccount = $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField); + + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; + $destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); + break; + case 'transfer': + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; + $sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); + + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; + $destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); + break; + default: + $validator->errors()->add($idField, trans('validation.invalid_account_info')); + + return; + + } + // add some errors in case of same account submitted: + if (null !== $sourceAccount && null !== $destinationAccount && $sourceAccount->id === $destinationAccount->id) { + $validator->errors()->add($idField, trans('validation.source_equals_destination')); + } + } + } + + /** + * @param Validator $validator + */ + private function validRepeatsUntil(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['nr_of_repetitions'] ?? null; + $repeatUntil = $data['repeat_until'] ?? null; + if (null !== $repetitions && null !== $repeatUntil) { + // expect a date OR count: + $validator->errors()->add('repeat_until', trans('validation.require_repeat_until')); + $validator->errors()->add('repetitions', trans('validation.require_repeat_until')); + + return; + } + } + + /** + * TODO merge this in a rule somehow. + * + * @param Validator $validator + */ + private function validRepetitionMoment(Validator $validator): void + { + $data = $validator->getData(); + $repetitions = $data['repetitions'] ?? []; + /** + * @var int $index + * @var array $repetition + */ + foreach ($repetitions as $index => $repetition) { + switch ($repetition['type']) { + default: + $validator->errors()->add(sprintf('repetitions.%d.type', $index), trans('validation.valid_recurrence_rep_type')); + + return; + case 'daily': + if ('' !== (string)$repetition['moment']) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + } + + return; + case 'monthly': + $dayOfMonth = (int)$repetition['moment']; + if ($dayOfMonth < 1 || $dayOfMonth > 31) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + } + + return; + case 'ndom': + $parameters = explode(',', $repetition['moment']); + if (\count($parameters) !== 2) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + $nthDay = (int)($parameters[0] ?? 0.0); + $dayOfWeek = (int)($parameters[1] ?? 0.0); + if ($nthDay < 1 || $nthDay > 5) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + if ($dayOfWeek < 1 || $dayOfWeek > 7) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + + return; + case 'weekly': + $dayOfWeek = (int)$repetition['moment']; + if ($dayOfWeek < 1 || $dayOfWeek > 7) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + break; + case 'yearly': + try { + Carbon::createFromFormat('Y-m-d', $repetition['moment']); + } catch (InvalidArgumentException $e) { + $validator->errors()->add(sprintf('repetitions.%d.moment', $index), trans('validation.valid_recurrence_rep_moment')); + + return; + } + } + } + } +} \ No newline at end of file diff --git a/app/Api/V1/Requests/TransactionRequest.php b/app/Api/V1/Requests/TransactionRequest.php index ab03ec561a..ce0c27e1be 100644 --- a/app/Api/V1/Requests/TransactionRequest.php +++ b/app/Api/V1/Requests/TransactionRequest.php @@ -188,6 +188,8 @@ class TransactionRequest extends Request /** * Throws an error when this asset account is invalid. * + * @noinspection MoreThanThreeArgumentsInspection + * * @param Validator $validator * @param int|null $accountId * @param null|string $accountName @@ -256,7 +258,7 @@ class TransactionRequest extends Request * * @param Validator $validator */ - protected function checkValidDescriptions(Validator $validator) + protected function checkValidDescriptions(Validator $validator): void { $data = $validator->getData(); $transactions = $data['transactions'] ?? []; @@ -317,6 +319,8 @@ class TransactionRequest extends Request } /** + * TODO can be made a rule? + * * If the transactions contain foreign amounts, there must also be foreign currency information. * * @param Validator $validator @@ -342,6 +346,8 @@ class TransactionRequest extends Request * Throws an error when the given opposing account (of type $type) is invalid. * Empty data is allowed, system will default to cash. * + * @noinspection MoreThanThreeArgumentsInspection + * * @param Validator $validator * @param string $type * @param int|null $accountId @@ -454,7 +460,7 @@ class TransactionRequest extends Request * * @throws FireflyException */ - protected function validateSplitAccounts(Validator $validator) + protected function validateSplitAccounts(Validator $validator): void { $data = $validator->getData(); $count = isset($data['transactions']) ? \count($data['transactions']) : 0; diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index b4cd95d86d..f144022ed7 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -30,6 +30,7 @@ use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; use FireflyIII\Services\Internal\Support\TransactionServiceTrait; use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; +use Log; /** * Class RecurrenceFactory @@ -44,12 +45,17 @@ class RecurrenceFactory /** * @param array $data * - * @throws FireflyException * @return Recurrence */ - public function create(array $data): Recurrence + public function create(array $data): ?Recurrence { - $type = $this->findTransactionType(ucfirst($data['recurrence']['type'])); + try { + $type = $this->findTransactionType(ucfirst($data['recurrence']['type'])); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + + return null; + } $repetitions = (int)$data['recurrence']['repetitions']; $recurrence = new Recurrence( [ diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 05b036b5ed..281d2e146c 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -150,9 +150,9 @@ class CreateRecurringTransactions implements ShouldQueue 'budget_name' => null, 'category_id' => null, 'category_name' => $this->repository->getCategory($transaction), - 'source_id' => $transaction->source_account_id, + 'source_id' => $transaction->source_id, 'source_name' => null, - 'destination_id' => $transaction->destination_account_id, + 'destination_id' => $transaction->destination_id, 'destination_name' => null, 'foreign_currency_id' => $transaction->foreign_currency_id, 'foreign_currency_code' => null, diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php index a9ad116f02..8687733929 100644 --- a/app/Models/RecurrenceTransaction.php +++ b/app/Models/RecurrenceTransaction.php @@ -35,8 +35,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; * * @property int $transaction_currency_id, * @property int $foreign_currency_id - * @property int $source_account_id - * @property int $destination_account_id + * @property int $source_id + * @property int $destination_id * @property string $amount * @property string $foreign_amount * @property string $description @@ -62,7 +62,7 @@ class RecurrenceTransaction extends Model ]; /** @var array */ protected $fillable - = ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_account_id', 'destination_account_id', 'amount', 'foreign_amount', + = ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_id', 'destination_id', 'amount', 'foreign_amount', 'description']; /** @var string */ protected $table = 'recurrences_transactions'; diff --git a/app/Models/RecurrenceTransactionMeta.php b/app/Models/RecurrenceTransactionMeta.php index 66d8cdd85c..aa246529cf 100644 --- a/app/Models/RecurrenceTransactionMeta.php +++ b/app/Models/RecurrenceTransactionMeta.php @@ -39,7 +39,7 @@ class RecurrenceTransactionMeta extends Model use SoftDeletes; /** @var array */ protected $casts - = [ + = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 1046fb41f3..b4d1ae1e91 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -508,7 +508,6 @@ class RecurringRepository implements RecurringRepositoryInterface /** * @param array $data * - * @throws FireflyException * @return Recurrence */ public function store(array $data): Recurrence diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index 98751b3f39..92f824d25a 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -155,7 +155,6 @@ interface RecurringRepositoryInterface * * @param array $data * - * @throws FireflyException * @return Recurrence */ public function store(array $data): Recurrence; diff --git a/app/Services/Internal/Destroy/RecurrenceDestroyService.php b/app/Services/Internal/Destroy/RecurrenceDestroyService.php index 968f249235..6f59892f4f 100644 --- a/app/Services/Internal/Destroy/RecurrenceDestroyService.php +++ b/app/Services/Internal/Destroy/RecurrenceDestroyService.php @@ -25,6 +25,7 @@ namespace FireflyIII\Services\Internal\Destroy; use Exception; use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceTransaction; use Log; /** @@ -39,6 +40,19 @@ class RecurrenceDestroyService public function destroy(Recurrence $recurrence): void { try { + // delete all meta data + $recurrence->recurrenceMeta()->delete(); + + // delete all transactions. + /** @var RecurrenceTransaction $transaction */ + foreach($recurrence->recurrenceTransactions as $transaction) { + $transaction->recurrenceTransactionMeta()->delete(); + $transaction->delete(); + } + // delete all repetitions + $recurrence->recurrenceRepetitions()->delete(); + + // delete recurrence $recurrence->delete(); } catch (Exception $e) { // @codeCoverageIgnore Log::error(sprintf('Could not delete recurrence: %s', $e->getMessage())); // @codeCoverageIgnore diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index 4de55141ce..c57dce1f82 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -66,8 +66,6 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence * @param array $transactions - * - * @throws FireflyException */ public function createTransactions(Recurrence $recurrence, array $transactions): void { @@ -76,19 +74,17 @@ trait RecurringTransactionTrait $source = null; $destination = null; switch ($recurrence->transactionType->type) { - default: - throw new FireflyException(sprintf('Cannot create "%s".', $recurrence->transactionType->type)); case TransactionType::WITHDRAWAL: - $source = $this->findAccount(AccountType::ASSET, $array['source_account_id'], null); - $destination = $this->findAccount(AccountType::EXPENSE, null, $array['destination_account_name']); + $source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name'] ?? null); + $destination = $this->findAccount(AccountType::EXPENSE, $array['destination_id'] ?? null, $array['destination_name']); break; case TransactionType::DEPOSIT: - $source = $this->findAccount(AccountType::REVENUE, null, $array['source_account_name']); - $destination = $this->findAccount(AccountType::ASSET, $array['destination_account_id'], null); + $source = $this->findAccount(AccountType::REVENUE, $array['source_id'] ?? null, $array['source_name']); + $destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name'] ?? null); break; case TransactionType::TRANSFER: - $source = $this->findAccount(AccountType::ASSET, $array['source_account_id'], null); - $destination = $this->findAccount(AccountType::ASSET, $array['destination_account_id'], null); + $source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name'] ?? null); + $destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name'] ?? null); break; } @@ -96,9 +92,9 @@ trait RecurringTransactionTrait [ 'recurrence_id' => $recurrence->id, 'transaction_currency_id' => $array['transaction_currency_id'], - 'foreign_currency_id' => '' === (string)$array['foreign_amount'] ? null : $array['foreign_currency_id'], - 'source_account_id' => $source->id, - 'destination_account_id' => $destination->id, + 'foreign_currency_id' => '' === (string)$array['foreign_currency_id'] ? null : $array['foreign_currency_id'], + 'source_id' => $source->id, + 'destination_id' => $destination->id, 'amount' => $array['amount'], 'foreign_amount' => '' === (string)$array['foreign_amount'] ? null : (string)$array['foreign_amount'], 'description' => $array['description'], diff --git a/database/migrations/2018_06_08_200526_changes_for_v475.php b/database/migrations/2018_06_08_200526_changes_for_v475.php index 366ea260f9..fa85ed5456 100644 --- a/database/migrations/2018_06_08_200526_changes_for_v475.php +++ b/database/migrations/2018_06_08_200526_changes_for_v475.php @@ -66,8 +66,8 @@ class ChangesForV475 extends Migration $table->integer('recurrence_id', false, true); $table->integer('transaction_currency_id', false, true); $table->integer('foreign_currency_id', false, true)->nullable(); - $table->integer('source_account_id', false, true); - $table->integer('destination_account_id', false, true); + $table->integer('source_id', false, true); + $table->integer('destination_id', false, true); $table->decimal('amount', 22, 12); $table->decimal('foreign_amount', 22, 12)->nullable(); @@ -77,8 +77,8 @@ class ChangesForV475 extends Migration $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); $table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null'); - $table->foreign('source_account_id')->references('id')->on('accounts')->onDelete('cascade'); - $table->foreign('destination_account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('source_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('destination_id')->references('id')->on('accounts')->onDelete('cascade'); } ); diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js index 7e1deebfff..4d47190f70 100644 --- a/public/js/ff/recurring/create.js +++ b/public/js/ff/recurring/create.js @@ -155,15 +155,15 @@ function initializeAutoComplete() { ); }); - if ($('input[name="destination_account_name"]').length > 0) { + if ($('input[name="destination_name"]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="destination_name"]').typeahead({source: data, autoSelect: false}); }); } - if ($('input[name="source_account_name"]').length > 0) { + if ($('input[name="source_name"]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="source_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="source_name"]').typeahead({source: data, autoSelect: false}); }); } @@ -208,16 +208,16 @@ function updateFormFields() { if (transactionType === 'withdrawal') { // hide source account name: - $('#source_account_name_holder').hide(); + $('#source_name_holder').hide(); // show source account ID: - $('#source_account_id_holder').show(); - - // show destination name: - $('#destination_account_name_holder').show(); + $('#source_id_holder').show(); // hide destination ID: - $('#destination_account_id_holder').hide(); + $('#destination_id_holder').hide(); + + // show destination name: + $('#destination_name_holder').show(); // show budget $('#budget_id_holder').show(); @@ -227,19 +227,19 @@ function updateFormFields() { } if (transactionType === 'deposit') { - $('#source_account_name_holder').show(); - $('#source_account_id_holder').hide(); - $('#destination_account_name_holder').hide(); - $('#destination_account_id_holder').show(); + $('#source_name_holder').show(); + $('#source_id_holder').hide(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').hide(); } if (transactionType === 'transfer') { - $('#source_account_name_holder').hide(); - $('#source_account_id_holder').show(); - $('#destination_account_name_holder').hide(); - $('#destination_account_id_holder').show(); + $('#source_name_holder').hide(); + $('#source_id_holder').show(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').show(); } diff --git a/public/js/ff/recurring/edit.js b/public/js/ff/recurring/edit.js index f499280b57..24ee67c1e8 100644 --- a/public/js/ff/recurring/edit.js +++ b/public/js/ff/recurring/edit.js @@ -156,15 +156,15 @@ function initializeAutoComplete() { ); }); - if ($('input[name="destination_account_name"]').length > 0) { + if ($('input[name="destination_name"]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="destination_name"]').typeahead({source: data, autoSelect: false}); }); } - if ($('input[name="source_account_name"]').length > 0) { + if ($('input[name="source_name"]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="source_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="source_name"]').typeahead({source: data, autoSelect: false}); }); } @@ -209,16 +209,16 @@ function updateFormFields() { if (transactionType === 'withdrawal') { // hide source account name: - $('#source_account_name_holder').hide(); + $('#source_name_holder').hide(); // show source account ID: - $('#source_account_id_holder').show(); + $('#source_id_holder').show(); // show destination name: - $('#destination_account_name_holder').show(); + $('#destination_name_holder').show(); // hide destination ID: - $('#destination_account_id_holder').hide(); + $('#destination_id_holder').hide(); // show budget $('#budget_id_holder').show(); @@ -228,19 +228,19 @@ function updateFormFields() { } if (transactionType === 'deposit') { - $('#source_account_name_holder').show(); - $('#source_account_id_holder').hide(); - $('#destination_account_name_holder').hide(); - $('#destination_account_id_holder').show(); + $('#source_name_holder').show(); + $('#source_id_holder').hide(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').hide(); } if (transactionType === 'transfer') { - $('#source_account_name_holder').hide(); - $('#source_account_id_holder').show(); - $('#destination_account_name_holder').hide(); - $('#destination_account_id_holder').show(); + $('#source_name_holder').hide(); + $('#source_id_holder').show(); + $('#destination_name_holder').hide(); + $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').show(); } diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 773f713fa0..fe18364b8e 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -24,66 +24,66 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Bank name', - 'bank_balance' => 'Balance', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', - 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', - 'match' => 'Matches on', - 'strict' => 'Strict mode', - 'repeat_freq' => 'Repeats', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'transaction_currency_id' => 'Currency', - 'external_ip' => 'Your server\'s external IP', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', - 'journal_source_account_name' => 'Revenue account (source)', - 'journal_source_account_id' => 'Asset account (source)', - 'BIC' => 'BIC', - 'verify_password' => 'Verify password security', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', - 'journal_destination_account_id' => 'Asset account (destination)', - 'asset_destination_account' => 'Asset account (destination)', - 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', - 'split_journal' => 'Split this transaction', - 'split_journal_explanation' => 'Split this transaction in multiple parts', - 'currency' => 'Currency', - 'account_id' => 'Asset account', - 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', - 'tagMode' => 'Tag mode', - 'tag_position' => 'Tag location', - 'virtualBalance' => 'Virtual balance', - 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', - 'piggy_bank_id' => 'Piggy bank', - 'returnHere' => 'Return here', - 'returnHereExplanation' => 'After storing, return here to create another one.', - 'returnHereUpdateExplanation' => 'After updating, return here.', - 'description' => 'Description', - 'expense_account' => 'Expense account', - 'revenue_account' => 'Revenue account', - 'decimal_places' => 'Decimal places', - 'exchange_rate_instruction' => 'Foreign currencies', - 'source_amount' => 'Amount (source)', - 'destination_amount' => 'Amount (destination)', - 'native_amount' => 'Native amount', - 'new_email_address' => 'New email address', - 'verification' => 'Verification', - 'api_key' => 'API key', - 'remember_me' => 'Remember me', + 'bank_name' => 'Bank name', + 'bank_balance' => 'Balance', + 'savings_balance' => 'Savings balance', + 'credit_card_limit' => 'Credit card limit', + 'automatch' => 'Match automatically', + 'skip' => 'Skip', + 'name' => 'Name', + 'active' => 'Active', + 'amount_min' => 'Minimum amount', + 'amount_max' => 'Maximum amount', + 'match' => 'Matches on', + 'strict' => 'Strict mode', + 'repeat_freq' => 'Repeats', + 'journal_currency_id' => 'Currency', + 'currency_id' => 'Currency', + 'transaction_currency_id' => 'Currency', + 'external_ip' => 'Your server\'s external IP', + 'attachments' => 'Attachments', + 'journal_amount' => 'Amount', + 'journal_source_name' => 'Revenue account (source)', + 'journal_source_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'verify_password' => 'Verify password security', + 'source_account' => 'Source account', + 'destination_account' => 'Destination account', + 'journal_destination_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Asset account (destination)', + 'asset_source_account' => 'Asset account (source)', + 'journal_description' => 'Description', + 'note' => 'Notes', + 'split_journal' => 'Split this transaction', + 'split_journal_explanation' => 'Split this transaction in multiple parts', + 'currency' => 'Currency', + 'account_id' => 'Asset account', + 'budget_id' => 'Budget', + 'openingBalance' => 'Opening balance', + 'tagMode' => 'Tag mode', + 'tag_position' => 'Tag location', + 'virtualBalance' => 'Virtual balance', + 'targetamount' => 'Target amount', + 'accountRole' => 'Account role', + 'openingBalanceDate' => 'Opening balance date', + 'ccType' => 'Credit card payment plan', + 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Piggy bank', + 'returnHere' => 'Return here', + 'returnHereExplanation' => 'After storing, return here to create another one.', + 'returnHereUpdateExplanation' => 'After updating, return here.', + 'description' => 'Description', + 'expense_account' => 'Expense account', + 'revenue_account' => 'Revenue account', + 'decimal_places' => 'Decimal places', + 'exchange_rate_instruction' => 'Foreign currencies', + 'source_amount' => 'Amount (source)', + 'destination_amount' => 'Amount (destination)', + 'native_amount' => 'Native amount', + 'new_email_address' => 'New email address', + 'verification' => 'Verification', + 'api_key' => 'API key', + 'remember_me' => 'Remember me', 'source_account_asset' => 'Source account (asset account)', 'destination_account_expense' => 'Destination account (expense account)', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 66df306ae7..f19b66f0e0 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -37,6 +37,8 @@ return [ 'invalid_selection' => 'Your selection is invalid', 'belongs_user' => 'This value is invalid for this field.', 'at_least_one_transaction' => 'Need at least one transaction.', + 'at_least_one_repetition' => 'Need at least one repetition.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', @@ -115,6 +117,8 @@ return [ 'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.', 'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security', 'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Invalid account information', 'attributes' => [ 'email' => 'email address', 'description' => 'description', diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index d88455cfaa..1c098a5d87 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -89,16 +89,16 @@ {{ ExpandedForm.amountNoCurrency('amount', []) }} {# source account if withdrawal, or if transfer: #} - {{ ExpandedForm.activeAssetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.activeAssetAccountList('source_id', null, {label: trans('form.asset_source_account')}) }} {# source account name for deposits: #} - {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} + {{ ExpandedForm.text('source_name', null, {label: trans('form.revenue_account')}) }} {# destination if deposit or transfer: #} - {{ ExpandedForm.activeAssetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.activeAssetAccountList('destination_id', null, {label: trans('form.asset_destination_account')} ) }} {# destination account name for withdrawals #} - {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} + {{ ExpandedForm.text('destination_name', null, {label: trans('form.expense_account')}) }}
    diff --git a/resources/views/recurring/edit.twig b/resources/views/recurring/edit.twig index cba7f8e113..03a5c0b4a3 100644 --- a/resources/views/recurring/edit.twig +++ b/resources/views/recurring/edit.twig @@ -88,16 +88,16 @@ {{ ExpandedForm.amountNoCurrency('amount', array.transactions[0].amount) }} {# source account if withdrawal, or if transfer: #} - {{ ExpandedForm.assetAccountList('source_account_id', array.transactions[0].source_account_id, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.assetAccountList('source_id', array.transactions[0].source_id, {label: trans('form.asset_source_account')}) }} {# source account name for deposits: #} - {{ ExpandedForm.text('source_account_name', array.transactions[0].source_account_name, {label: trans('form.revenue_account')}) }} + {{ ExpandedForm.text('source_name', array.transactions[0].source_name, {label: trans('form.revenue_account')}) }} {# destination if deposit or transfer: #} - {{ ExpandedForm.assetAccountList('destination_account_id', array.transactions[0].destination_account_id, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.assetAccountList('destination_id', array.transactions[0].destination_id, {label: trans('form.asset_destination_account')} ) }} {# destination account name for withdrawals #} - {{ ExpandedForm.text('destination_account_name', array.transactions[0].destination_account_name, {label: trans('form.expense_account')}) }} + {{ ExpandedForm.text('destination_name', array.transactions[0].destination_name, {label: trans('form.expense_account')}) }}
    diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig index a4f39bd1d7..03fa6e5476 100644 --- a/resources/views/recurring/index.twig +++ b/resources/views/recurring/index.twig @@ -75,9 +75,9 @@ {% if null != rtt['foreign_amount'] %} ({{ formatAmountBySymbol(rtt['foreign_amount'],rtt['foreign_currency_symbol'],rtt['foreign_currency_dp']) }}), {% endif %} - {{ rtt['source_account_name'] }} + {{ rtt['source_name'] }} → - {{ rtt['destination_account_name'] }} + {{ rtt['destination_name'] }} {% endfor %} diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index 54936e929d..85703b3282 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -101,11 +101,11 @@ {% for transaction in array.transactions %} - - {{ transaction.source_account_name }} + + {{ transaction.source_name }} - - {{ transaction.destination_account_name }} + + {{ transaction.destination_name }} {{ formatAmountBySymbol(transaction.amount,transaction.currency_symbol,transaction.currency_dp) }} From c9356c123767a3415d39f35797f1d1653d2f1cfc Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 30 Jun 2018 05:21:21 +0200 Subject: [PATCH 088/134] Restructure code to rename a variable. --- app/Api/V1/Requests/PiggyBankRequest.php | 4 +- app/Api/V1/Requests/RecurrenceRequest.php | 26 ++++---- app/Handlers/Events/AutomationHandler.php | 2 + app/Helpers/Collector/JournalCollector.php | 16 ++--- app/Http/Controllers/ProfileController.php | 2 +- .../Recurring/DeleteController.php | 2 +- .../Controllers/Report/BalanceController.php | 2 +- .../Transaction/MassController.php | 8 +-- .../Transaction/SingleController.php | 62 +++++++++---------- .../Transaction/SplitController.php | 40 ++++++------ app/Http/Requests/JournalFormRequest.php | 30 ++++----- app/Http/Requests/MassEditJournalRequest.php | 10 +-- app/Http/Requests/RecurrenceFormRequest.php | 62 +++++++++---------- app/Http/Requests/SplitJournalFormRequest.php | 20 +++--- app/Import/Storage/ImportArrayStorage.php | 2 +- app/Models/Account.php | 2 +- app/Models/Bill.php | 10 +-- app/Models/Budget.php | 5 +- app/Models/Category.php | 2 +- app/Models/Preference.php | 2 +- app/Models/RuleGroup.php | 4 +- app/Models/Transaction.php | 2 +- app/Models/TransactionCurrency.php | 2 +- app/Models/TransactionJournal.php | 4 +- app/Models/TransactionType.php | 2 +- .../Account/FindAccountsTrait.php | 2 +- .../Budget/BudgetRepositoryInterface.php | 14 ++--- .../Journal/JournalRepositoryInterface.php | 1 + .../LinkType/LinkTypeRepositoryInterface.php | 14 ++--- .../PiggyBankRepositoryInterface.php | 14 +++-- app/Rules/IsValidAttachmentModel.php | 2 +- app/Services/Currency/FixerIOv2.php | 2 +- app/Transformers/AttachmentTransformer.php | 34 +++++----- app/Transformers/BudgetLimitTransformer.php | 1 + app/Transformers/RecurrenceTransformer.php | 26 ++++---- app/Transformers/RuleActionTransformer.php | 5 +- app/Transformers/RuleTransformer.php | 39 ++++++------ database/factories/ModelFactory.php | 8 +-- public/js/ff/transactions/mass/edit.js | 8 +-- public/js/ff/transactions/single/common.js | 20 +++--- public/js/ff/transactions/single/create.js | 47 +++++++------- public/js/ff/transactions/single/edit.js | 12 ++-- public/js/ff/transactions/split/edit.js | 4 +- resources/views/transactions/mass/edit.twig | 8 +-- .../views/transactions/single/create.twig | 8 +-- resources/views/transactions/single/edit.twig | 8 +-- resources/views/transactions/split/edit.twig | 4 +- .../Transaction/MassControllerTest.php | 4 +- .../Transaction/SingleControllerTest.php | 28 ++++----- .../Transaction/SplitControllerTest.php | 10 +-- 50 files changed, 325 insertions(+), 321 deletions(-) diff --git a/app/Api/V1/Requests/PiggyBankRequest.php b/app/Api/V1/Requests/PiggyBankRequest.php index 0e87661073..15a41bda01 100644 --- a/app/Api/V1/Requests/PiggyBankRequest.php +++ b/app/Api/V1/Requests/PiggyBankRequest.php @@ -78,8 +78,8 @@ class PiggyBankRequest extends Request case 'PUT': case 'PATCH': /** @var PiggyBank $piggyBank */ - $piggyBank = $this->route()->parameter('piggyBank'); - $rules['name'] = 'required|between:1,255|uniquePiggyBankForUser:' . $piggyBank->id; + $piggyBank = $this->route()->parameter('piggyBank'); + $rules['name'] = 'required|between:1,255|uniquePiggyBankForUser:' . $piggyBank->id; break; } diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php index 6c7925fed6..3dce86b02f 100644 --- a/app/Api/V1/Requests/RecurrenceRequest.php +++ b/app/Api/V1/Requests/RecurrenceRequest.php @@ -64,7 +64,7 @@ class RecurrenceRequest extends Request 'meta' => [ 'piggy_bank_id' => $this->integer('piggy_bank_id'), 'piggy_bank_name' => $this->string('piggy_bank_name'), - 'tags' => explode(',', $this->string('tags')), + 'tags' => explode(',', $this->string('tags')), ], 'transactions' => [], 'repetitions' => [], @@ -88,26 +88,26 @@ class RecurrenceRequest extends Request /** @var array $transaction */ foreach ($transactions as $transaction) { $return['transactions'][] = [ - 'amount' => $transaction['amount'], + 'amount' => $transaction['amount'], - 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, - 'currency_code' => $transaction['currency_code'] ?? null, + 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, + 'currency_code' => $transaction['currency_code'] ?? null, 'foreign_amount' => $transaction['foreign_amount'] ?? null, 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, - 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, - 'budget_name' => $transaction['budget_name'] ?? null, - 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, - 'category_name' => $transaction['category_name'] ?? null, + 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, + 'budget_name' => $transaction['budget_name'] ?? null, + 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, + 'category_name' => $transaction['category_name'] ?? null, - 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, - 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, - 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, - 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, + 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, + 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, + 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, + 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, - 'description' => $transaction['description'], + 'description' => $transaction['description'], ]; } diff --git a/app/Handlers/Events/AutomationHandler.php b/app/Handlers/Events/AutomationHandler.php index 697371afcf..3585e0ccf2 100644 --- a/app/Handlers/Events/AutomationHandler.php +++ b/app/Handlers/Events/AutomationHandler.php @@ -49,10 +49,12 @@ class AutomationHandler $user = $repository->findNull($event->userId); if (null === $user) { Log::debug('User is NULL'); + return true; } if ($event->journals->count() === 0) { Log::debug('No journals.'); + return true; } diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index a68b4cbec1..33d24fcaf7 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -321,6 +321,14 @@ class JournalCollector implements JournalCollectorInterface return $journals; } + /** + * @return EloquentBuilder + */ + public function getQuery(): EloquentBuilder + { + return $this->query; + } + /** * @return JournalCollectorInterface */ @@ -768,14 +776,6 @@ class JournalCollector implements JournalCollectorInterface return $this; } - /** - * @return EloquentBuilder - */ - public function getQuery(): EloquentBuilder - { - return $this->query; - } - /** * @param Collection $set * diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index c872ad7805..fc02ea3c55 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -110,7 +110,7 @@ class ProfileController extends Controller $image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret, 200); - return view('profile.code', compact('image','secret')); + return view('profile.code', compact('image', 'secret')); } /** diff --git a/app/Http/Controllers/Recurring/DeleteController.php b/app/Http/Controllers/Recurring/DeleteController.php index d0d995822d..6af99cae02 100644 --- a/app/Http/Controllers/Recurring/DeleteController.php +++ b/app/Http/Controllers/Recurring/DeleteController.php @@ -48,7 +48,7 @@ class DeleteController extends Controller // todo actual number. $journalsCreated = 5; - return view('recurring.delete', compact('recurrence', 'subTitle','journalsCreated')); + return view('recurring.delete', compact('recurrence', 'subTitle', 'journalsCreated')); } /** diff --git a/app/Http/Controllers/Report/BalanceController.php b/app/Http/Controllers/Report/BalanceController.php index 2945a15b13..3b31be87de 100644 --- a/app/Http/Controllers/Report/BalanceController.php +++ b/app/Http/Controllers/Report/BalanceController.php @@ -56,7 +56,7 @@ class BalanceController extends Controller $balance = $helper->getBalanceReport($accounts, $start, $end); - $result = view('reports.partials.balance', compact( 'balance'))->render(); + $result = view('reports.partials.balance', compact('balance'))->render(); $cache->store($result); return $result; diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 198cfc64ba..cd08cf2435 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -181,11 +181,11 @@ class MassController extends Controller if (null !== $journal) { // get optional fields: $what = strtolower($this->repository->getTransactionType($journal)); - $sourceAccountId = $request->get('source_account_id')[$journal->id] ?? null; + $sourceAccountId = $request->get('source_id')[$journal->id] ?? null; $currencyId = $request->get('transaction_currency_id')[$journal->id] ?? 1; - $sourceAccountName = $request->get('source_account_name')[$journal->id] ?? null; - $destAccountId = $request->get('destination_account_id')[$journal->id] ?? null; - $destAccountName = $request->get('destination_account_name')[$journal->id] ?? null; + $sourceAccountName = $request->get('source_name')[$journal->id] ?? null; + $destAccountId = $request->get('destination_id')[$journal->id] ?? null; + $destAccountName = $request->get('destination_name')[$journal->id] ?? null; $budgetId = (int)($request->get('budget_id')[$journal->id] ?? 0.0); $category = $request->get('category')[$journal->id]; $tags = $journal->tags->pluck('tag')->toArray(); diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 8d91025d75..ebbecb07c0 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -99,10 +99,10 @@ class SingleController extends Controller $preFilled = [ 'description' => $journal->description, - 'source_account_id' => $source->id, - 'source_account_name' => $source->name, - 'destination_account_id' => $destination->id, - 'destination_account_name' => $destination->name, + 'source_id' => $source->id, + 'source_name' => $source->name, + 'destination_id' => $destination->id, + 'destination_name' => $destination->name, 'amount' => $amount, 'source_amount' => $amount, 'destination_amount' => $foreignAmount, @@ -152,10 +152,10 @@ class SingleController extends Controller $source = (int)$request->get('source'); if (($what === 'withdrawal' || $what === 'transfer') && $source > 0) { - $preFilled['source_account_id'] = $source; + $preFilled['source_id'] = $source; } if ($what === 'deposit' && $source > 0) { - $preFilled['destination_account_id'] = $source; + $preFilled['destination_id'] = $source; } session()->put('preFilled', $preFilled); @@ -259,35 +259,35 @@ class SingleController extends Controller $pTransaction = $repository->getFirstPosTransaction($journal); $foreignCurrency = $pTransaction->foreignCurrency ?? $pTransaction->transactionCurrency; $preFilled = [ - 'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString() - 'interest_date' => $repository->getJournalDate($journal, 'interest_date'), - 'book_date' => $repository->getJournalDate($journal, 'book_date'), - 'process_date' => $repository->getJournalDate($journal, 'process_date'), - 'category' => $repository->getJournalCategoryName($journal), - 'budget_id' => $repository->getJournalBudgetId($journal), - 'tags' => implode(',', $repository->getTags($journal)), - 'source_account_id' => $sourceAccounts->first()->id, - 'source_account_name' => $sourceAccounts->first()->edit_name, - 'destination_account_id' => $destinationAccounts->first()->id, - 'destination_account_name' => $destinationAccounts->first()->edit_name, + 'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString() + 'interest_date' => $repository->getJournalDate($journal, 'interest_date'), + 'book_date' => $repository->getJournalDate($journal, 'book_date'), + 'process_date' => $repository->getJournalDate($journal, 'process_date'), + 'category' => $repository->getJournalCategoryName($journal), + 'budget_id' => $repository->getJournalBudgetId($journal), + 'tags' => implode(',', $repository->getTags($journal)), + 'source_id' => $sourceAccounts->first()->id, + 'source_name' => $sourceAccounts->first()->edit_name, + 'destination_id' => $destinationAccounts->first()->id, + 'destination_name' => $destinationAccounts->first()->edit_name, // new custom fields: - 'due_date' => $repository->getJournalDate($journal, 'due_date'), - 'payment_date' => $repository->getJournalDate($journal, 'payment_date'), - 'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'), - 'interal_reference' => $repository->getMetaField($journal, 'internal_reference'), - 'notes' => $repository->getNoteText($journal), + 'due_date' => $repository->getJournalDate($journal, 'due_date'), + 'payment_date' => $repository->getJournalDate($journal, 'payment_date'), + 'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'), + 'interal_reference' => $repository->getMetaField($journal, 'internal_reference'), + 'notes' => $repository->getNoteText($journal), // amount fields - 'amount' => $pTransaction->amount, - 'source_amount' => $pTransaction->amount, - 'native_amount' => $pTransaction->amount, - 'destination_amount' => $pTransaction->foreign_amount, - 'currency' => $pTransaction->transactionCurrency, - 'source_currency' => $pTransaction->transactionCurrency, - 'native_currency' => $pTransaction->transactionCurrency, - 'foreign_currency' => $foreignCurrency, - 'destination_currency' => $foreignCurrency, + 'amount' => $pTransaction->amount, + 'source_amount' => $pTransaction->amount, + 'native_amount' => $pTransaction->amount, + 'destination_amount' => $pTransaction->foreign_amount, + 'currency' => $pTransaction->transactionCurrency, + 'source_currency' => $pTransaction->transactionCurrency, + 'native_currency' => $pTransaction->transactionCurrency, + 'foreign_currency' => $foreignCurrency, + 'destination_currency' => $foreignCurrency, ]; // amounts for withdrawals and deposits: diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index d5276872f5..a0b0014576 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -184,30 +184,30 @@ class SplitController extends Controller $sourceAccounts = $this->repository->getJournalSourceAccounts($journal); $destinationAccounts = $this->repository->getJournalDestinationAccounts($journal); $array = [ - 'journal_description' => $request->old('journal_description', $journal->description), - 'journal_amount' => '0', - 'journal_foreign_amount' => '0', - 'sourceAccounts' => $sourceAccounts, - 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), - 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), - 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), - 'destinationAccounts' => $destinationAccounts, - 'what' => strtolower($this->repository->getTransactionType($journal)), - 'date' => $request->old('date', $this->repository->getJournalDate($journal, null)), - 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), + 'journal_description' => $request->old('journal_description', $journal->description), + 'journal_amount' => '0', + 'journal_foreign_amount' => '0', + 'sourceAccounts' => $sourceAccounts, + 'journal_source_id' => $request->old('journal_source_id', $sourceAccounts->first()->id), + 'journal_source_name' => $request->old('journal_source_name', $sourceAccounts->first()->name), + 'journal_destination_id' => $request->old('journal_destination_id', $destinationAccounts->first()->id), + 'destinationAccounts' => $destinationAccounts, + 'what' => strtolower($this->repository->getTransactionType($journal)), + 'date' => $request->old('date', $this->repository->getJournalDate($journal, null)), + 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), // all custom fields: - 'interest_date' => $request->old('interest_date', $this->repository->getMetaField($journal, 'interest_date')), - 'book_date' => $request->old('book_date', $this->repository->getMetaField($journal, 'book_date')), - 'process_date' => $request->old('process_date', $this->repository->getMetaField($journal, 'process_date')), - 'due_date' => $request->old('due_date', $this->repository->getMetaField($journal, 'due_date')), - 'payment_date' => $request->old('payment_date', $this->repository->getMetaField($journal, 'payment_date')), - 'invoice_date' => $request->old('invoice_date', $this->repository->getMetaField($journal, 'invoice_date')), - 'internal_reference' => $request->old('internal_reference', $this->repository->getMetaField($journal, 'internal_reference')), - 'notes' => $request->old('notes', $this->repository->getNoteText($journal)), + 'interest_date' => $request->old('interest_date', $this->repository->getMetaField($journal, 'interest_date')), + 'book_date' => $request->old('book_date', $this->repository->getMetaField($journal, 'book_date')), + 'process_date' => $request->old('process_date', $this->repository->getMetaField($journal, 'process_date')), + 'due_date' => $request->old('due_date', $this->repository->getMetaField($journal, 'due_date')), + 'payment_date' => $request->old('payment_date', $this->repository->getMetaField($journal, 'payment_date')), + 'invoice_date' => $request->old('invoice_date', $this->repository->getMetaField($journal, 'invoice_date')), + 'internal_reference' => $request->old('internal_reference', $this->repository->getMetaField($journal, 'internal_reference')), + 'notes' => $request->old('notes', $this->repository->getNoteText($journal)), // transactions. - 'transactions' => $this->getTransactionDataFromJournal($journal), + 'transactions' => $this->getTransactionDataFromJournal($journal), ]; // update transactions array with old request data. $array['transactions'] = $this->updateWithPrevious($array['transactions'], $request->old()); diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index eaa39797eb..d98c6f43ce 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -81,10 +81,10 @@ class JournalFormRequest extends Request 'budget_name' => null, 'category_id' => null, 'category_name' => $this->string('category'), - 'source_id' => $this->integer('source_account_id'), - 'source_name' => $this->string('source_account_name'), - 'destination_id' => $this->integer('destination_account_id'), - 'destination_name' => $this->string('destination_account_name'), + 'source_id' => $this->integer('source_id'), + 'source_name' => $this->string('source_name'), + 'destination_id' => $this->integer('destination_id'), + 'destination_name' => $this->string('destination_name'), 'foreign_currency_id' => null, 'foreign_currency_code' => null, 'foreign_amount' => null, @@ -161,11 +161,11 @@ class JournalFormRequest extends Request 'amount' => 'numeric|required|more:0', 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', 'category' => 'between:1,255|nullable', - 'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'source_account_name' => 'between:1,255|nullable', - 'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'destination_account_name' => 'between:1,255|nullable', - 'piggy_bank_id' => 'between:1,255|nullable', + 'source_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'source_name' => 'between:1,255|nullable', + 'destination_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'destination_name' => 'between:1,255|nullable', + 'piggy_bank_id' => 'numeric|nullable', // foreign currency amounts 'native_amount' => 'numeric|more:0|nullable', @@ -193,17 +193,17 @@ class JournalFormRequest extends Request { switch ($what) { case strtolower(TransactionType::WITHDRAWAL): - $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - $rules['destination_account_name'] = 'between:1,255|nullable'; + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['destination_name'] = 'between:1,255|nullable'; break; case strtolower(TransactionType::DEPOSIT): - $rules['source_account_name'] = 'between:1,255|nullable'; - $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['source_name'] = 'between:1,255|nullable'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; break; case strtolower(TransactionType::TRANSFER): // this may not work: - $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id'; - $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id'; + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_id'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_id'; break; default: diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index e82e243c80..cd82765c6e 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -45,11 +45,11 @@ class MassEditJournalRequest extends Request // fixed return [ - 'description.*' => 'required|min:1,max:255', - 'source_account_id.*' => 'numeric|belongsToUser:accounts,id', - 'destination_account_id.*' => 'numeric|belongsToUser:accounts,id', - 'revenue_account' => 'max:255', - 'expense_account' => 'max:255', + 'description.*' => 'required|min:1,max:255', + 'source_id.*' => 'numeric|belongsToUser:accounts,id', + 'destination_id.*' => 'numeric|belongsToUser:accounts,id', + 'revenue_account' => 'max:255', + 'expense_account' => 'max:255', ]; } } diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index c05c6ea0a8..ad18aa3cf1 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -104,16 +104,16 @@ class RecurrenceFormRequest extends Request default: throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); case 'withdrawal': - $return['transactions'][0]['source_account_id'] = $this->integer('source_account_id'); - $return['transactions'][0]['destination_account_name'] = $this->string('destination_account_name'); + $return['transactions'][0]['source_id'] = $this->integer('source_id'); + $return['transactions'][0]['destination_name'] = $this->string('destination_name'); break; case 'deposit': - $return['transactions'][0]['source_account_name'] = $this->string('source_account_name'); - $return['transactions'][0]['destination_account_id'] = $this->integer('destination_account_id'); + $return['transactions'][0]['source_name'] = $this->string('source_name'); + $return['transactions'][0]['destination_id'] = $this->integer('destination_id'); break; case 'transfer': - $return['transactions'][0]['source_account_id'] = $this->integer('source_account_id'); - $return['transactions'][0]['destination_account_id'] = $this->integer('destination_account_id'); + $return['transactions'][0]['source_id'] = $this->integer('source_id'); + $return['transactions'][0]['destination_id'] = $this->integer('destination_id'); break; } @@ -131,34 +131,34 @@ class RecurrenceFormRequest extends Request $tomorrow->addDay(); $rules = [ // mandatory info for recurrence. - 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', - 'first_date' => 'required|date|after:' . $today->format('Y-m-d'), - 'repetition_type' => ['required', new ValidRecurrenceRepetitionValue, new ValidRecurrenceRepetitionType, 'between:1,20'], - 'skip' => 'required|numeric|between:0,31', + 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', + 'first_date' => 'required|date|after:' . $today->format('Y-m-d'), + 'repetition_type' => ['required', new ValidRecurrenceRepetitionValue, new ValidRecurrenceRepetitionType, 'between:1,20'], + 'skip' => 'required|numeric|between:0,31', // optional for recurrence: - 'recurring_description' => 'between:0,65000', - 'active' => 'numeric|between:0,1', - 'apply_rules' => 'numeric|between:0,1', + 'recurring_description' => 'between:0,65000', + 'active' => 'numeric|between:0,1', + 'apply_rules' => 'numeric|between:0,1', // mandatory for transaction: - 'transaction_description' => 'required|between:1,255', - 'transaction_type' => 'required|in:withdrawal,deposit,transfer', - 'transaction_currency_id' => 'required|exists:transaction_currencies,id', - 'amount' => 'numeric|required|more:0', + 'transaction_description' => 'required|between:1,255', + 'transaction_type' => 'required|in:withdrawal,deposit,transfer', + 'transaction_currency_id' => 'required|exists:transaction_currencies,id', + 'amount' => 'numeric|required|more:0', // mandatory account info: - 'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'source_account_name' => 'between:1,255|nullable', - 'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'destination_account_name' => 'between:1,255|nullable', + 'source_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'source_name' => 'between:1,255|nullable', + 'destination_id' => 'numeric|belongsToUser:accounts,id|nullable', + 'destination_name' => 'between:1,255|nullable', // foreign amount data: - 'foreign_amount' => 'nullable|more:0', + 'foreign_amount' => 'nullable|more:0', // optional fields: - 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', - 'category' => 'between:1,255|nullable', - 'tags' => 'between:1,255|nullable', + 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', + 'category' => 'between:1,255|nullable', + 'tags' => 'between:1,255|nullable', ]; if ($this->integer('foreign_currency_id') > 0) { $rules['foreign_currency_id'] = 'exists:transaction_currencies,id'; @@ -181,17 +181,17 @@ class RecurrenceFormRequest extends Request // switchc on type to expand rules for source and destination accounts: switch ($this->string('transaction_type')) { case strtolower(TransactionType::WITHDRAWAL): - $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - $rules['destination_account_name'] = 'between:1,255|nullable'; + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['destination_name'] = 'between:1,255|nullable'; break; case strtolower(TransactionType::DEPOSIT): - $rules['source_account_name'] = 'between:1,255|nullable'; - $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; + $rules['source_name'] = 'between:1,255|nullable'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; break; case strtolower(TransactionType::TRANSFER): // this may not work: - $rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id'; - $rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id'; + $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_id'; + $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_id'; break; default: diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php index bc989b5f7a..5e1a4d062f 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -64,16 +64,16 @@ class SplitJournalFormRequest extends Request foreach ($this->get('transactions') as $index => $transaction) { switch ($data['type']) { case 'withdrawal': - $sourceId = $this->integer('journal_source_account_id'); + $sourceId = $this->integer('journal_source_id'); $destinationName = $transaction['destination_name'] ?? ''; break; case 'deposit': $sourceName = $transaction['source_name'] ?? ''; - $destinationId = $this->integer('journal_destination_account_id'); + $destinationId = $this->integer('journal_destination_id'); break; case 'transfer': - $sourceId = $this->integer('journal_source_account_id'); - $destinationId = $this->integer('journal_destination_account_id'); + $sourceId = $this->integer('journal_source_id'); + $destinationId = $this->integer('journal_destination_id'); break; } $foreignAmount = $transaction['foreign_amount'] ?? null; @@ -112,20 +112,20 @@ class SplitJournalFormRequest extends Request 'what' => 'required|in:withdrawal,deposit,transfer', 'journal_description' => 'required|between:1,255', 'id' => 'numeric|belongsToUser:transaction_journals,id', - 'journal_source_account_id' => 'numeric|belongsToUser:accounts,id', - 'journal_source_account_name.*' => 'between:1,255', + 'journal_source_id' => 'numeric|belongsToUser:accounts,id', + 'journal_source_name.*' => 'between:1,255', 'journal_currency_id' => 'required|exists:transaction_currencies,id', 'date' => 'required|date', 'interest_date' => 'date|nullable', 'book_date' => 'date|nullable', 'process_date' => 'date|nullable', 'transactions.*.transaction_description' => 'required|between:1,255', - 'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id', + 'transactions.*.destination_id' => 'numeric|belongsToUser:accounts,id', 'transactions.*.destination_name' => 'between:1,255|nullable', 'transactions.*.amount' => 'required|numeric', 'transactions.*.budget_id' => 'belongsToUser:budgets,id', 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.piggy_bank_id' => 'between:1,255|nullable', + 'transactions.*.piggy_bank_id' => 'numeric|nullable', ]; } @@ -155,8 +155,8 @@ class SplitJournalFormRequest extends Request /** @var array $array */ foreach ($transactions as $array) { if ($array['destination_id'] !== null && $array['source_id'] !== null && $array['destination_id'] === $array['source_id']) { - $validator->errors()->add('journal_source_account_id', trans('validation.source_equals_destination')); - $validator->errors()->add('journal_destination_account_id', trans('validation.source_equals_destination')); + $validator->errors()->add('journal_source_id', trans('validation.source_equals_destination')); + $validator->errors()->add('journal_destination_id', trans('validation.source_equals_destination')); } } diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index c7edae2e6f..15d5ae58fa 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -415,7 +415,7 @@ class ImportArrayStorage // store the journal. try { $journal = $this->journalRepos->store($store); - } catch(FireflyException $e) { + } catch (FireflyException $e) { Log::error($e->getMessage()); Log::error($e->getTraceAsString()); $this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage())); diff --git a/app/Models/Account.php b/app/Models/Account.php index 3c2c4f761f..adcfb61dc7 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -44,7 +44,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property AccountType $accountType * @property bool $active * @property string $virtual_balance - * @property User $user + * @property User $user */ class Account extends Model { diff --git a/app/Models/Bill.php b/app/Models/Bill.php index 81da6f180d..6693972b20 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -45,11 +45,11 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property TransactionCurrency $transactionCurrency * @property Carbon $created_at * @property Carbon $updated_at - * @property Carbon $date - * @property string $repeat_freq - * @property int $skip - * @property bool $automatch - * @property User $user + * @property Carbon $date + * @property string $repeat_freq + * @property int $skip + * @property bool $automatch + * @property User $user */ class Bill extends Model { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 503495b411..d3c0ff820d 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -31,9 +31,10 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Budget. - * @property int $id + * + * @property int $id * @property string $name - * @property bool $active + * @property bool $active */ class Budget extends Model { diff --git a/app/Models/Category.php b/app/Models/Category.php index 7bc567f2b7..5f06f91fbe 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -33,7 +33,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * Class Category. * * @property string $name - * @property int $id + * @property int $id */ class Category extends Model { diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 2aae7b4a6e..19a337bb5c 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -39,7 +39,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $name * @property Carbon $updated_at * @property Carbon $created_at - * @property int $id + * @property int $id */ class Preference extends Model { diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index 8bbbc74e8b..6c607e49e0 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -37,8 +37,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon $updated_at * @property string $title * @property string $text - * @property int $id - * @property int $order + * @property int $id + * @property int $order */ class RuleGroup extends Model { diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 4742467e29..d008f373e9 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -73,7 +73,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool $is_split * @property int $attachmentCount * @property int $transaction_currency_id - * @property int $foreign_currency_id + * @property int $foreign_currency_id */ class Transaction extends Model { diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index 5948f38811..26e7d9fa0c 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -32,7 +32,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $code * @property string $symbol * @property int $decimal_places - * @property int $id + * @property int $id * */ class TransactionCurrency extends Model diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 28da385b2f..760f4831da 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -39,8 +39,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class TransactionJournal. * - * @property User $user - * @property int $bill_id + * @property User $user + * @property int $bill_id * @property Collection $categories */ class TransactionJournal extends Model diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index 597dc1b612..4c9bc1f04a 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -30,7 +30,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * Class TransactionType. * * @property string $type - * @property int $id + * @property int $id */ class TransactionType extends Model { diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php index 347d458d4c..8cfadbe8dc 100644 --- a/app/Repositories/Account/FindAccountsTrait.php +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -158,7 +158,7 @@ trait FindAccountsTrait Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id)); return $account; - } else{ + } else { Log::debug(sprintf('"%s" does not equal "%s"', $account->name, $name)); } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index d08e406a3d..d8e69f2fef 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -250,6 +250,13 @@ interface BudgetRepositoryInterface */ public function store(array $data): Budget; + /** + * @param array $data + * + * @return BudgetLimit + */ + public function storeBudgetLimit(array $data): BudgetLimit; + /** * @param Budget $budget * @param array $data @@ -274,13 +281,6 @@ interface BudgetRepositoryInterface */ public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit; - /** - * @param array $data - * - * @return BudgetLimit - */ - public function storeBudgetLimit(array $data): BudgetLimit; - /** * @param Budget $budget * @param Carbon $start diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index dc6bfe9b26..e0884a3858 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -326,6 +326,7 @@ interface JournalRepositoryInterface /** * @param array $data + * * @throws FireflyException * @return TransactionJournal */ diff --git a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php index 9aa8c03757..617cb45b59 100644 --- a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php +++ b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php @@ -62,13 +62,6 @@ interface LinkTypeRepositoryInterface */ public function find(int $id): LinkType; - /** - * @param int $id - * - * @return LinkType|null - */ - public function findNull(int $id): ?LinkType; - /** * Find link type by name. * @@ -88,6 +81,13 @@ interface LinkTypeRepositoryInterface */ public function findLink(TransactionJournal $one, TransactionJournal $two): bool; + /** + * @param int $id + * + * @return LinkType|null + */ + public function findNull(int $id): ?LinkType; + /** * See if such a link already exists (and get it). * diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index f1ee19124a..4b4d7d062e 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -102,17 +102,12 @@ interface PiggyBankRepositoryInterface /** * @param int $piggyBankid + * * @deprecated * @return PiggyBank */ public function find(int $piggyBankid): PiggyBank; - /** - * @param int $piggyBankId - * @return PiggyBank|null - */ - public function findNull(int $piggyBankId): ?PiggyBank; - /** * Find by name or return NULL. * @@ -122,6 +117,13 @@ interface PiggyBankRepositoryInterface */ public function findByName(string $name): ?PiggyBank; + /** + * @param int $piggyBankId + * + * @return PiggyBank|null + */ + public function findNull(int $piggyBankId): ?PiggyBank; + /** * Get current amount saved in piggy bank. * diff --git a/app/Rules/IsValidAttachmentModel.php b/app/Rules/IsValidAttachmentModel.php index db2959c7c8..954e5a2151 100644 --- a/app/Rules/IsValidAttachmentModel.php +++ b/app/Rules/IsValidAttachmentModel.php @@ -79,7 +79,7 @@ class IsValidAttachmentModel implements Rule /** @var JournalRepositoryInterface $repository */ $repository = app(JournalRepositoryInterface::class); $repository->setUser($user); - $result = $repository->findNull((int)$value); + $result = $repository->findNull((int)$value); return null !== $result; break; diff --git a/app/Services/Currency/FixerIOv2.php b/app/Services/Currency/FixerIOv2.php index b5cf3cde09..b224e65de4 100644 --- a/app/Services/Currency/FixerIOv2.php +++ b/app/Services/Currency/FixerIOv2.php @@ -49,7 +49,7 @@ class FixerIOv2 implements ExchangeRateInterface public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate { // create new exchange rate with default values. - $rate = 0; + $rate = 0; $exchangeRate = new CurrencyExchangeRate; $exchangeRate->user()->associate($this->user); $exchangeRate->fromCurrency()->associate($fromCurrency); diff --git a/app/Transformers/AttachmentTransformer.php b/app/Transformers/AttachmentTransformer.php index f1c6ac01bd..d65d4db907 100644 --- a/app/Transformers/AttachmentTransformer.php +++ b/app/Transformers/AttachmentTransformer.php @@ -25,10 +25,10 @@ namespace FireflyIII\Transformers; use FireflyIII\Models\Attachment; +use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; -use League\Fractal\Resource\Collection as FractalCollection; /** * Class AttachmentTransformer @@ -40,13 +40,13 @@ class AttachmentTransformer extends TransformerAbstract * * @var array */ - protected $availableIncludes = ['user','notes']; + protected $availableIncludes = ['user', 'notes']; /** * List of resources to automatically include * * @var array */ - protected $defaultIncludes = ['user','notes']; + protected $defaultIncludes = ['user', 'notes']; /** @var ParameterBag */ protected $parameters; @@ -63,20 +63,6 @@ class AttachmentTransformer extends TransformerAbstract $this->parameters = $parameters; } - /** - * Attach the user. - * - * @codeCoverageIgnore - * - * @param Attachment $attachment - * - * @return Item - */ - public function includeUser(Attachment $attachment): Item - { - return $this->item($attachment->user, new UserTransformer($this->parameters), 'users'); - } - /** * Attach the notes. * @@ -91,6 +77,20 @@ class AttachmentTransformer extends TransformerAbstract return $this->collection($attachment->notes, new NoteTransformer($this->parameters), 'notes'); } + /** + * Attach the user. + * + * @codeCoverageIgnore + * + * @param Attachment $attachment + * + * @return Item + */ + public function includeUser(Attachment $attachment): Item + { + return $this->item($attachment->user, new UserTransformer($this->parameters), 'users'); + } + /** * Transform attachment. * diff --git a/app/Transformers/BudgetLimitTransformer.php b/app/Transformers/BudgetLimitTransformer.php index 462290fdb9..90e858bcdc 100644 --- a/app/Transformers/BudgetLimitTransformer.php +++ b/app/Transformers/BudgetLimitTransformer.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Transformers; + use FireflyIII\Models\BudgetLimit; use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index 0c23487c08..f0d83c5a21 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -157,19 +157,19 @@ class RecurrenceTransformer extends TransformerAbstract /** @var RecurrenceTransaction $transaction */ foreach ($recurrence->recurrenceTransactions as $transaction) { $transactionArray = [ - 'currency_id' => $transaction->transaction_currency_id, - 'currency_code' => $transaction->transactionCurrency->code, - 'currency_symbol' => $transaction->transactionCurrency->symbol, - 'currency_dp' => $transaction->transactionCurrency->decimal_places, - 'foreign_currency_id' => $transaction->foreign_currency_id, - 'source_account_id' => $transaction->source_account_id, - 'source_account_name' => $transaction->sourceAccount->name, - 'destination_account_id' => $transaction->destination_account_id, - 'destination_account_name' => $transaction->destinationAccount->name, - 'amount' => $transaction->amount, - 'foreign_amount' => $transaction->foreign_amount, - 'description' => $transaction->description, - 'meta' => [], + 'currency_id' => $transaction->transaction_currency_id, + 'currency_code' => $transaction->transactionCurrency->code, + 'currency_symbol' => $transaction->transactionCurrency->symbol, + 'currency_dp' => $transaction->transactionCurrency->decimal_places, + 'foreign_currency_id' => $transaction->foreign_currency_id, + 'source_id' => $transaction->source_id, + 'source_name' => $transaction->sourceAccount->name, + 'destination_id' => $transaction->destination_id, + 'destination_name' => $transaction->destinationAccount->name, + 'amount' => $transaction->amount, + 'foreign_amount' => $transaction->foreign_amount, + 'description' => $transaction->description, + 'meta' => [], ]; if (null !== $transaction->foreign_currency_id) { $transactionArray['foreign_currency_code'] = $transaction->foreignCurrency->code; diff --git a/app/Transformers/RuleActionTransformer.php b/app/Transformers/RuleActionTransformer.php index a0a8401716..b8b664dd49 100644 --- a/app/Transformers/RuleActionTransformer.php +++ b/app/Transformers/RuleActionTransformer.php @@ -25,7 +25,6 @@ namespace FireflyIII\Transformers; use FireflyIII\Models\RuleAction; -use FireflyIII\Models\RuleTrigger; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; @@ -75,8 +74,8 @@ class RuleActionTransformer extends TransformerAbstract 'id' => (int)$ruleAction->id, 'updated_at' => $ruleAction->updated_at->toAtomString(), 'created_at' => $ruleAction->created_at->toAtomString(), - 'action_type' => $ruleAction->action_type, - 'action_value' => $ruleAction->action_value, + 'action_type' => $ruleAction->action_type, + 'action_value' => $ruleAction->action_value, 'order' => $ruleAction->order, 'active' => $ruleAction->active, 'stop_processing' => $ruleAction->stop_processing, diff --git a/app/Transformers/RuleTransformer.php b/app/Transformers/RuleTransformer.php index 35db378eeb..89f8353aa0 100644 --- a/app/Transformers/RuleTransformer.php +++ b/app/Transformers/RuleTransformer.php @@ -25,10 +25,10 @@ namespace FireflyIII\Transformers; use FireflyIII\Models\Rule; +use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\TransformerAbstract; use Symfony\Component\HttpFoundation\ParameterBag; -use League\Fractal\Resource\Collection as FractalCollection; /** * Class RuleTransformer @@ -68,8 +68,22 @@ class RuleTransformer extends TransformerAbstract * * @return FractalCollection */ - public function includeRuleTriggers(Rule $rule): FractalCollection { - return $this->collection($rule->ruleTriggers, new RuleTriggerTransformer($this->parameters), 'rule_triggers'); + public function includeRuleActions(Rule $rule): FractalCollection + { + return $this->collection($rule->ruleActions, new RuleActionTransformer($this->parameters), 'rule_actions'); + } + + /** + * Include the rule group. + * + * @param Rule $rule + * + * @codeCoverageIgnore + * @return Item + */ + public function includeRuleGroup(Rule $rule): Item + { + return $this->item($rule->ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); } /** @@ -77,8 +91,9 @@ class RuleTransformer extends TransformerAbstract * * @return FractalCollection */ - public function includeRuleActions(Rule $rule): FractalCollection { - return $this->collection($rule->ruleActions, new RuleActionTransformer($this->parameters), 'rule_actions'); + public function includeRuleTriggers(Rule $rule): FractalCollection + { + return $this->collection($rule->ruleTriggers, new RuleTriggerTransformer($this->parameters), 'rule_triggers'); } /** @@ -94,20 +109,6 @@ class RuleTransformer extends TransformerAbstract return $this->item($rule->user, new UserTransformer($this->parameters), 'users'); } - - /** - * Include the rule group. - * - * @param Rule $rule - * - * @codeCoverageIgnore - * @return Item - */ - public function includeRuleGroup(Rule $rule): Item - { - return $this->item($rule->ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups'); - } - /** * Transform the rule. * diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 04a90e6a2a..668e584a90 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -245,13 +245,13 @@ $factory->define( 'transaction_amount' => (string)$faker->randomFloat(2, -100, 100), 'destination_amount' => (string)$faker->randomFloat(2, -100, 100), 'opposing_account_id' => $faker->numberBetween(1, 10), - 'source_account_id' => $faker->numberBetween(1, 10), + 'source_id' => $faker->numberBetween(1, 10), 'opposing_account_name' => $faker->words(3, true), 'description' => $faker->words(3, true), - 'source_account_name' => $faker->words(3, true), - 'destination_account_id' => $faker->numberBetween(1, 10), + 'source_name' => $faker->words(3, true), + 'destination_id' => $faker->numberBetween(1, 10), 'date' => new Carbon, - 'destination_account_name' => $faker->words(3, true), + 'destination_name' => $faker->words(3, true), 'amount' => (string)$faker->randomFloat(2, -100, 100), 'budget_id' => 0, 'category' => $faker->words(3, true), diff --git a/public/js/ff/transactions/mass/edit.js b/public/js/ff/transactions/mass/edit.js index b267ab1bd0..9f98362fed 100644 --- a/public/js/ff/transactions/mass/edit.js +++ b/public/js/ff/transactions/mass/edit.js @@ -24,16 +24,16 @@ $(document).ready(function () { "use strict"; // destination account names: - if ($('input[name^="destination_account_name["]').length > 0) { + if ($('input[name^="destination_name["]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name^="destination_account_name["]').typeahead({source: data, autoSelect: false}); + $('input[name^="destination_name["]').typeahead({source: data, autoSelect: false}); }); } // source account name - if ($('input[name^="source_account_name["]').length > 0) { + if ($('input[name^="source_name["]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name^="source_account_name["]').typeahead({source: data, autoSelect: false}); + $('input[name^="source_name["]').typeahead({source: data, autoSelect: false}); }); } diff --git a/public/js/ff/transactions/single/common.js b/public/js/ff/transactions/single/common.js index 47962ab6a8..122a71d591 100644 --- a/public/js/ff/transactions/single/common.js +++ b/public/js/ff/transactions/single/common.js @@ -65,15 +65,15 @@ function setCommonAutocomplete() { }); - if ($('input[name="destination_account_name"]').length > 0) { + if ($('input[name="destination_name"]').length > 0) { $.getJSON('json/expense-accounts').done(function (data) { - $('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="destination_name"]').typeahead({source: data, autoSelect: false}); }); } - if ($('input[name="source_account_name"]').length > 0) { + if ($('input[name="source_name"]').length > 0) { $.getJSON('json/revenue-accounts').done(function (data) { - $('input[name="source_account_name"]').typeahead({source: data, autoSelect: false}); + $('input[name="source_name"]').typeahead({source: data, autoSelect: false}); }); } @@ -160,8 +160,8 @@ function updateNativeAmount(data) { * Instructions for transfers */ function getTransferExchangeInstructions() { - var sourceAccount = $('select[name="source_account_id"]').val(); - var destAccount = $('select[name="destination_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; var destinationCurrency = accountInfo[destAccount].preferredCurrency; @@ -180,8 +180,8 @@ function validateCurrencyForTransfer() { return; } $('#source_amount_holder').show(); - var sourceAccount = $('select[name="source_account_id"]').val(); - var destAccount = $('select[name="destination_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; var sourceSymbol = currencyInfo[sourceCurrency].symbol; var destinationCurrency = accountInfo[destAccount].preferredCurrency; @@ -208,8 +208,8 @@ function validateCurrencyForTransfer() { * */ function convertSourceToDestination() { - var sourceAccount = $('select[name="source_account_id"]').val(); - var destAccount = $('select[name="destination_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; var destinationCurrency = accountInfo[destAccount].preferredCurrency; diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index b8729f2582..2f2294196c 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -38,20 +38,17 @@ $(document).ready(function () { // when user changes source account or destination, native currency may be different. - $('select[name="source_account_id"]').on('change', function() { + $('select[name="source_id"]').on('change', function() { selectsDifferentSource(); // do something for transfers: validateCurrencyForTransfer(); }); - $('select[name="destination_account_id"]').on('change', function() { + $('select[name="destination_id"]').on('change', function() { selectsDifferentDestination(); // do something for transfers: validateCurrencyForTransfer(); }); - //$('select[name="source_account_id"]').on('change', updateNativeCurrency); - //$('select[name="destination_account_id"]').on('change', updateNativeCurrency); - // convert foreign currency to native currency (when input changes, exchange rate) $('#ffInput_amount').on('change', convertForeignToNative); @@ -74,7 +71,7 @@ function selectsDifferentSource() { return; } // store original currency ID of the selected account in a separate var: - var sourceId = $('select[name="source_account_id"]').val(); + var sourceId = $('select[name="source_id"]').val(); var sourceCurrency = accountInfo[sourceId].preferredCurrency; $('input[name="source_account_currency"]').val(sourceCurrency); console.log('selectsDifferenctSource(): Set source account currency to ' + sourceCurrency); @@ -82,7 +79,7 @@ function selectsDifferentSource() { // change input thing: $('.currency-option[data-id="' + sourceCurrency + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); - $('select[name="source_account_id"]').focus(); + $('select[name="source_id"]').focus(); } /** @@ -96,7 +93,7 @@ function selectsDifferentDestination() { return; } // store original currency ID of the selected account in a separate var: - var destinationId = $('select[name="destination_account_id"]').val(); + var destinationId = $('select[name="destination_id"]').val(); var destinationCurrency = accountInfo[destinationId].preferredCurrency; $('input[name="destination_account_currency"]').val(destinationCurrency); console.log('selectsDifferentDestination(): Set destinationId account currency to ' + destinationCurrency); @@ -104,7 +101,7 @@ function selectsDifferentDestination() { // change input thing: $('.currency-option[data-id="' + destinationCurrency + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); - $('select[name="destination_account_id"]').focus(); + $('select[name="destination_id"]').focus(); } @@ -153,19 +150,19 @@ function updateForm() { $('input[name="what"]').val(what); - var destName = $('#ffInput_destination_account_name'); - var srcName = $('#ffInput_source_account_name'); + var destName = $('#ffInput_destination_name'); + var srcName = $('#ffInput_source_name'); switch (what) { case 'withdrawal': // show source_id and dest_name - document.getElementById('source_account_id_holder').style.display = 'block'; - document.getElementById('destination_account_name_holder').style.display = 'block'; + document.getElementById('source_id_holder').style.display = 'block'; + document.getElementById('destination_name_holder').style.display = 'block'; // hide others: - document.getElementById('source_account_name_holder').style.display = 'none'; - document.getElementById('destination_account_id_holder').style.display = 'none'; + document.getElementById('source_name_holder').style.display = 'none'; + document.getElementById('destination_id_holder').style.display = 'none'; document.getElementById('budget_id_holder').style.display = 'block'; // hide piggy bank: @@ -185,12 +182,12 @@ function updateForm() { break; case 'deposit': // show source_name and dest_id: - document.getElementById('source_account_name_holder').style.display = 'block'; - document.getElementById('destination_account_id_holder').style.display = 'block'; + document.getElementById('source_name_holder').style.display = 'block'; + document.getElementById('destination_id_holder').style.display = 'block'; // hide others: - document.getElementById('source_account_id_holder').style.display = 'none'; - document.getElementById('destination_account_name_holder').style.display = 'none'; + document.getElementById('source_id_holder').style.display = 'none'; + document.getElementById('destination_name_holder').style.display = 'none'; // hide budget document.getElementById('budget_id_holder').style.display = 'none'; @@ -212,12 +209,12 @@ function updateForm() { break; case 'transfer': // show source_id and dest_id: - document.getElementById('source_account_id_holder').style.display = 'block'; - document.getElementById('destination_account_id_holder').style.display = 'block'; + document.getElementById('source_id_holder').style.display = 'block'; + document.getElementById('destination_id_holder').style.display = 'block'; // hide others: - document.getElementById('source_account_name_holder').style.display = 'none'; - document.getElementById('destination_account_name_holder').style.display = 'none'; + document.getElementById('source_name_holder').style.display = 'none'; + document.getElementById('destination_name_holder').style.display = 'none'; // hide budget document.getElementById('budget_id_holder').style.display = 'none'; @@ -288,10 +285,10 @@ function clickButton(e) { */ function getAccountId() { if (what === "withdrawal") { - return $('select[name="source_account_id"]').val(); + return $('select[name="source_id"]').val(); } if (what === "deposit" || what === "transfer") { - return $('select[name="destination_account_id"]').val(); + return $('select[name="destination_id"]').val(); } return undefined; } diff --git a/public/js/ff/transactions/single/edit.js b/public/js/ff/transactions/single/edit.js index cf5248e021..30ce29ea8f 100644 --- a/public/js/ff/transactions/single/edit.js +++ b/public/js/ff/transactions/single/edit.js @@ -38,12 +38,12 @@ $(document).ready(function () { $('#ffInput_amount').on('change', convertForeignToNative); // respond to transfer changes: - $('#ffInput_source_account_id').on('change', function () { + $('#ffInput_source_id').on('change', function () { validateCurrencyForTransfer(); // update the two source account currency ID fields (initial value): initCurrencyIdValues(); }); - $('#ffInput_destination_account_id').on('change', function () { + $('#ffInput_destination_id').on('change', function () { validateCurrencyForTransfer(); // update the two source account currency ID fields (initial value): initCurrencyIdValues(); @@ -77,9 +77,9 @@ function initCurrencyIdValues() { $('input[name="destination_account_currency"]').val(currencyId); return; } - var sourceAccount = $('select[name="source_account_id"]').val(); + var sourceAccount = $('select[name="source_id"]').val(); console.log('Source account is ' + sourceAccount); - var destAccount = $('select[name="destination_account_id"]').val(); + var destAccount = $('select[name="destination_id"]').val(); console.log('Destination account is ' + destAccount); var sourceCurrency = parseInt(accountInfo[sourceAccount].preferredCurrency); @@ -134,10 +134,10 @@ function updateInitialPage() { function getAccountId() { console.log('in getAccountId()'); if (journal.transaction_type.type === "Withdrawal") { - return $('select[name="source_account_id"]').val(); + return $('select[name="source_id"]').val(); } if (journal.transaction_type.type === "Deposit") { - return $('select[name="destination_account_id"]').val(); + return $('select[name="destination_id"]').val(); } alert('Cannot handle ' + journal.transaction_type.type); diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index 61af5fc050..f3178e0f4e 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -191,12 +191,12 @@ function resetDivSplits() { var input = $(v); input.attr('name', 'transactions[' + i + '][transaction_description]'); }); - // ends with ][destination_account_name] + // ends with ][destination_name] $.each($('input[name$="][destination_name]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][destination_name]'); }); - // ends with ][source_account_name] + // ends with ][source_name] $.each($('input[name$="][source_name]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][source_name]'); diff --git a/resources/views/transactions/mass/edit.twig b/resources/views/transactions/mass/edit.twig index d6fcaff658..fa863e3847 100644 --- a/resources/views/transactions/mass/edit.twig +++ b/resources/views/transactions/mass/edit.twig @@ -70,7 +70,7 @@ {# SOURCE ACCOUNT ID FOR TRANSFER OR WITHDRAWAL #} {% if transaction.type == 'Transfer' or transaction.type == 'Withdrawal' %} - {% for account in accounts %} @@ -79,13 +79,13 @@ {% else %} {# SOURCE ACCOUNT NAME FOR DEPOSIT #} + name="source_name[{{ transaction.journal_id }}]" type="text" value="{% if transaction.source_type != 'Cash account' %}{{ transaction.source_name }}{% endif %}"> {% endif %} {% if transaction.type == 'Transfer' or transaction.type == 'Deposit' %} {# DESTINATION ACCOUNT NAME FOR TRANSFER AND DEPOSIT #} - {% for account in accounts %} @@ -95,7 +95,7 @@ {# DESTINATION ACCOUNT NAME FOR EXPENSE #} {% endif %} diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index 63fa0fc6cc..82da51a107 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -33,16 +33,16 @@ {{ ExpandedForm.text('description') }} {# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #} - {{ ExpandedForm.activeAssetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.activeAssetAccountList('source_id', null, {label: trans('form.asset_source_account')}) }} {# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #} - {{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }} + {{ ExpandedForm.text('source_name', null, {label: trans('form.revenue_account')}) }} {# FREE FORMAT DESTINATION ACCOUNT ONLY FOR EXPENSES #} - {{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }} + {{ ExpandedForm.text('destination_name', null, {label: trans('form.expense_account')}) }} {# SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS #} - {{ ExpandedForm.activeAssetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.activeAssetAccountList('destination_id', null, {label: trans('form.asset_destination_account')} ) }} {# ALWAYS SHOW AMOUNT #} {{ ExpandedForm.amount('amount') }} diff --git a/resources/views/transactions/single/edit.twig b/resources/views/transactions/single/edit.twig index 11fc096c5b..92b27629f6 100644 --- a/resources/views/transactions/single/edit.twig +++ b/resources/views/transactions/single/edit.twig @@ -39,22 +39,22 @@ {# SELECTABLE SOURCE ACCOUNT ONLY FOR WITHDRAWALS AND TRANSFERS #} {% if what == 'transfer' or what == 'withdrawal' %} - {{ ExpandedForm.assetAccountList('source_account_id', data.source_account_id, {label: trans('form.asset_source_account')}) }} + {{ ExpandedForm.assetAccountList('source_id', data.source_id, {label: trans('form.asset_source_account')}) }} {% endif %} {# FREE FORMAT SOURCE ACCOUNT ONLY FOR DEPOSITS #} {% if what == 'deposit' %} - {{ ExpandedForm.text('source_account_name',data.source_account_name, {label: trans('form.revenue_account')}) }} + {{ ExpandedForm.text('source_name',data.source_name, {label: trans('form.revenue_account')}) }} {% endif %} {# FREE FORMAT DESTINATION ACCOUNT ONLY FOR EXPENSES #} {% if what == 'withdrawal' %} - {{ ExpandedForm.text('destination_account_name',data.destination_account_name, {label: trans('form.expense_account')}) }} + {{ ExpandedForm.text('destination_name',data.destination_name, {label: trans('form.expense_account')}) }} {% endif %} {# SELECTABLE DESTINATION ACCOUNT ONLY FOR TRANSFERS AND DEPOSITS #} {% if what == 'transfer' or what == 'deposit' %} - {{ ExpandedForm.assetAccountList('destination_account_id', data.destination_account_id, {label: trans('form.asset_destination_account')} ) }} + {{ ExpandedForm.assetAccountList('destination_id', data.destination_id, {label: trans('form.asset_destination_account')} ) }} {% endif %} {# ALWAYS SHOW AMOUNT #} diff --git a/resources/views/transactions/split/edit.twig b/resources/views/transactions/split/edit.twig index 898b872907..d8c53af0e8 100644 --- a/resources/views/transactions/split/edit.twig +++ b/resources/views/transactions/split/edit.twig @@ -42,12 +42,12 @@ {# show source if withdrawal or transfer #} {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} - {{ ExpandedForm.activeAssetAccountList('journal_source_account_id', preFilled.journal_source_account_id) }} + {{ ExpandedForm.activeAssetAccountList('journal_source_id', preFilled.journal_source_id) }} {% endif %} {# show destination account id, if deposit (is asset): #} {% if preFilled.what == 'deposit' or preFilled.what == 'transfer' %} - {{ ExpandedForm.activeAssetAccountList('journal_destination_account_id', preFilled.journal_destination_account_id) }} + {{ ExpandedForm.activeAssetAccountList('journal_destination_id', preFilled.journal_destination_id) }} {% endif %} {# show amount and some helper text when making splits: #} diff --git a/tests/Feature/Controllers/Transaction/MassControllerTest.php b/tests/Feature/Controllers/Transaction/MassControllerTest.php index 82ed6bb76d..ce6d3b0f01 100644 --- a/tests/Feature/Controllers/Transaction/MassControllerTest.php +++ b/tests/Feature/Controllers/Transaction/MassControllerTest.php @@ -200,8 +200,8 @@ class MassControllerTest extends TestCase 'amount' => [$deposit->id => 1600], 'amount_currency_id_amount_' . $deposit->id => 1, 'date' => [$deposit->id => '2014-07-24'], - 'source_account_name' => [$deposit->id => 'Job'], - 'destination_account_id' => [$deposit->id => 1], + 'source_name' => [$deposit->id => 'Job'], + 'destination_id' => [$deposit->id => 1], 'category' => [$deposit->id => 'Salary'], ]; diff --git a/tests/Feature/Controllers/Transaction/SingleControllerTest.php b/tests/Feature/Controllers/Transaction/SingleControllerTest.php index ea2bafac0d..c0ef2c2ca8 100644 --- a/tests/Feature/Controllers/Transaction/SingleControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SingleControllerTest.php @@ -306,7 +306,7 @@ class SingleControllerTest extends TestCase $response->assertStatus(200); // has bread crumb $response->assertSee('
    - - + + From f27eb084c7a259d7a84bacc93da66489f3a18f94 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 Jul 2018 16:06:49 +0200 Subject: [PATCH 117/134] Test the currency exchange controller. --- app/Models/CurrencyExchangeRate.php | 3 + .../V1/Controllers/CurrencyControllerTest.php | 2 +- .../CurrencyExchangeRateControllerTest.php | 100 ++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index 6f06cc8862..e77bb2484c 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -36,6 +36,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property TransactionCurrency $fromCurrency * @property TransactionCurrency $toCurrency * @property float $rate + * @property Carbon $date + * @property int $from_currency_id + * @property int $to_currency_id * */ class CurrencyExchangeRate extends Model diff --git a/tests/Api/V1/Controllers/CurrencyControllerTest.php b/tests/Api/V1/Controllers/CurrencyControllerTest.php index d6fb9884b3..c90dada382 100644 --- a/tests/Api/V1/Controllers/CurrencyControllerTest.php +++ b/tests/Api/V1/Controllers/CurrencyControllerTest.php @@ -42,7 +42,7 @@ class CurrencyControllerTest extends TestCase /** * */ - public function setUp() + public function setUp(): void { parent::setUp(); Passport::actingAs($this->user()); diff --git a/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php b/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php new file mode 100644 index 0000000000..4a0c1c8e04 --- /dev/null +++ b/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php @@ -0,0 +1,100 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use Carbon\Carbon; +use FireflyIII\Models\CurrencyExchangeRate; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Services\Currency\ExchangeRateInterface; +use Laravel\Passport\Passport; +use Log; +use Tests\TestCase; + +/** + * + * Class CurrencyExchangeRateControllerTest + */ +class CurrencyExchangeRateControllerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Passport::actingAs($this->user()); + Log::debug(sprintf('Now in %s.', \get_class($this))); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\CurrencyExchangeRateController + */ + public function testIndex(): void + { + // mock repository + $repository = $this->mock(CurrencyRepositoryInterface::class); + $service = $this->mock(ExchangeRateInterface::class); + + $rate = new CurrencyExchangeRate(); + $rate->date = new Carbon(); + $rate->updated_at = new Carbon(); + $rate->created_at = new Carbon(); + $rate->rate = '0.5'; + $rate->to_currency_id = 1; + $rate->from_currency_id = 2; + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('findByCodeNull')->withArgs(['EUR'])->andReturn(TransactionCurrency::whereCode('EUR')->first())->once(); + $repository->shouldReceive('findByCodeNull')->withArgs(['USD'])->andReturn(TransactionCurrency::whereCode('USD')->first())->once(); + $repository->shouldReceive('getExchangeRate')->andReturn(null)->once(); + $service->shouldReceive('setUser')->once(); + $service->shouldReceive('getRate')->once()->andReturn($rate); + + // test API + $params = [ + 'from' => 'EUR', + 'to' => 'USD', + 'date' => '2018-01-01', + ]; + $response = $this->get('/api/v1/cer?' . http_build_query($params)); + $response->assertStatus(200); + $response->assertJson( + ['data' => [ + 'rate' => 0.5, + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/currency_exchange_rates/', + ], + ], + ], + ] + ); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } +} \ No newline at end of file From 2e67bd3b7833e5276e9f339c29836d28f98c9416 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 Jul 2018 20:02:20 +0200 Subject: [PATCH 118/134] New test code. --- .../V1/Controllers/JournalLinkController.php | 8 +- .../V1/Controllers/RecurrenceController.php | 2 +- app/Api/V1/Requests/RecurrenceRequest.php | 4 +- resources/lang/en_US/validation.php | 2 +- .../V1/Controllers/CategoryControllerTest.php | 8 +- .../Controllers/JournalLinkControllerTest.php | 239 +++ .../V1/Controllers/LinkTypeControllerTest.php | 200 ++ .../Controllers/RecurrenceControllerTest.php | 1631 +++++++++++++++++ 8 files changed, 2083 insertions(+), 11 deletions(-) create mode 100644 tests/Api/V1/Controllers/JournalLinkControllerTest.php create mode 100644 tests/Api/V1/Controllers/LinkTypeControllerTest.php create mode 100644 tests/Api/V1/Controllers/RecurrenceControllerTest.php diff --git a/app/Api/V1/Controllers/JournalLinkController.php b/app/Api/V1/Controllers/JournalLinkController.php index c54b42484e..0dde9742be 100644 --- a/app/Api/V1/Controllers/JournalLinkController.php +++ b/app/Api/V1/Controllers/JournalLinkController.php @@ -68,13 +68,13 @@ class JournalLinkController extends Controller /** * Delete the resource. * - * @param string $object + * @param TransactionJournalLink $link * * @return JsonResponse */ - public function delete(string $object): JsonResponse + public function delete(TransactionJournalLink $link): JsonResponse { - // todo delete object. + $this->repository->destroyLink($link); return response()->json([], 204); } @@ -88,8 +88,6 @@ class JournalLinkController extends Controller */ public function index(Request $request): JsonResponse { - - // create some objects: $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index dce57a0d98..a5ec34e9c6 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -66,7 +66,7 @@ class RecurrenceController extends Controller /** * Delete the resource. * - * @param string $object + * @param Recurrence $recurrence * * @return JsonResponse */ diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php index c7c3c04951..3aa948722a 100644 --- a/app/Api/V1/Requests/RecurrenceRequest.php +++ b/app/Api/V1/Requests/RecurrenceRequest.php @@ -30,6 +30,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Rules\BelongsUser; use Illuminate\Validation\Validator; use InvalidArgumentException; +use Log; /** * Class RecurrenceRequest @@ -208,6 +209,7 @@ class RecurrenceRequest extends Request $repository = app(AccountRepositoryInterface::class); $repository->setUser(auth()->user()); $set = $repository->getAccountsById([$accountId]); + Log::debug(sprintf('Count of accounts found by ID %d is: %d', $accountId, $set->count())); if ($set->count() === 1) { /** @var Account $first */ $first = $set->first(); @@ -399,7 +401,7 @@ class RecurrenceRequest extends Request if (null !== $repetitions && null !== $repeatUntil) { // expect a date OR count: $validator->errors()->add('repeat_until', trans('validation.require_repeat_until')); - $validator->errors()->add('repetitions', trans('validation.require_repeat_until')); + $validator->errors()->add('nr_of_repetitions', trans('validation.require_repeat_until')); return; } diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 38371f9d97..f9e017f755 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'This value is invalid for this field.', 'at_least_one_transaction' => 'Need at least one transaction.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', diff --git a/tests/Api/V1/Controllers/CategoryControllerTest.php b/tests/Api/V1/Controllers/CategoryControllerTest.php index 4782bac76c..672a1a366d 100644 --- a/tests/Api/V1/Controllers/CategoryControllerTest.php +++ b/tests/Api/V1/Controllers/CategoryControllerTest.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace Tests\Api\V1\Controllers; -use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use Laravel\Passport\Passport; use Log; @@ -87,6 +87,7 @@ class CategoryControllerTest extends TestCase $response = $this->get('/api/v1/categories'); $response->assertStatus(200); $response->assertSee($categories->first()->name); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); } /** @@ -107,6 +108,7 @@ class CategoryControllerTest extends TestCase $response = $this->get('/api/v1/categories/' . $category->id); $response->assertStatus(200); $response->assertSee($category->name); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); } /** @@ -116,7 +118,7 @@ class CategoryControllerTest extends TestCase */ public function testStore(): void { - /** @var Budget $category */ + /** @var Category $category */ $category = $this->user()->categories()->first(); // mock stuff: @@ -150,7 +152,7 @@ class CategoryControllerTest extends TestCase // mock repositories $repository = $this->mock(CategoryRepositoryInterface::class); - /** @var Budget $category */ + /** @var Category $category */ $category = $this->user()->categories()->first(); // mock calls: diff --git a/tests/Api/V1/Controllers/JournalLinkControllerTest.php b/tests/Api/V1/Controllers/JournalLinkControllerTest.php new file mode 100644 index 0000000000..59720eb5fd --- /dev/null +++ b/tests/Api/V1/Controllers/JournalLinkControllerTest.php @@ -0,0 +1,239 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournalLink; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use Illuminate\Support\Collection; +use Laravel\Passport\Passport; +use Log; +use Tests\TestCase; + +/** + * + * Class JournalLinkControllerTest + */ +class JournalLinkControllerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Passport::actingAs($this->user()); + Log::debug(sprintf('Now in %s.', \get_class($this))); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + */ + public function testDelete(): void + { + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $journalRepos->shouldReceive('setUser')->once(); + $repository->shouldReceive('destroyLink')->once()->andReturn(true); + + // get a link + /** @var TransactionJournalLink $journalLink */ + $journalLink = TransactionJournalLink::first(); + + // call API + $response = $this->delete('/api/v1/journal_links/' . $journalLink->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + */ + public function testIndex(): void + { + $journalLinks = TransactionJournalLink::get(); + $transaction = Transaction::first(); + $transaction->date = new Carbon; + $transaction->transaction_type_type = 'Withdrawal'; + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('findByName')->once()->andReturn(null); + $repository->shouldReceive('getJournalLinks')->once()->andReturn($journalLinks); + + $journalRepos->shouldReceive('setUser')->once(); + + $collector->shouldReceive('setUser')->withAnyArgs(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf(); + $collector->shouldReceive('setJournals')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn(new Collection([$transaction])); + + // call API + $response = $this->get('/api/v1/journal_links'); + $response->assertStatus(200); + $response->assertSee($journalLinks->first()->id); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + */ + public function testShow(): void + { + $journalLink = TransactionJournalLink::first(); + $transaction = Transaction::first(); + $transaction->date = new Carbon; + $transaction->transaction_type_type = 'Withdrawal'; + + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $journalRepos->shouldReceive('setUser')->once(); + $collector->shouldReceive('setUser')->withAnyArgs(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf(); + $collector->shouldReceive('setJournals')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn(new Collection([$transaction])); + + // call API + $response = $this->get('/api/v1/journal_links/' . $journalLink->id); + $response->assertStatus(200); + $response->assertSee($journalLink->id); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testStore(): void + { + $journalLink = TransactionJournalLink::first(); + $journal = $this->user()->transactionJournals()->find(1); + $transaction = Transaction::first(); + $transaction->date = new Carbon; + $transaction->transaction_type_type = 'Withdrawal'; + + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $journalRepos->shouldReceive('setUser')->once(); + $collector->shouldReceive('setUser')->withAnyArgs(); + + $collector->shouldReceive('setUser')->withAnyArgs(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf(); + $collector->shouldReceive('setJournals')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->andReturn($journal); + $repository->shouldReceive('storeLink')->once()->andReturn($journalLink); + + + // data to submit + $data = [ + 'link_type_id' => '1', + 'inward_id' => '1', + 'outward_id' => '2', + 'notes' => 'Some notes', + ]; + + // test API + $response = $this->post('/api/v1/journal_links', $data); + $response->assertStatus(200); + $response->assertSee($journalLink->created_at->toAtomString()); // the creation moment. + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testUpdate(): void + { + + // mock repositories + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + + $journalLink = TransactionJournalLink::first(); + $journal = $this->user()->transactionJournals()->find(1); + $transaction = Transaction::first(); + $transaction->date = new Carbon; + $transaction->transaction_type_type = 'Withdrawal'; + + + // mock calls: + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser')->once(); + $collector->shouldReceive('setUser')->withAnyArgs(); + + $collector->shouldReceive('setUser')->withAnyArgs(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf(); + $collector->shouldReceive('setJournals')->andReturnSelf(); + $collector->shouldReceive('getJournals')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->andReturn($journal); + $repository->shouldReceive('updateLink')->once()->andReturn($journalLink); + + // data to submit + $data = [ + 'link_type_id' => '1', + 'inward_id' => '1', + 'outward_id' => '2', + 'notes' => 'Some notes', + ]; + + // test API + $response = $this->put('/api/v1/journal_links/' . $journalLink->id, $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertSee($journalLink->created_at->toAtomString()); // the creation moment. + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } +} \ No newline at end of file diff --git a/tests/Api/V1/Controllers/LinkTypeControllerTest.php b/tests/Api/V1/Controllers/LinkTypeControllerTest.php new file mode 100644 index 0000000000..c23a9c06a5 --- /dev/null +++ b/tests/Api/V1/Controllers/LinkTypeControllerTest.php @@ -0,0 +1,200 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + +use FireflyIII\Models\LinkType; +use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Laravel\Passport\Passport; +use Log; +use Tests\TestCase; + + +/** + * + * Class LinkTypeControllerTest + */ +class LinkTypeControllerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Passport::actingAs($this->user()); + Log::debug(sprintf('Now in %s.', \get_class($this))); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + */ + public function testDelete(): void + { + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + + // create editable link type: + $linkType = LinkType::create( + [ + 'name' => 'random' . random_int(1, 100000), + 'outward' => 'outward' . random_int(1, 100000), + 'inward' => 'inward ' . random_int(1, 100000), + 'editable' => true, + + ] + ); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('destroy')->once()->andReturn(true); + + // call API + $response = $this->delete('/api/v1/link_types/' . $linkType->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + */ + public function testIndex(): void + { + $linkTypes = LinkType::get(); + + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('get')->once()->andReturn($linkTypes); + + // call API + $response = $this->get('/api/v1/link_types'); + $response->assertStatus(200); + $response->assertSee($linkTypes->first()->created_at->toAtomString()); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + */ + public function testShow(): void + { + $linkType = LinkType::first(); + + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + + // call API + $response = $this->get('/api/v1/link_types/' . $linkType->id); + $response->assertStatus(200); + $response->assertSee($linkType->first()->created_at->toAtomString()); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + * @covers \FireflyIII\Api\V1\Requests\LinkTypeRequest + */ + public function testStore(): void + { + $linkType = LinkType::first(); + + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('store')->once()->andReturn($linkType); + $userRepository->shouldReceive('hasRole')->once()->andReturn(true); + + + // data to submit + $data = [ + 'name' => 'random' . random_int(1, 100000), + 'outward' => 'outward' . random_int(1, 100000), + 'inward' => 'inward ' . random_int(1, 100000), + 'editable' => true, + + ]; + + // test API + $response = $this->post('/api/v1/link_types', $data); + $response->assertStatus(200); + $response->assertSee($linkType->created_at->toAtomString()); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + * @covers \FireflyIII\Api\V1\Requests\LinkTypeRequest + */ + public function testUpdate(): void + { + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $userRepository->shouldReceive('hasRole')->once()->andReturn(true); + + // create editable link type: + $linkType = LinkType::create( + [ + 'name' => 'random' . random_int(1, 100000), + 'outward' => 'outward' . random_int(1, 100000), + 'inward' => 'inward ' . random_int(1, 100000), + 'editable' => true, + + ] + ); + + // mock calls: + $repository->shouldReceive('setUser'); + $repository->shouldReceive('update')->once()->andReturn($linkType); + + // data to submit + $data = [ + 'name' => 'random' . random_int(1, 100000), + 'outward' => 'outward' . random_int(1, 100000), + 'inward' => 'inward ' . random_int(1, 100000), + 'editable' => true, + + ]; + + // test API + $response = $this->put('/api/v1/link_types/' . $linkType->id, $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + $response->assertSee($linkType->created_at->toAtomString()); + } + + +} \ No newline at end of file diff --git a/tests/Api/V1/Controllers/RecurrenceControllerTest.php b/tests/Api/V1/Controllers/RecurrenceControllerTest.php new file mode 100644 index 0000000000..56f469e4a7 --- /dev/null +++ b/tests/Api/V1/Controllers/RecurrenceControllerTest.php @@ -0,0 +1,1631 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + +use Carbon\Carbon; +use FireflyIII\Factory\CategoryFactory; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Support\Collection; +use Laravel\Passport\Passport; +use Log; +use Tests\TestCase; + +class RecurrenceControllerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Passport::actingAs($this->user()); + Log::debug(sprintf('Now in %s.', \get_class($this))); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + */ + public function testDelete(): void + { + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('destroy')->once()->andReturn(true); + + // get a recurrence: + $recurrence = $this->user()->recurrences()->first(); + + // call API + $response = $this->delete('/api/v1/recurrences/' . $recurrence->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + */ + public function testIndex(): void + { + /** @var Recurrence $recurrences */ + $recurrences = $this->user()->recurrences()->get(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $repository->shouldReceive('getAll')->once()->andReturn($recurrences); + $repository->shouldReceive('getNoteText')->andReturn('Notes.'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + + // call API + $response = $this->get('/api/v1/recurrences'); + $response->assertStatus(200); + $response->assertSee($recurrences->first()->title); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + */ + public function testShow(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $repository->shouldReceive('getNoteText')->andReturn('Notes.'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // call API + $response = $this->get('/api/v1/recurrences/' . $recurrence->id); + $response->assertStatus(200); + $response->assertSee($recurrence->title); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreAssetId(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn($recurrence); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source name field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreAssetName(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn($recurrence); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[0]])->once()->andReturn(new Collection); + // used by the validator to find the source_name: + $accountRepos->shouldReceive('findByName')->withArgs(['Checking Account', [AccountType::ASSET]])->once()->andReturn($assetAccount); + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_name' => 'Checking Account', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * Submit a deposit. Since most validators have been tested in other methods, dont bother too much. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreDeposit(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn($recurrence); + + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'deposit', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description deposit', + 'source_name' => 'Some expense account', + 'destination_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * Add a recurring with correct reference to a destination (expense). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreDestinationId(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + $expenseAccount = $this->user()->accounts()->where('account_type_id', 4)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn($recurrence); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + $accountRepos->shouldReceive('getAccountsById')->withArgs([[$expenseAccount->id]])->once() + ->andReturn(new Collection([$expenseAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + 'destination_id' => $expenseAccount->id, + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * Add a recurring with correct reference to a destination (expense). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreDestinationName(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + $expenseAccount = $this->user()->accounts()->where('account_type_id', 4)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn($recurrence); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + 'destination_name' => $expenseAccount->name, + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * Includes both repetition count and an end date. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailBothRepetitions(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $repeatUntil = new Carbon; + $repeatUntil->addMonth(); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'repeat_until' => $repeatUntil->format('Y-m-d'), + 'nr_of_repetitions' => 10, + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repeat_until' => [ + 'Require either a number of repetitions, or an end date (repeat_until). Not both.', + ], + 'nr_of_repetitions' => [ + 'Require either a number of repetitions, or an end date (repeat_until). Not both.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit foreign amount but no currency information. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailForeignCurrency(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[0]])->once()->andReturn(new Collection); + // used by the validator to find the source_name: + $accountRepos->shouldReceive('findByName')->withArgs(['Checking Account', [AccountType::ASSET]])->once()->andReturn($assetAccount); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'foreign_amount' => '100', + 'description' => 'Test description', + 'source_name' => 'Checking Account', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'transactions.0.foreign_amount' => [ + 'The content of this field is invalid without currency information.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidDaily(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '1', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repetitions.0.moment' => [ + 'Invalid repetition moment for this type of repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidWeekly(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'weekly', + 'moment' => '8', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repetitions.0.moment' => [ + 'Invalid repetition moment for this type of repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidMonthly(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'monthly', + 'moment' => '32', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repetitions.0.moment' => [ + 'Invalid repetition moment for this type of repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidNdom(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'ndom', + 'moment' => '9,9', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repetitions.0.moment' => [ + 'Invalid repetition moment for this type of repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidNdomHigh(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'ndom', + 'moment' => '4,9', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repetitions.0.moment' => [ + 'Invalid repetition moment for this type of repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit the minimum amount to store a recurring transaction (using source ID field). + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidNdomCount(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [ + [ + 'type' => 'ndom', + 'moment' => '9', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'repetitions.0.moment' => [ + 'Invalid repetition moment for this type of repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Add a recurring but refer to an asset as destination. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailInvalidDestinationId(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + $accountRepos->shouldReceive('getAccountsById')->withArgs([[$assetAccount->id]])->once() + ->andReturn(new Collection([$assetAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + 'destination_id' => $assetAccount->id, + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'transactions.0.destination_id' => [ + 'This value is invalid for this field.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit without a source account. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailNoAsset(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '0', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'transactions.0.source_id' => [ + 'This value is invalid for this field.', + 'The transactions.0.source_id field is required.', + ], + ], + ] + ); + $response->assertStatus(422); + + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit with an expense account. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailNotAsset(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // expense account: + $expenseAccount = $this->user()->accounts()->where('account_type_id', 4)->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[$expenseAccount->id]])->once() + ->andReturn(new Collection([$expenseAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => $expenseAccount->id, + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'transactions.0.source_id' => [ + 'This value is invalid for this field.', + ], + ], + ] + ); + $response->assertStatus(422); + + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit with an invalid asset account name. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailNotAssetName(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // expense account: + $expenseAccount = $this->user()->accounts()->where('account_type_id', 4)->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[0]])->once() + ->andReturn(new Collection); + // used to search by name. + $accountRepos->shouldReceive('findByName')->withArgs(['Fake name', [AccountType::ASSET]])->once() + ->andReturn(null); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_name' => 'Fake name', + 'source_id' => '0', + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'transactions.0.source_id' => [ + 'This value is invalid for this field.', + ], + 'transactions.0.source_name' => [ + 'This value is invalid for this field.', + ], + ], + ] + ); + $response->assertStatus(422); + + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Dont include enough repetitions. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailRepetitions(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[1]])->once() + ->andReturn(new Collection([$assetAccount])); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description', + 'source_id' => '1', + ], + ], + 'repetitions' => [], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'description' => [ + 'Need at least one repetition.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Dont include enough repetitions. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreFailTransactions(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'withdrawal', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'description' => [ + 'Need at least one transaction.', + ], + ], + ] + ); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * Submit a transfer. Since most validators have been tested in other methods, dont bother too much. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testStoreTransfer(): void + { + /** @var Recurrence $recurrence */ + $recurrence = $this->user()->recurrences()->first(); + + // mock stuff: + $repository = $this->mock(RecurringRepositoryInterface::class); + $factory = $this->mock(CategoryFactory::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + + $assetAccount = $this->user()->accounts()->where('account_type_id', 3)->first(); + $otherAssetAccount = $this->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $assetAccount->id)->first(); + + // mock calls: + $repository->shouldReceive('setUser'); + $factory->shouldReceive('setUser'); + $budgetRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn($recurrence); + + + // used by the validator to find the source_id: + $accountRepos->shouldReceive('getAccountsById')->withArgs([[$assetAccount->id]])->once()->andReturn(new Collection([$assetAccount])); + $accountRepos->shouldReceive('getAccountsById')->withArgs([[$otherAssetAccount->id]])->once()->andReturn(new Collection([$otherAssetAccount])); + + + // entries used by the transformer + $repository->shouldReceive('getNoteText')->andReturn('Note text'); + $repository->shouldReceive('repetitionDescription')->andReturn('Some description.'); + $repository->shouldReceive('getXOccurrences')->andReturn([]); + + // entries used by the transformer (the fake entry has a category + a budget): + $factory->shouldReceive('findOrCreate')->andReturn(null); + $budgetRepos->shouldReceive('findNull')->andReturn(null); + + + // data to submit + $firstDate = new Carbon; + $firstDate->addDays(2); + $data = [ + 'type' => 'transfer', + 'title' => 'Hello', + 'first_date' => $firstDate->format('Y-m-d'), + 'apply_rules' => 1, + 'active' => 1, + 'transactions' => [ + [ + 'amount' => '100', + 'currency_id' => '1', + 'description' => 'Test description transfer', + 'source_id' => $assetAccount->id, + 'destination_id' => $otherAssetAccount->id, + ], + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => '0', + 'weekend' => '1', + + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/recurrences', $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + +} \ No newline at end of file From 54afc6ca8c2cdd1b551e8bab68e8fb512f49e40c Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 Jul 2018 20:17:50 +0200 Subject: [PATCH 119/134] Some last minute updates. --- app/Api/V1/Controllers/RuleController.php | 6 +- .../V1/Controllers/RuleGroupController.php | 4 +- app/Api/V1/Controllers/TagController.php | 112 ------------------ .../Import/JobStatusController.php | 2 +- app/Http/Requests/BillFormRequest.php | 2 +- app/Import/Routine/SpectreRoutine.php | 1 - app/Jobs/CreateRecurringTransactions.php | 58 ++++++++- 7 files changed, 64 insertions(+), 121 deletions(-) delete mode 100644 app/Api/V1/Controllers/TagController.php diff --git a/app/Api/V1/Controllers/RuleController.php b/app/Api/V1/Controllers/RuleController.php index cf1e818239..bd581dcfb2 100644 --- a/app/Api/V1/Controllers/RuleController.php +++ b/app/Api/V1/Controllers/RuleController.php @@ -64,13 +64,13 @@ class RuleController extends Controller /** * Delete the resource. * - * @param string $object + * @param Rule $rule * * @return JsonResponse */ - public function delete(string $object): JsonResponse + public function delete(Rule $rule): JsonResponse { - // todo delete object. + $this->ruleRepository->destroy($rule); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php index 6c4196375f..9085594c69 100644 --- a/app/Api/V1/Controllers/RuleGroupController.php +++ b/app/Api/V1/Controllers/RuleGroupController.php @@ -66,9 +66,9 @@ class RuleGroupController extends Controller * * @return JsonResponse */ - public function delete(string $object): JsonResponse + public function delete(RuleGroup $ruleGroup): JsonResponse { - // todo delete object. + $this->ruleGroupRepository->destroy($ruleGroup, null); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/TagController.php b/app/Api/V1/Controllers/TagController.php deleted file mode 100644 index 8aa1b6acb0..0000000000 --- a/app/Api/V1/Controllers/TagController.php +++ /dev/null @@ -1,112 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Api\V1\Controllers; - -use FireflyIII\User; -use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; - - -class TagController extends Controller -{ - public function __construct() - { - parent::__construct(); - $this->middleware( - function ($request, $next) { - /** @var User $user */ - $user = auth()->user(); - - // todo add local repositories. - return $next($request); - } - ); - } - - /** - * Delete the resource. - * - * @param string $object - * - * @return JsonResponse - */ - public function delete(string $object): JsonResponse - { - // todo delete object. - - return response()->json([], 204); - } - - /** - * List all of them. - * - * @param Request $request - * - * @return JsonResponse] - */ - public function index(Request $request): JsonResponse - { - // todo implement. - - } - - /** - * List single resource. - * - * @param Request $request - * @param string $object - * - * @return JsonResponse - */ - public function show(Request $request, string $object): JsonResponse - { - // todo implement me. - - } - - /** - * Store new object. - * - * @param Request $request - * - * @return JsonResponse - */ - public function store(Request $request): JsonResponse - { - // todo replace code and replace request object. - - } - - /** - * @param Request $request - * @param string $object - * - * @return JsonResponse - */ - public function update(Request $request, string $object): JsonResponse - { - // todo replace code and replace request object. - - } -} \ No newline at end of file diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index d512e82196..8a9ce1d851 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -127,7 +127,7 @@ class JobStatusController extends Controller public function start(ImportJob $importJob): JsonResponse { // catch impossible status: - $allowed = ['ready_to_run', 'need_job_config', 'error']; // todo remove error + $allowed = ['ready_to_run', 'need_job_config']; if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { Log::error('Job is not ready.'); diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index df36e4946e..dc2d35cf94 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -57,7 +57,7 @@ class BillFormRequest extends Request /** * @return array */ - public function rules() + public function rules(): array { $nameRule = 'required|between:1,255|uniqueObjectForUser:bills,name'; if ($this->integer('id') > 0) { diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 41d2a2a80e..83aea2664a 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -101,7 +101,6 @@ class SpectreRoutine implements RoutineInterface $handler = app(StageImportDataHandler::class); $handler->setImportJob($this->importJob); $handler->run(); - // todo apply rules. $this->repository->setStatus($this->importJob, 'provider_finished'); $this->repository->setStage($this->importJob, 'final'); } diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 281d2e146c..d4a09795af 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -7,8 +7,13 @@ use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\Rule; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use FireflyIII\TransactionRules\Processor; +use FireflyIII\User; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -30,6 +35,8 @@ class CreateRecurringTransactions implements ShouldQueue private $journalRepository; /** @var RecurringRepositoryInterface */ private $repository; + /** @var array */ + private $rules = []; /** * Create a new job instance. @@ -42,6 +49,7 @@ class CreateRecurringTransactions implements ShouldQueue $this->date = $date; $this->repository = app(RecurringRepositoryInterface::class); $this->journalRepository = app(JournalRepositoryInterface::class); + } /** @@ -77,6 +85,9 @@ class CreateRecurringTransactions implements ShouldQueue $created = $this->handleRepetitions($recurrence); Log::debug(sprintf('Done with recurrence #%d', $recurrence->id)); $result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($created); + + // apply rules: + $this->applyRules($recurrence->user, $created); } Log::debug('Now running report thing.'); @@ -100,6 +111,34 @@ class CreateRecurringTransactions implements ShouldQueue return $recurrence->active; } + /** + * @param User $user + * @param Collection $journals + */ + private function applyRules(User $user, Collection $journals): void + { + $userId = $user->id; + if (!isset($this->rules[$userId])) { + $this->rules[$userId] = $this->getRules($user); + } + // run the rules: + if ($this->rules[$userId]->count() > 0) { + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->rules[$userId]->each( + function (Rule $rule) use ($journal) { + Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); + $processor = Processor::make($rule); + $processor->handleTransactionJournal($journal); + if ($rule->stop_processing) { + return; + } + } + ); + } + } + } + /** * @param array $occurrences * @@ -115,6 +154,23 @@ class CreateRecurringTransactions implements ShouldQueue return $return; } + /** + * @param User $user + * + * @return Collection + */ + private function getRules(User $user): Collection + { + /** @var RuleRepositoryInterface $repository */ + $repository = app(RuleRepositoryInterface::class); + $repository->setUser($user); + $set = $repository->getForImport(); + + Log::debug(sprintf('Found %d user rules.', $set->count())); + + return $set; + } + /** * @param Recurrence $recurrence * @@ -214,7 +270,7 @@ class CreateRecurringTransactions implements ShouldQueue ]; $journal = $this->journalRepository->store($array); Log::info(sprintf('Created new journal #%d', $journal->id)); - // todo fire rules + $collection->push($journal); // update recurring thing: $recurrence->latest_date = $date; From 10f195d334b8d7552bd6117b4d880134aa1f0e88 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 Jul 2018 20:18:00 +0200 Subject: [PATCH 120/134] Update versions. --- config/firefly.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/firefly.php b/config/firefly.php index 8d18fe1024..3ce05abf00 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -88,8 +88,8 @@ return [ 'is_demo_site' => false, ], 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, - 'version' => '4.7.4', - 'api_version' => '0.3', + 'version' => '4.7.5', + 'api_version' => '0.4', 'db_version' => 4, 'maxUploadSize' => 15242880, 'allowedMimes' => [ From e36a9fda1bf6dc65a94ded57d55a150c12c26e2b Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 Jul 2018 20:39:45 +0200 Subject: [PATCH 121/134] Get a list of transactions belonging to the recurrence. --- .../Controllers/Recurring/IndexController.php | 10 ++++-- .../Recurring/RecurringRepository.php | 36 +++++++++++++++++++ .../RecurringRepositoryInterface.php | 11 ++++++ app/Transformers/RecurrenceTransformer.php | 2 +- resources/views/recurring/show.twig | 2 +- 5 files changed, 56 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index 69f5847d95..d7f02b5071 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -179,15 +179,19 @@ class IndexController extends Controller } /** + * @param Request $request * @param Recurrence $recurrence * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * @throws \FireflyIII\Exceptions\FireflyException + * @throws FireflyException */ - public function show(Recurrence $recurrence) + public function show(Request $request, Recurrence $recurrence) { $transformer = new RecurrenceTransformer(new ParameterBag); $array = $transformer->transform($recurrence); + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $transactions = $this->recurring->getTransactions($recurrence, $page, $pageSize); // transform dates back to Carbon objects: foreach ($array['recurrence_repetitions'] as $index => $repetition) { @@ -198,7 +202,7 @@ class IndexController extends Controller $subTitle = trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); - return view('recurring.show', compact('recurrence', 'subTitle', 'array')); + return view('recurring.show', compact('recurrence', 'subTitle', 'array','transactions')); } /** diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 2d4b93e62a..22dfaa10aa 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -26,6 +26,7 @@ namespace FireflyIII\Repositories\Recurring; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\RecurrenceFactory; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Note; use FireflyIII\Models\Preference; use FireflyIII\Models\Recurrence; @@ -34,9 +35,11 @@ use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService; use FireflyIII\Services\Internal\Update\RecurrenceUpdateService; use FireflyIII\User; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Log; @@ -328,6 +331,39 @@ class RecurringRepository implements RecurringRepositoryInterface return $tags; } + /** + * @param Recurrence $recurrence + * + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getTransactions(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator + { + $journalMeta = TransactionJournalMeta + ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $this->user->id) + ->where('name', 'recurrence_id') + ->where('data', json_encode((string)$recurrence->id)) + ->get()->pluck('transaction_journal_id')->toArray(); + $search = []; + foreach ($journalMeta as $journalId) { + $search[] = ['id' => (int)$journalId]; + } + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($recurrence->user); + $collector->withOpposingAccount()->setAllAssetAccounts()-> + + withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page); + // filter on specific journals. + $collector->setJournals(new Collection($search)); + + return $collector->getPaginatedJournals(); + } + /** * Calculate the next X iterations starting on the date given in $date. * diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index 92f824d25a..f760972d01 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -29,6 +29,7 @@ use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\User; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -46,6 +47,16 @@ interface RecurringRepositoryInterface */ public function destroy(Recurrence $recurrence): void; + /** + * @param Recurrence $recurrence + * + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getTransactions(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator; + /** * Returns all of the user's recurring transactions. * diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index f0d83c5a21..f35df38ddd 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -52,7 +52,7 @@ class RecurrenceTransformer extends TransformerAbstract * * @var array */ - protected $availableIncludes = ['user']; + protected $availableIncludes = ['user', 'transactions']; /** * List of resources to automatically include * diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index 85703b3282..f8d3507f26 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -194,7 +194,7 @@
    - List be here. + {% include 'list.journals' with {sorting:false, hideBills:true, hideBudgets: true, hideCategories: true, showReconcile: false} %}
    From 5690a44c3894b2728f8bbde70cf8e148f89a225d Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 2 Jul 2018 20:41:28 +0200 Subject: [PATCH 122/134] Remove a filter. --- app/Repositories/Recurring/RecurringRepository.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 22dfaa10aa..9407bdd7e7 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -27,6 +27,7 @@ use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\RecurrenceFactory; use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\Note; use FireflyIII\Models\Preference; use FireflyIII\Models\Recurrence; @@ -356,9 +357,9 @@ class RecurringRepository implements RecurringRepositoryInterface $collector = app(JournalCollectorInterface::class); $collector->setUser($recurrence->user); $collector->withOpposingAccount()->setAllAssetAccounts()-> - withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page); // filter on specific journals. + $collector->removeFilter(InternalTransferFilter::class); $collector->setJournals(new Collection($search)); return $collector->getPaginatedJournals(); From 2260ede559822e5be217f38d7fc924e96d84413b Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 05:32:35 +0200 Subject: [PATCH 123/134] Add transaction list to recurring transaction --- .../Recurring/DeleteController.php | 25 ++++++++++++- .../Recurring/RecurringRepository.php | 35 ++++++++++++++++--- .../RecurringRepositoryInterface.php | 27 ++++++++------ resources/views/recurring/show.twig | 3 +- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/Recurring/DeleteController.php b/app/Http/Controllers/Recurring/DeleteController.php index 6af99cae02..a36a0ce018 100644 --- a/app/Http/Controllers/Recurring/DeleteController.php +++ b/app/Http/Controllers/Recurring/DeleteController.php @@ -34,6 +34,29 @@ use Illuminate\Http\Request; */ class DeleteController extends Controller { + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + + $this->recurring = app(RecurringRepositoryInterface::class); + + return $next($request); + } + ); + } + /** * @param Recurrence $recurrence * @@ -46,7 +69,7 @@ class DeleteController extends Controller $this->rememberPreviousUri('recurrences.delete.uri'); // todo actual number. - $journalsCreated = 5; + $journalsCreated = $this->recurring->getTransactions($recurrence)->count(); return view('recurring.delete', compact('recurrence', 'subTitle', 'journalsCreated')); } diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 9407bdd7e7..43be804321 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -334,13 +334,12 @@ class RecurringRepository implements RecurringRepositoryInterface /** * @param Recurrence $recurrence - * * @param int $page * @param int $pageSize * * @return LengthAwarePaginator */ - public function getTransactions(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator + public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator { $journalMeta = TransactionJournalMeta ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') @@ -356,8 +355,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var JournalCollectorInterface $collector */ $collector = app(JournalCollectorInterface::class); $collector->setUser($recurrence->user); - $collector->withOpposingAccount()->setAllAssetAccounts()-> - withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page); + $collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page); // filter on specific journals. $collector->removeFilter(InternalTransferFilter::class); $collector->setJournals(new Collection($search)); @@ -365,6 +363,35 @@ class RecurringRepository implements RecurringRepositoryInterface return $collector->getPaginatedJournals(); } + /** + * @param Recurrence $recurrence + * + * @return Collection + */ + public function getTransactions(Recurrence $recurrence): Collection + { + $journalMeta = TransactionJournalMeta + ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $this->user->id) + ->where('name', 'recurrence_id') + ->where('data', json_encode((string)$recurrence->id)) + ->get()->pluck('transaction_journal_id')->toArray(); + $search = []; + foreach ($journalMeta as $journalId) { + $search[] = ['id' => (int)$journalId]; + } + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setUser($recurrence->user); + $collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation(); + // filter on specific journals. + $collector->removeFilter(InternalTransferFilter::class); + $collector->setJournals(new Collection($search)); + + return $collector->getJournals(); + } + /** * Calculate the next X iterations starting on the date given in $date. * diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index f760972d01..d738d768d6 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -47,16 +47,6 @@ interface RecurringRepositoryInterface */ public function destroy(Recurrence $recurrence): void; - /** - * @param Recurrence $recurrence - * - * @param int $page - * @param int $pageSize - * - * @return LengthAwarePaginator - */ - public function getTransactions(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator; - /** * Returns all of the user's recurring transactions. * @@ -91,6 +81,7 @@ interface RecurringRepositoryInterface /** * Returns the journals created for this recurrence, possibly limited by time. + * TODO make consistent with getTransactions * * @param Recurrence $recurrence * @param Carbon|null $start @@ -131,6 +122,22 @@ interface RecurringRepositoryInterface */ public function getTags(Recurrence $recurrence): array; + /** + * @param Recurrence $recurrence + * @param int $page + * @param int $pageSize + * + * @return LengthAwarePaginator + */ + public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator; + + /** + * @param Recurrence $recurrence + * + * @return Collection + */ + public function getTransactions(Recurrence $recurrence): Collection; + /** * Calculate the next X iterations starting on the date given in $date. * Returns an array of Carbon objects. diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index f8d3507f26..ef01897d28 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -194,7 +194,7 @@
    - {% include 'list.journals' with {sorting:false, hideBills:true, hideBudgets: true, hideCategories: true, showReconcile: false} %} + {% include 'list.journals' with {sorting:false, hideBills:true, hideBudgets: true, showReconcile: false} %}
    @@ -207,4 +207,5 @@ {% block scripts %} + {% endblock %} From d48c3a6d2f11e0202d28527b8597379e281b3324 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 05:52:27 +0200 Subject: [PATCH 124/134] Fix tests --- app/Http/Requests/JournalFormRequest.php | 45 ++++++++++++++----- resources/lang/en_US/validation.php | 3 ++ .../Transaction/SingleControllerTest.php | 2 + 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 05cbead4dd..24e450fbb1 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -25,6 +25,7 @@ namespace FireflyIII\Http\Requests; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionType; use Illuminate\Validation\Validator; +use Log; /** * Class JournalFormRequest. @@ -237,12 +238,19 @@ class JournalFormRequest extends Request { $data = $validator->getData(); $type = $data['what'] ?? 'invalid'; + Log::debug(sprintf('Type is %s', $type)); if ($type === 'withdrawal') { + $selectedCurrency = (int)($data['amount_currency_id_amount'] ?? 0); $accountCurrency = (int)($data['source_account_currency'] ?? 0); - $nativeAmount = (string)$data['native_amount']; - if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount) { - $validator->errors()->add('native_amount', trans('validation.numeric', ['attribute' => 'native_amount'])); + Log::debug(sprintf('Selected currency is %d, account currency is %d', $selectedCurrency, $accountCurrency)); + $nativeAmount = (string)($data['native_amount'] ?? ''); + if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount + && $selectedCurrency !== 0 + && $accountCurrency !== 0 + ) { + Log::debug('ADD validation error on native_amount'); + $validator->errors()->add('native_amount', trans('validation.numeric_native')); return; } @@ -252,9 +260,12 @@ class JournalFormRequest extends Request if ($type === 'deposit') { $selectedCurrency = (int)($data['amount_currency_id_amount'] ?? 0); $accountCurrency = (int)($data['destination_account_currency'] ?? 0); - $nativeAmount = (string)$data['native_amount']; - if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount) { - $validator->errors()->add('native_amount', trans('validation.numeric', ['attribute' => 'native_amount'])); + $nativeAmount = (string)($data['native_amount'] ?? ''); + if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount + && $selectedCurrency !== 0 + && $accountCurrency !== 0 + ) { + $validator->errors()->add('native_amount', trans('validation.numeric_native')); return; } @@ -262,17 +273,29 @@ class JournalFormRequest extends Request // and for transfers if ($type === 'transfer') { + $sourceCurrency = (int)($data['source_account_currency'] ?? 0); $destinationCurrency = (int)($data['destination_account_currency'] ?? 0); - $sourceAmount = (string)$data['source_amount']; - $destinationAmount = (string)$data['destination_amount']; - if ($sourceCurrency !== $destinationCurrency && '' === $sourceAmount) { - $validator->errors()->add('source_amount', trans('validation.numeric', ['attribute' => 'source_amount'])); + $sourceAmount = (string)($data['source_amount'] ?? ''); + $destinationAmount = (string)($data['destination_amount'] ?? ''); + + Log::debug(sprintf('Source currency is %d, destination currency is %d', $sourceCurrency, $destinationCurrency)); + + if ($sourceCurrency !== $destinationCurrency && '' === $sourceAmount + && $sourceCurrency !== 0 + && $destinationCurrency !== 0 + ) { + $validator->errors()->add('source_amount', trans('validation.numeric_source')); } - if ($sourceCurrency !== $destinationCurrency && '' === $destinationAmount) { + if ($sourceCurrency !== $destinationCurrency && '' === $destinationAmount + && $sourceCurrency !== 0 + && $destinationCurrency !== 0 + ) { + $validator->errors()->add('destination_amount', trans('validation.numeric_destination')); $validator->errors()->add('destination_amount', trans('validation.numeric', ['attribute' => 'destination_amount'])); } + return; } } diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index f9e017f755..6040f20399 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -91,6 +91,9 @@ return [ 'min.array' => 'The :attribute must have at least :min items.', 'not_in' => 'The selected :attribute is invalid.', 'numeric' => 'The :attribute must be a number.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', 'required_if' => 'The :attribute field is required when :other is :value.', diff --git a/tests/Feature/Controllers/Transaction/SingleControllerTest.php b/tests/Feature/Controllers/Transaction/SingleControllerTest.php index 563e7044ff..486c4e0492 100644 --- a/tests/Feature/Controllers/Transaction/SingleControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SingleControllerTest.php @@ -810,6 +810,8 @@ class SingleControllerTest extends TestCase $data = [ 'what' => 'transfer', 'amount' => '10', + 'source_amount' => '10', + 'destination_amount' => '10', 'amount_currency_id_amount' => 1, 'source_account_currency' => 1, 'destination_account_currency' => 2, From 2ef86c33391a5ef07c962cf6abb4366872a47667 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 05:52:35 +0200 Subject: [PATCH 125/134] Add description. --- resources/views/recurring/show.twig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index ef01897d28..b4cbf3875e 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -92,6 +92,7 @@
    + @@ -101,6 +102,9 @@ {% for transaction in array.transactions %} + From 0fa4d75a47c7c1a8d291314826c079e7cb4c1d84 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 05:56:46 +0200 Subject: [PATCH 126/134] Enable profile for Sandstorm users. --- resources/views/partials/menu-sidebar.twig | 11 +++++------ resources/views/profile/index.twig | 11 +++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index 7c29a52920..7dde1e8bcc 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -136,12 +136,11 @@
      - {% if not SANDSTORM %} -
    • - {{ 'profile'|_ }} - -
    • - {% endif %} + +
    • + {{ 'profile'|_ }} + +
    • {{ 'preferences'|_ }} diff --git a/resources/views/profile/index.twig b/resources/views/profile/index.twig index 4719bb4885..fa1e076841 100644 --- a/resources/views/profile/index.twig +++ b/resources/views/profile/index.twig @@ -6,10 +6,6 @@ {% block content %} - - - -
      @@ -20,15 +16,18 @@

      {{ trans('firefly.user_id_is',{user: userId})|raw }}

      + {% if not SANDSTORM %} + {% endif %}
      + {% if not SANDSTORM %}
      @@ -53,7 +52,9 @@
      + {% endif %} + {% if not SANDSTORM %}
      @@ -84,6 +85,8 @@
      + {% endif %} +
      From 53addcf99ad4ebedc9a9bb03c167a417ad440187 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 06:03:38 +0200 Subject: [PATCH 127/134] Add cron job to docker file. --- Dockerfile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Dockerfile b/Dockerfile index e9619b7ee4..4eb3f9cb1d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update -y && \ libpq-dev \ libbz2-dev \ gettext-base \ + cron \ locales && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -47,6 +48,13 @@ RUN cd /tmp && \ make && \ make install + +# Create the log file to be able to run tail +RUN touch /var/log/cron.log + +# Setup cron job +RUN (crontab -l ; echo "* * * * * root $FIREFLY_PATH/artisan schedule:run >> /var/log/cron.log") | crontab + # Install PHP exentions. RUN docker-php-ext-install -j$(nproc) curl gd intl json readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2 pdo_pgsql @@ -77,6 +85,9 @@ RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest # Expose port 80 EXPOSE 80 +# Run the command on container startup +CMD cron + # Run entrypoint thing ENTRYPOINT [".deploy/docker/entrypoint.sh"] From 18b06ff2832daa095179b1a06f7c3ebee6566bd9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 17:48:26 +0200 Subject: [PATCH 128/134] Some last-minute fixes. --- app/Console/Commands/VerifyDatabase.php | 18 +++++++++++++ .../Controllers/Import/IndexController.php | 6 ++--- app/Models/RuleAction.php | 1 + app/Models/TransactionJournal.php | 1 + .../Journal/JournalRepository.php | 5 ++++ .../Routine/File/ImportableConverter.php | 27 ++++++++++++++----- resources/lang/en_US/validation.php | 4 +-- 7 files changed, 50 insertions(+), 12 deletions(-) diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 00920aaf7f..0b3ecc5f4d 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -90,6 +90,7 @@ class VerifyDatabase extends Command $this->createAccessTokens(); $this->fixDoubleAmounts(); $this->fixBadMeta(); + $this->removeBills(); } /** @@ -255,6 +256,23 @@ class VerifyDatabase extends Command } } + /** + * + */ + private function removeBills(): void + { + /** @var TransactionType $withdrawal */ + $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); + $journals = TransactionJournal::whereNotNull('bill_id') + ->where('transaction_type_id', '!=', $withdrawal->id)->get(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->line(sprintf('Transaction journal #%d should not be linked to bill #%d.', $journal->id, $journal->bill_id)); + $journal->bill_id = null; + $journal->save(); + } + } + /** * Eeport (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events. */ diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index a6fe9d3167..72c422caad 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -161,10 +161,8 @@ class IndexController extends Controller $config['delimiter'] = $config['delimiter'] ?? ','; $config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter']; - // this prevents private information from escaping - $config['column-mapping-config'] = []; - $result = json_encode($config, JSON_PRETTY_PRINT); - $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); + $result = json_encode($config, JSON_PRETTY_PRINT); + $name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\')); /** @var LaravelResponse $response */ $response = response($result, 200); $response->header('Content-disposition', 'attachment; filename=' . $name) diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index 7ca38fcc39..6bad677108 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -36,6 +36,7 @@ use Illuminate\Database\Eloquent\Model; * @property int $order * @property bool $active * @property bool $stop_processing + * @property Rule $rule */ class RuleAction extends Model { diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 30f49cdf85..e6be30af6a 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -45,6 +45,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Collection $categories * @property bool $completed * @property string $description + * @property string $transaction_type_id */ class TransactionJournal extends Model { diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 4dc900bb95..f3397e1d6b 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -101,6 +101,11 @@ class JournalRepository implements JournalRepositoryInterface $transaction->budgets()->detach(); } } + // if journal is not a withdrawal, remove the bill ID. + if (TransactionType::WITHDRAWAL !== $type->type) { + $journal->bill_id = null; + $journal->save(); + } Preferences::mark(); diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 16e8f5b1d8..bb851e24e4 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -185,8 +185,15 @@ class ImportableConverter Log::debug(sprintf('"%s" (#%d) is source and "%s" (#%d) is destination.', $source->name, $source->id, $destination->name, $destination->id)); - if (bccomp($amount, '0') === 1) { - // amount is positive? Then switch: + + if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) { + Log::debug('Source and destination are asset accounts. This is a transfer.'); + $transactionType = 'transfer'; + } + + // amount is positive and its not a transfer? Then switch: + if ($transactionType !== 'transfer' && bccomp($amount, '0') === 1) { + [$destination, $source] = [$source, $destination]; Log::debug( sprintf( @@ -195,6 +202,17 @@ class ImportableConverter ) ); } + // amount is negative and type is transfer? then switch. + if ($transactionType === 'transfer' && bccomp($amount, '0') === -1) { + // amount is positive? Then switch: + [$destination, $source] = [$source, $destination]; + Log::debug( + sprintf( + '%s is negative, so "%s" (#%d) is now source and "%s" (#%d) is now destination.', + $amount, $source->name, $source->id, $destination->name, $destination->id + ) + ); + } // get currency preference from source asset account (preferred) // or destination asset account @@ -218,10 +236,7 @@ class ImportableConverter $currency = $this->defaultCurrency; } - if ($source->accountType->type === AccountType::ASSET && $destination->accountType->type === AccountType::ASSET) { - Log::debug('Source and destination are asset accounts. This is a transfer.'); - $transactionType = 'transfer'; - } + if ($source->accountType->type === AccountType::REVENUE) { Log::debug('Source is a revenue account. This is a deposit.'); $transactionType = 'deposit'; diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 6040f20399..b573da5844 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'The :attribute must be accepted.', 'bic' => 'This is not a valid BIC.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute must be larger than zero.', From d3a1f43cbb997c62539f80c76306f8d6f18274fa Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 18:24:43 +0200 Subject: [PATCH 129/134] Some last-minute fixes. --- resources/lang/de_DE/validation.php | 9 +++++--- resources/lang/es_ES/validation.php | 9 +++++--- resources/lang/fr_FR/validation.php | 9 +++++--- resources/lang/id_ID/validation.php | 9 +++++--- resources/lang/it_IT/validation.php | 7 ++++-- resources/lang/nl_NL/validation.php | 9 +++++--- resources/lang/pl_PL/validation.php | 9 +++++--- resources/lang/pt_BR/validation.php | 9 +++++--- resources/lang/ru_RU/validation.php | 9 +++++--- resources/lang/tr_TR/validation.php | 9 +++++--- .../Import/IndexControllerTest.php | 2 +- .../Routine/File/ImportableConverterTest.php | 8 +++---- .../Triggers/HasAnyBudgetTest.php | 23 ++++++++++++++++--- 13 files changed, 84 insertions(+), 37 deletions(-) diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 1579fce1db..d30156cb75 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Dieser Wert ist für dieses Feld ungültig.', 'at_least_one_transaction' => 'Sie brauchen mindestens eine Transaktion.', 'at_least_one_repetition' => 'Mindestens eine Wiederholung erforderlich.', - 'require_repeat_until' => 'Erfordert entweder eine Anzahl von Wiederholungen oder ein Enddatum (repeats_until). Nicht beides.', + 'require_repeat_until' => 'Erfordert entweder eine Anzahl von Wiederholungen oder ein Enddatum (repeat_until). Nicht beides.', 'require_currency_info' => 'Der Inhalt dieses Feldes ist ohne Währungsinformationen ungültig.', 'equal_description' => 'Die Transaktionsbeschreibung darf nicht der globalen Beschreibung entsprechen.', 'file_invalid_mime' => 'Die Datei „:name” ist vom Typ „:mime”, welcher nicht zum Hochladen zugelassen ist.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'Der Wert von :attribute ist unbekannt.', 'accepted' => ':attribute muss akzeptiert werden.', 'bic' => 'Dies ist kein gültiger BIC.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Regel muss mindestens einen Auslöser enthalten', + 'at_least_one_action' => 'Regel muss mindestens eine Aktion enthalten', 'base64' => 'Dies sind keine gültigen base64-kodierten Daten.', 'model_id_invalid' => 'Die angegebene ID scheint für dieses Modell ungültig zu sein.', 'more' => ':attribute muss größer als Null sein.', @@ -91,6 +91,9 @@ return [ 'min.array' => ':attribute muss mindestens :min Elemente enthalten.', 'not_in' => ':attribute ist ungültig.', 'numeric' => ':attribute muss eine Zahl sein.', + 'numeric_native' => 'Die native Betrag muss eine Zahl sein.', + 'numeric_destination' => 'Der Zielbeitrag muss eine Zahl sein.', + 'numeric_source' => 'Der Quellbetrag muss eine Zahl sein.', 'regex' => 'Das Format von :attribute ist ungültig.', 'required' => ':attribute Feld muss ausgefüllt sein.', 'required_if' => ':attribute Feld ist notwendig, wenn :other :value entspricht.', diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 3a4f8bf42c..106e5f668c 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Este valor no es válido para este campo.', 'at_least_one_transaction' => 'Se necesita al menos una transacción.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'El contenido de este campo no es válido sin la información montearia.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'El archivo ":name" es de tipo ":mime", el cual no se acepta.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'El :attribute debe ser aceptado.', 'bic' => 'Esto no es un BIC válido.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute debe ser mayor que cero.', @@ -91,6 +91,9 @@ return [ 'min.array' => 'El campo :attribute debe tener al menos :min elementos.', 'not_in' => 'El campo :attribute seleccionado es incorrecto.', 'numeric' => 'El campo :attribute debe ser un número.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'El formato del campo :attribute no es válido.', 'required' => 'El campo :attribute es obligatorio.', 'required_if' => 'El campo :attribute es obligatorio cuando el campo :other es :value.', diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index c6b8b5620a..e5277aa087 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Cette valeur n\'est pas valide pour ce champ.', 'at_least_one_transaction' => 'Besoin d\'au moins une transaction.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Le contenu de ce champ n\'est pas valide sans informations sur la devise.', 'equal_description' => 'La description de la transaction ne doit pas être égale à la description globale.', 'file_invalid_mime' => 'Le fichier ":name" est du type ":mime" ce qui n\'est pas accepté pour un nouvel envoi.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'Le champ :attribute doit être accepté.', 'bic' => 'Ce n’est pas un code BIC valide.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute doit être supérieur à zéro.', @@ -91,6 +91,9 @@ return [ 'min.array' => 'Le tableau :attribute doit avoir au moins :min éléments.', 'not_in' => 'Le champ :attribute sélectionné n\'est pas valide.', 'numeric' => 'Le champ :attribute doit contenir un nombre.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Le format du champ :attribute est invalide.', 'required' => 'Le champ :attribute est obligatoire.', 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index e69a758174..3175395fc5 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'This value is invalid for this field.', 'at_least_one_transaction' => 'Need at least one transaction.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'File ":name" adalah tipe ":mime" yang tidak diterima sebagai upload baru.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => ':attribute harus diterima.', 'bic' => 'Ini bukan BIC yang valid.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute harus lebih besar dari nol.', @@ -91,6 +91,9 @@ return [ 'min.array' => ':attribute harus minimal item :min.', 'not_in' => ':attribute yang dipilih tidak valid.', 'numeric' => ':attribute harus angka.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Format :attribute tidak valid.', 'required' => 'Bidang :attribute diperlukan.', 'required_if' => 'Bidang :attribute diperlukan ketika :other adalah :value.', diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index c39bf3f0da..c5d8699a6b 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'Il valore di :attribute è sconosciuto.', 'accepted' => 'L\' :attribute deve essere accettato.', 'bic' => 'Questo non è un BIC valido.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Una regola deve avere almeno un trigger.', + 'at_least_one_action' => 'Una regola deve avere almeno una azione.', 'base64' => 'Questi non sono dati codificati in base64 validi.', 'model_id_invalid' => 'L\'ID fornito sembra non essere valido per questo modello.', 'more' => ':attribute deve essere maggiore di zero.', @@ -91,6 +91,9 @@ return [ 'min.array' => ':attribute deve avere almeno :min voci.', 'not_in' => ':attribute selezionato è invalido.', 'numeric' => ':attribute deve essere un numero.', + 'numeric_native' => 'L\'importo nativo deve essere un numero.', + 'numeric_destination' => 'L\'importo di destinazione deve essere un numero.', + 'numeric_source' => 'L\'importo sorgente deve essere un numero.', 'regex' => ':attribute formato non valido', 'required' => 'Il campo :attribute è obbligatorio.', 'required_if' => 'Il campo :attribute è obbligatorio quando :other è :value.', diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index 465daadb5e..add99c5012 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Deze waarde is ongeldig voor dit veld.', 'at_least_one_transaction' => 'Er is op zijn minst één transactie nodig.', 'at_least_one_repetition' => 'Er is op zijn minst één herhaling nodig.', - 'require_repeat_until' => 'Je moet een aantal herhalingen opgeven, of een einddatum (repeats_until). Niet beide.', + 'require_repeat_until' => 'Je moet een aantal herhalingen opgeven, of een einddatum (repeat_until). Niet beide.', 'require_currency_info' => 'De inhoud van dit veld is ongeldig zonder valutagegevens.', 'equal_description' => 'Transactiebeschrijving mag niet gelijk zijn aan globale beschrijving.', 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'De waarde van :attribute is onbekend.', 'accepted' => ':attribute moet geaccepteerd zijn.', 'bic' => 'Dit is geen geldige BIC.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'De regel moet minstens één trigger hebben.', + 'at_least_one_action' => 'De regel moet minstens één actie hebben.', 'base64' => 'Dit is geen geldige base64 gecodeerde data.', 'model_id_invalid' => 'Dit ID past niet bij dit object.', 'more' => ':attribute moet groter zijn dan nul.', @@ -91,6 +91,9 @@ return [ 'min.array' => ':attribute moet minimaal :min items bevatten.', 'not_in' => 'Het formaat van :attribute is ongeldig.', 'numeric' => ':attribute moet een nummer zijn.', + 'numeric_native' => 'Het originele bedrag moet een getal zijn.', + 'numeric_destination' => 'Het doelbedrag moet een getal zijn.', + 'numeric_source' => 'Het bronbedrag moet een getal zijn.', 'regex' => ':attribute formaat is ongeldig.', 'required' => ':attribute is verplicht.', 'required_if' => ':attribute is verplicht indien :other gelijk is aan :value.', diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index 40ea0ea2d8..2cd06c8b85 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Ta wartość jest nieprawidłowa dla tego pola.', 'at_least_one_transaction' => 'Wymaga co najmniej jednej transakcji.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Treść tego pola jest nieprawidłowa bez informacji o walucie.', 'equal_description' => 'Opis transakcji nie powinien być równy globalnemu opisowi.', 'file_invalid_mime' => 'Plik ":name" jest typu ":mime", który nie jest akceptowany jako nowy plik do przekazania.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => ':attribute musi zostać zaakceptowany.', 'bic' => 'To nie jest prawidłowy BIC.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute musi być większy od zera.', @@ -91,6 +91,9 @@ return [ 'min.array' => ':attribute musi zawierać przynajmniej :min elementów.', 'not_in' => 'Wybrany :attribute jest nieprawidłowy.', 'numeric' => ':attribute musi byc liczbą.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Format :attribute jest nieprawidłowy.', 'required' => 'Pole :attribute jest wymagane.', 'required_if' => 'Pole :attribute jest wymagane gdy :other jest :value.', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 48f3428533..eec02240af 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Esse valor é inválido para este campo.', 'at_least_one_transaction' => 'Precisa de ao menos uma transação.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'O conteúdo deste campo é inválido sem informações de moeda.', 'equal_description' => 'A descrição da transação não pode ser igual à descrição global.', 'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'O campo :attribute deve ser aceito.', 'bic' => 'Este não é um BIC válido.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute deve ser maior que zero.', @@ -91,6 +91,9 @@ return [ 'min.array' => 'O campo :attribute deve ter no mínimo :min itens.', 'not_in' => 'O campo :attribute contém um valor inválido.', 'numeric' => 'O campo :attribute deverá conter um valor numérico.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'O formato do valor para o campo :attribute é inválido.', 'required' => 'O campo :attribute é obrigatório.', 'required_if' => 'O campo :attribute é obrigatório quando o valor do campo :other é igual a :value.', diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index cf9e7ffbc7..e3fd87a974 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Данное значение недопустимо для этого поля.', 'at_least_one_transaction' => 'Необходима как минимум одна транзакция.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Содержимое этого поля недействительно без информации о валюте.', 'equal_description' => 'Описание транзакции не должно совпадать с глобальным описанием.', 'file_invalid_mime' => 'Файл ":name" имеет тип ":mime". Загрузка файлов такого типа невозможна.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => 'Необходимо принять :attribute.', 'bic' => 'Это некорректный BIC.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'This is not valid base64 encoded data.', 'model_id_invalid' => 'The given ID seems invalid for this model.', 'more' => ':attribute должен быть больше нуля.', @@ -91,6 +91,9 @@ return [ 'min.array' => 'Значение :attribute должно содержать не меньше :min элементов.', 'not_in' => 'Выбранный :attribute не верный.', 'numeric' => ':attribute должен быть числом.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => 'Формат :attribute некорректен.', 'required' => 'Поле :attribute является обязательным.', 'required_if' => 'Значение :attribute является обязательным, когда :other равное :value.', diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index 88ca42b1e9..9fa4b2ad3b 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -38,7 +38,7 @@ return [ 'belongs_user' => 'Bu değer bu alan için geçerli değil.', 'at_least_one_transaction' => 'En az bir işlem gerekir.', 'at_least_one_repetition' => 'Need at least one repetition.', - 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeats_until). Not both.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Bu alanın içeriği para birimi bilgileri geçersiz.', 'equal_description' => 'İşlem açıklaması genel açıklama eşit değildir.', 'file_invalid_mime' => '":name" dosyası ":mime" türünde olup yeni bir yükleme olarak kabul edilemez.', @@ -46,8 +46,8 @@ return [ 'belongs_to_user' => 'The value of :attribute is unknown.', 'accepted' => ':attribute kabul edilmek zorunda.', 'bic' => 'Bu BIC geçerli değilrdir.', - 'at_least_one_trigger' => 'Rule must have at least one trigger', - 'at_least_one_action' => 'Rule must have at least one action', + 'at_least_one_trigger' => 'Rule must have at least one trigger.', + 'at_least_one_action' => 'Rule must have at least one action.', 'base64' => 'Bu geçerli Base64 olarak kodlanmış veri değildir.', 'model_id_invalid' => 'Verilen kimlik bu model için geçersiz görünüyor.', 'more' => ':attribute sıfırdan büyük olmak zorundadır.', @@ -91,6 +91,9 @@ return [ 'min.array' => ':attribute en az :min öğe içermelidir.', 'not_in' => 'Seçili :attribute geçersiz.', 'numeric' => ':attribute sayı olmalıdır.', + 'numeric_native' => 'The native amount must be a number.', + 'numeric_destination' => 'The destination amount must be a number.', + 'numeric_source' => 'The source amount must be a number.', 'regex' => ':attribute biçimi geçersiz.', 'required' => ':attribute alanı gereklidir.', 'required_if' => ':other :value iken :attribute alanı gereklidir.', diff --git a/tests/Feature/Controllers/Import/IndexControllerTest.php b/tests/Feature/Controllers/Import/IndexControllerTest.php index 76026e0227..10b8e6a942 100644 --- a/tests/Feature/Controllers/Import/IndexControllerTest.php +++ b/tests/Feature/Controllers/Import/IndexControllerTest.php @@ -225,7 +225,7 @@ class IndexControllerTest extends TestCase $this->be($this->user()); $response = $this->get(route('import.job.download', [$job->key])); $response->assertStatus(200); - $response->assertExactJson(['column-mapping-config' => [], 'delimiter' => ',', 'hi' => 'there', 1 => true]); + $response->assertExactJson(['column-mapping-config' => ['a', 'b', 'c'], 'delimiter' => ',', 'hi' => 'there', 1 => true]); } /** diff --git a/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php b/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php index d101303717..60675e0aa5 100644 --- a/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php +++ b/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php @@ -342,8 +342,8 @@ class ImportableConverterTest extends TestCase $this->assertEquals($importable->billName, $result[0]['bill_name']); $this->assertEquals($usd->id, $result[0]['transactions'][0]['currency_id']); // since amount is positive, $asset recieves the money - $this->assertEquals($other->id, $result[0]['transactions'][0]['source_id']); - $this->assertEquals($asset->id, $result[0]['transactions'][0]['destination_id']); + $this->assertEquals($asset->id, $result[0]['transactions'][0]['source_id']); + $this->assertEquals($other->id, $result[0]['transactions'][0]['destination_id']); } /** @@ -412,8 +412,8 @@ class ImportableConverterTest extends TestCase $this->assertEquals($importable->billName, $result[0]['bill_name']); $this->assertEquals($usd->id, $result[0]['transactions'][0]['currency_id']); // since amount is negative, $asset sends the money - $this->assertEquals($asset->id, $result[0]['transactions'][0]['source_id']); - $this->assertEquals($other->id, $result[0]['transactions'][0]['destination_id']); + $this->assertEquals($other->id, $result[0]['transactions'][0]['source_id']); + $this->assertEquals($asset->id, $result[0]['transactions'][0]['destination_id']); } /** diff --git a/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php b/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php index 989660cfba..fe532b16b0 100644 --- a/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php +++ b/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php @@ -37,7 +37,14 @@ class HasAnyBudgetTest extends TestCase */ public function testTriggered(): void { - $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); + $loop = 0; + do { + /** @var TransactionJournal $journal */ + $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); + $count = $journal->transactions()->count(); + $loop++; + } while ($count !== 0 && $loop < 30); + $budget = $journal->user->budgets()->first(); $journal->budgets()->detach(); $journal->budgets()->save($budget); @@ -46,6 +53,7 @@ class HasAnyBudgetTest extends TestCase $trigger = HasAnyBudget::makeFromStrings('', false); $result = $trigger->triggered($journal); $this->assertTrue($result); + } /** @@ -53,11 +61,13 @@ class HasAnyBudgetTest extends TestCase */ public function testTriggeredNot(): void { + $loop = 0; do { /** @var TransactionJournal $journal */ $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); $count = $journal->transactions()->count(); - } while ($count !== 0); + $loop++; + } while ($count !== 0 && $loop < 30); $journal->budgets()->detach(); $this->assertEquals(0, $journal->budgets()->count()); @@ -78,7 +88,14 @@ class HasAnyBudgetTest extends TestCase */ public function testTriggeredTransactions(): void { - $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); + $loop = 0; + do { + /** @var TransactionJournal $journal */ + $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); + $count = $journal->transactions()->count(); + $loop++; + } while ($count !== 0 && $loop < 30); + $budget = $journal->user->budgets()->first(); $journal->budgets()->detach(); $this->assertEquals(0, $journal->budgets()->count()); From 8eb4259be0b86b8d1904e8706dfe053142f25a94 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 19:04:46 +0200 Subject: [PATCH 130/134] Last minute test fixes. --- .../Triggers/HasAnyBudget.php | 2 +- .../Triggers/HasAnyBudgetTest.php | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/TransactionRules/Triggers/HasAnyBudget.php b/app/TransactionRules/Triggers/HasAnyBudget.php index 46bd011a64..224c4b9212 100644 --- a/app/TransactionRules/Triggers/HasAnyBudget.php +++ b/app/TransactionRules/Triggers/HasAnyBudget.php @@ -81,7 +81,7 @@ final class HasAnyBudget extends AbstractTrigger implements TriggerInterface } } - Log::debug(sprintf('RuleTrigger HasAnyBudget for journal #%d: count is %d, return false.', $journal->id, $count)); + Log::debug(sprintf('RuleTrigger HasAnyBudget for journal #%d: final is false.', $journal->id)); return false; } diff --git a/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php b/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php index fe532b16b0..d983ed8c64 100644 --- a/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php +++ b/tests/Unit/TransactionRules/Triggers/HasAnyBudgetTest.php @@ -25,6 +25,7 @@ namespace Tests\Unit\TransactionRules\Triggers; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\TransactionRules\Triggers\HasAnyBudget; +use Log; use Tests\TestCase; /** @@ -43,9 +44,9 @@ class HasAnyBudgetTest extends TestCase $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); $count = $journal->transactions()->count(); $loop++; - } while ($count !== 0 && $loop < 30); + } while ($count !== 2 && $loop < 30); - $budget = $journal->user->budgets()->first(); + $budget = $journal->user->budgets()->first(); $journal->budgets()->detach(); $journal->budgets()->save($budget); @@ -67,7 +68,7 @@ class HasAnyBudgetTest extends TestCase $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); $count = $journal->transactions()->count(); $loop++; - } while ($count !== 0 && $loop < 30); + } while ($count !== 2 && $loop < 30); $journal->budgets()->detach(); $this->assertEquals(0, $journal->budgets()->count()); @@ -88,26 +89,37 @@ class HasAnyBudgetTest extends TestCase */ public function testTriggeredTransactions(): void { + Log::debug('Now in testTriggeredTransactions()'); $loop = 0; do { + Log::debug(sprintf('Loop is now at #%d', $loop)); /** @var TransactionJournal $journal */ $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); $count = $journal->transactions()->count(); - $loop++; - } while ($count !== 0 && $loop < 30); - $budget = $journal->user->budgets()->first(); + Log::debug(sprintf('Found journal #%d with %d transactions', $journal->id, $count)); + + $loop++; + } while ($count !== 2 && $loop < 30); + Log::debug('end of loop!'); + + $budget = $journal->user->budgets()->first(); + Log::debug(sprintf('First budget is %d ("%s")', $budget->id, $budget->name)); $journal->budgets()->detach(); $this->assertEquals(0, $journal->budgets()->count()); + Log::debug('Survived the assumption.'); // append to transaction + Log::debug('Do transaction loop.'); foreach ($journal->transactions()->get() as $index => $transaction) { + Log::debug(sprintf('Now at index #%d, transaction #%d', $index, $transaction->id)); $transaction->budgets()->detach(); if (0 === $index) { + Log::debug('Index is zero, attach budget.'); $transaction->budgets()->save($budget); } } - + Log::debug('Done with loop, make trigger'); $trigger = HasAnyBudget::makeFromStrings('', false); $result = $trigger->triggered($journal); $this->assertTrue($result); From 9f69e112d040f28157a8f9e8271d29868b4616d4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 19:34:13 +0200 Subject: [PATCH 131/134] Last minute fixes. --- app/Http/Controllers/RuleController.php | 48 ++++++++++++------- .../Prerequisites/BunqPrerequisites.php | 1 + 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index 5704a28d89..4ec76ebee9 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -549,23 +549,39 @@ class RuleController extends Controller { if (0 === $this->ruleRepos->count()) { $data = [ - 'rule_group_id' => $this->ruleRepos->getFirstRuleGroup()->id, - 'stop_processing' => 0, - 'title' => trans('firefly.default_rule_name'), - 'description' => trans('firefly.default_rule_description'), - 'trigger' => 'store-journal', - 'strict' => true, - 'rule-trigger-values' => [ - trans('firefly.default_rule_trigger_description'), - trans('firefly.default_rule_trigger_from_account'), - ], - 'rule-action-values' => [ - trans('firefly.default_rule_action_prepend'), - trans('firefly.default_rule_action_set_category'), - ], + 'rule_group_id' => $this->ruleRepos->getFirstRuleGroup()->id, + 'stop-processing' => 0, + 'title' => trans('firefly.default_rule_name'), + 'description' => trans('firefly.default_rule_description'), + 'trigger' => 'store-journal', + 'strict' => true, + 'rule-triggers' => [ + [ + 'name' => 'description_is', + 'value' => trans('firefly.default_rule_trigger_description'), + 'stop-processing' => false, - 'rule-triggers' => ['description_is', 'from_account_is'], - 'rule-actions' => ['prepend_description', 'set_category'], + ], + [ + 'name' => 'from_account_is', + 'value' => trans('firefly.default_rule_trigger_from_account'), + 'stop-processing' => false, + + ], + + ], + 'rule-actions' => [ + [ + 'name' => 'prepend_description', + 'value' => trans('firefly.default_rule_action_prepend'), + 'stop-processing' => false, + ], + [ + 'name' => 'set_category', + 'value' => trans('firefly.default_rule_action_set_category'), + 'stop-processing' => false, + ], + ], ]; $this->ruleRepos->store($data); diff --git a/app/Import/Prerequisites/BunqPrerequisites.php b/app/Import/Prerequisites/BunqPrerequisites.php index 96fd3ad672..21334ced35 100644 --- a/app/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Import/Prerequisites/BunqPrerequisites.php @@ -116,6 +116,7 @@ class BunqPrerequisites implements PrerequisitesInterface $environment = $this->getBunqEnvironment(); $deviceDescription = 'Firefly III v' . config('firefly.version'); $permittedIps = [$externalIP]; + Log::debug(sprintf('Environment for bunq is %s', $environment->getChoiceString())); try { /** @var ApiContext $object */ From 19f38aa6ed59dd1b5fb07d3d984d2d213fd4f192 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 19:57:38 +0200 Subject: [PATCH 132/134] Updated everything for version 4.7.5 --- .sandstorm/changelog.md | 19 +++ .sandstorm/sandstorm-pkgdef.capnp | 4 +- changelog.md | 25 ++++ composer.lock | 221 ++++++++++++++++-------------- readme.md | 46 +++---- 5 files changed, 186 insertions(+), 129 deletions(-) diff --git a/.sandstorm/changelog.md b/.sandstorm/changelog.md index c46086da90..c6415e473d 100644 --- a/.sandstorm/changelog.md +++ b/.sandstorm/changelog.md @@ -1,3 +1,22 @@ +# 4.7.5 +- A new feature called "recurring transactions" that will make Firefly III automatically create transactions for you. +- New API end points for attachments, available budgets, budgets, budget limits, categories, configuration, currency exchange rates, journal links, link types, piggy banks, preferences, recurring transactions, rules, rule groups and tags. +- Added support for YunoHost. +- The 2FA secret is visible so you can type it into 2FA apps. +- Bunq and Spectre imports will now ask to apply rules. +- Sandstorm users can now make API keys. +- Various typo's in the English translations. [issue 1493](https://github.com/firefly-iii/firefly-iii/issues/1493) +- Bug where Spectre was never called [issue 1492](https://github.com/firefly-iii/firefly-iii/issues/1492) +- Clear cache after journal is created through API [issue 1483](https://github.com/firefly-iii/firefly-iii/issues/1483) +- Make sure docker directories exist [issue 1500](https://github.com/firefly-iii/firefly-iii/issues/1500) +- Broken link to bill edit [issue 1505](https://github.com/firefly-iii/firefly-iii/issues/1505) +- Several bugs in the editing of split transactions [issue 1509](https://github.com/firefly-iii/firefly-iii/issues/1509) +- Import routine ignored formatting of several date fields [issue 1510](https://github.com/firefly-iii/firefly-iii/issues/1510) +- Piggy bank events now show the correct currency [issue 1446](https://github.com/firefly-iii/firefly-iii/issues/1446) +- Inactive accounts are no longer suggested [issue 1463](https://github.com/firefly-iii/firefly-iii/issues/1463) +- Some income / expense charts are less confusing [issue 1518](https://github.com/firefly-iii/firefly-iii/issues/1518) +- Validation bug in multi-currency create view [issue 1521](https://github.com/firefly-iii/firefly-iii/issues/1521) + # 4.7.4 - [Issue 1409](https://github.com/firefly-iii/firefly-iii/issues/1409), add Indian Rupee and explain that users can do this themselves [issue 1413](https://github.com/firefly-iii/firefly-iii/issues/1413) - [Issue 1445](https://github.com/firefly-iii/firefly-iii/issues/1445), upgrade Curl in Docker image. diff --git a/.sandstorm/sandstorm-pkgdef.capnp b/.sandstorm/sandstorm-pkgdef.capnp index 1a8b1f1c97..3f86b4e979 100644 --- a/.sandstorm/sandstorm-pkgdef.capnp +++ b/.sandstorm/sandstorm-pkgdef.capnp @@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = ( manifest = ( appTitle = (defaultText = "Firefly III"), - appVersion = 13, - appMarketingVersion = (defaultText = "4.7.4"), + appVersion = 14, + appMarketingVersion = (defaultText = "4.7.5"), actions = [ # Define your "new document" handlers here. diff --git a/changelog.md b/changelog.md index d16497a54c..6358b2b40b 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.7.5] - 2018-07-02 +### Added +- A new feature called "recurring transactions" that will make Firefly III automatically create transactions for you. +- New API end points for attachments, available budgets, budgets, budget limits, categories, configuration, currency exchange rates, journal links, link types, piggy banks, preferences, recurring transactions, rules, rule groups and tags. +- Added support for YunoHost. + +### Changed +- The 2FA secret is visible so you can type it into 2FA apps. +- Bunq and Spectre imports will now ask to apply rules. +- Sandstorm users can now make API keys. + +### Fixed +- Various typo's in the English translations. [issue 1493](https://github.com/firefly-iii/firefly-iii/issues/1493) +- Bug where Spectre was never called [issue 1492](https://github.com/firefly-iii/firefly-iii/issues/1492) +- Clear cache after journal is created through API [issue 1483](https://github.com/firefly-iii/firefly-iii/issues/1483) +- Make sure docker directories exist [issue 1500](https://github.com/firefly-iii/firefly-iii/issues/1500) +- Broken link to bill edit [issue 1505](https://github.com/firefly-iii/firefly-iii/issues/1505) +- Several bugs in the editing of split transactions [issue 1509](https://github.com/firefly-iii/firefly-iii/issues/1509) +- Import routine ignored formatting of several date fields [issue 1510](https://github.com/firefly-iii/firefly-iii/issues/1510) +- Piggy bank events now show the correct currency [issue 1446](https://github.com/firefly-iii/firefly-iii/issues/1446) +- Inactive accounts are no longer suggested [issue 1463](https://github.com/firefly-iii/firefly-iii/issues/1463) +- Some income / expense charts are less confusing [issue 1518](https://github.com/firefly-iii/firefly-iii/issues/1518) +- Validation bug in multi-currency create view [issue 1521](https://github.com/firefly-iii/firefly-iii/issues/1521) +- Bug where imported transfers would be stored incorrectly. + ## [4.7.4] - 2015-06-03 ### Added - [Issue 1409](https://github.com/firefly-iii/firefly-iii/issues/1409), add Indian Rupee and explain that users can do this themselves [issue 1413](https://github.com/firefly-iii/firefly-iii/issues/1413) diff --git a/composer.lock b/composer.lock index ca02e5a1b9..a37ccfe587 100644 --- a/composer.lock +++ b/composer.lock @@ -1150,16 +1150,16 @@ }, { "name": "laravel/framework", - "version": "v5.6.25", + "version": "v5.6.26", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "a15efe4fbcd6b38ea76edc5c8939ffde4f4c7b7f" + "reference": "7047df295e77cecb6a2f84736a732af66cc6789c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/a15efe4fbcd6b38ea76edc5c8939ffde4f4c7b7f", - "reference": "a15efe4fbcd6b38ea76edc5c8939ffde4f4c7b7f", + "url": "https://api.github.com/repos/laravel/framework/zipball/7047df295e77cecb6a2f84736a732af66cc6789c", + "reference": "7047df295e77cecb6a2f84736a732af66cc6789c", "shasum": "" }, "require": { @@ -1285,7 +1285,7 @@ "framework", "laravel" ], - "time": "2018-06-12T14:39:24+00:00" + "time": "2018-06-20T14:21:11+00:00" }, { "name": "laravel/passport", @@ -1358,16 +1358,16 @@ }, { "name": "laravelcollective/html", - "version": "v5.6.9", + "version": "v5.6.10", "source": { "type": "git", "url": "https://github.com/LaravelCollective/html.git", - "reference": "fda9d3dad85ecea609ef9c6323d6923536cf5643" + "reference": "974605fcd22a7e4d19f0b2ef635a0d1d7400387d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/fda9d3dad85ecea609ef9c6323d6923536cf5643", - "reference": "fda9d3dad85ecea609ef9c6323d6923536cf5643", + "url": "https://api.github.com/repos/LaravelCollective/html/zipball/974605fcd22a7e4d19f0b2ef635a0d1d7400387d", + "reference": "974605fcd22a7e4d19f0b2ef635a0d1d7400387d", "shasum": "" }, "require": { @@ -1422,7 +1422,7 @@ ], "description": "HTML and Form Builders for the Laravel Framework", "homepage": "https://laravelcollective.com", - "time": "2018-05-30T16:09:07+00:00" + "time": "2018-06-18T15:04:16+00:00" }, { "name": "lcobucci/jwt", @@ -2698,16 +2698,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v6.0.2", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc" + "reference": "0ff595e1d9d7d1c929b2a5f7b774bbcfbbd81587" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/412333372fb6c8ffb65496a2bbd7321af75733fc", - "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/0ff595e1d9d7d1c929b2a5f7b774bbcfbbd81587", + "reference": "0ff595e1d9d7d1c929b2a5f7b774bbcfbbd81587", "shasum": "" }, "require": { @@ -2718,10 +2718,14 @@ "mockery/mockery": "~0.9.1", "symfony/phpunit-bridge": "~3.3@dev" }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses", + "true/punycode": "Needed to support internationalized email addresses, if ext-intl is not installed" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -2743,26 +2747,26 @@ } ], "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "http://swiftmailer.symfony.com", + "homepage": "https://swiftmailer.symfony.com", "keywords": [ "email", "mail", "mailer" ], - "time": "2017-09-30T22:39:41+00:00" + "time": "2018-07-02T20:24:38+00:00" }, { "name": "symfony/console", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242" + "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2d5d973bf9933d46802b01010bd25c800c87c242", - "reference": "2d5d973bf9933d46802b01010bd25c800c87c242", + "url": "https://api.github.com/repos/symfony/console/zipball/70591cda56b4b47c55776ac78e157c4bb6c8b43f", + "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f", "shasum": "" }, "require": { @@ -2817,11 +2821,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/css-selector", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2874,16 +2878,16 @@ }, { "name": "symfony/debug", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b" + "reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/449f8b00b28ab6e6912c3e6b920406143b27193b", - "reference": "449f8b00b28ab6e6912c3e6b920406143b27193b", + "url": "https://api.github.com/repos/symfony/debug/zipball/dbe0fad88046a755dcf9379f2964c61a02f5ae3d", + "reference": "dbe0fad88046a755dcf9379f2964c61a02f5ae3d", "shasum": "" }, "require": { @@ -2926,11 +2930,11 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-08T09:39:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2993,16 +2997,16 @@ }, { "name": "symfony/finder", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238" + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/087e2ee0d74464a4c6baac4e90417db7477dc238", - "reference": "087e2ee0d74464a4c6baac4e90417db7477dc238", + "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", "shasum": "" }, "require": { @@ -3038,20 +3042,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99" + "reference": "4f9c7cf962e635b0b26b14500ac046e07dbef7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a916c88390fb861ee21f12a92b107d51bb68af99", - "reference": "a916c88390fb861ee21f12a92b107d51bb68af99", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4f9c7cf962e635b0b26b14500ac046e07dbef7f3", + "reference": "4f9c7cf962e635b0b26b14500ac046e07dbef7f3", "shasum": "" }, "require": { @@ -3092,20 +3096,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-05-25T14:55:38+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90" + "reference": "29c094a1c4f8209b7e033f612cbbd69029e38955" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", - "reference": "b5ab9d4cdbfd369083744b6b5dfbf454e31e5f90", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/29c094a1c4f8209b7e033f612cbbd69029e38955", + "reference": "29c094a1c4f8209b7e033f612cbbd69029e38955", "shasum": "" }, "require": { @@ -3113,13 +3117,13 @@ "psr/log": "~1.0", "symfony/debug": "~3.4|~4.0", "symfony/event-dispatcher": "~4.1", - "symfony/http-foundation": "~4.1", + "symfony/http-foundation": "^4.1.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/config": "<3.4", "symfony/dependency-injection": "<4.1", - "symfony/var-dumper": "<4.1", + "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "provide": { @@ -3140,7 +3144,7 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", - "symfony/var-dumper": "~4.1" + "symfony/var-dumper": "^4.1.1" }, "suggest": { "symfony/browser-kit": "", @@ -3179,7 +3183,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-05-30T12:52:34+00:00" + "time": "2018-06-25T13:06:45+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3460,16 +3464,16 @@ }, { "name": "symfony/process", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434" + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/73445bd33b0d337c060eef9652b94df72b6b3434", - "reference": "73445bd33b0d337c060eef9652b94df72b6b3434", + "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", "shasum": "" }, "require": { @@ -3505,7 +3509,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -3569,16 +3573,16 @@ }, { "name": "symfony/routing", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2" + "reference": "b38b9797327b26ea2e4146a40e6e2dc9820a6932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/180b51c66d10f09e562c9ebc395b39aacb2cf8a2", - "reference": "180b51c66d10f09e562c9ebc395b39aacb2cf8a2", + "url": "https://api.github.com/repos/symfony/routing/zipball/b38b9797327b26ea2e4146a40e6e2dc9820a6932", + "reference": "b38b9797327b26ea2e4146a40e6e2dc9820a6932", "shasum": "" }, "require": { @@ -3591,7 +3595,6 @@ }, "require-dev": { "doctrine/annotations": "~1.0", - "doctrine/common": "~2.2", "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", @@ -3643,20 +3646,20 @@ "uri", "url" ], - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { "name": "symfony/translation", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a" + "reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", - "reference": "16328f5b217cebc8dd4adfe4aeeaa8c377581f5a", + "url": "https://api.github.com/repos/symfony/translation/zipball/b6d8164085ee0b6debcd1b7a131fd6f63bb04854", + "reference": "b6d8164085ee0b6debcd1b7a131fd6f63bb04854", "shasum": "" }, "require": { @@ -3712,20 +3715,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-06-22T08:59:39+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64" + "reference": "b2eebaec085d1f2cafbad7644733d494a3bbbc9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/bc88ad53e825ebacc7b190bbd360781fce381c64", - "reference": "bc88ad53e825ebacc7b190bbd360781fce381c64", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b2eebaec085d1f2cafbad7644733d494a3bbbc9b", + "reference": "b2eebaec085d1f2cafbad7644733d494a3bbbc9b", "shasum": "" }, "require": { @@ -3787,7 +3790,7 @@ "debug", "dump" ], - "time": "2018-04-29T07:56:09+00:00" + "time": "2018-06-23T12:23:56+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -3903,28 +3906,28 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", + "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^4.8.35 || ^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -3934,7 +3937,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause-Attribution" + "BSD-3-Clause" ], "authors": [ { @@ -3949,20 +3952,20 @@ "env", "environment" ], - "time": "2016-09-01T10:05:43+00:00" + "time": "2018-07-01T10:25:50+00:00" }, { "name": "zendframework/zend-diactoros", - "version": "1.7.2", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "741e7a571836f038de731105f4742ca8a164e43a" + "reference": "11c9c1835e60eef6f9234377a480fcec096ebd9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/741e7a571836f038de731105f4742ca8a164e43a", - "reference": "741e7a571836f038de731105f4742ca8a164e43a", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/11c9c1835e60eef6f9234377a480fcec096ebd9e", + "reference": "11c9c1835e60eef6f9234377a480fcec096ebd9e", "shasum": "" }, "require": { @@ -3981,12 +3984,22 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev", - "dev-develop": "1.8.x-dev", + "dev-master": "1.8.x-dev", + "dev-develop": "1.9.x-dev", "dev-release-2.0": "2.0.x-dev" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], "psr-4": { "Zend\\Diactoros\\": "src/" } @@ -4002,7 +4015,7 @@ "psr", "psr-7" ], - "time": "2018-05-29T16:53:08+00:00" + "time": "2018-06-27T18:52:43+00:00" } ], "packages-dev": [ @@ -5154,16 +5167,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.2.4", + "version": "7.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd" + "reference": "400a3836ee549ae6f665323ac3f21e27eac7155f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/00bc0b93f0ff4f557e9ea766557fde96da9a03dd", - "reference": "00bc0b93f0ff4f557e9ea766557fde96da9a03dd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/400a3836ee549ae6f665323ac3f21e27eac7155f", + "reference": "400a3836ee549ae6f665323ac3f21e27eac7155f", "shasum": "" }, "require": { @@ -5179,7 +5192,7 @@ "php": "^7.1", "phpspec/prophecy": "^1.7", "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0", + "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^2.0", "sebastian/comparator": "^3.0", @@ -5234,7 +5247,7 @@ "testing", "xunit" ], - "time": "2018-06-05T03:40:05+00:00" + "time": "2018-06-21T13:13:39+00:00" }, { "name": "roave/security-advisories", @@ -5242,12 +5255,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0e4ea9f9e1fd3c6a563524f8f399696d98c7c85a" + "reference": "5c802c6300dca269edde06c6ae8c7eb561de3176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0e4ea9f9e1fd3c6a563524f8f399696d98c7c85a", - "reference": "0e4ea9f9e1fd3c6a563524f8f399696d98c7c85a", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5c802c6300dca269edde06c6ae8c7eb561de3176", + "reference": "5c802c6300dca269edde06c6ae8c7eb561de3176", "shasum": "" }, "conflict": { @@ -5298,7 +5311,7 @@ "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "magento/magento1ce": ">=1.5.0.1,<1.9.3.2", "magento/magento1ee": ">=1.9,<1.14.3.2", - "magento/magento2ce": ">=2,<2.2", + "magento/magento2ce": ">=2,<2.3", "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", @@ -5349,7 +5362,7 @@ "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.2|>=2.1.0-beta1,<2.1.3", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", "titon/framework": ">=0,<9.9.99", "twig/twig": "<1.20", "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", @@ -5401,7 +5414,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-06-08T09:55:50+00:00" + "time": "2018-07-03T10:42:36+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5968,7 +5981,7 @@ }, { "name": "symfony/class-loader", - "version": "v3.4.11", + "version": "v3.4.12", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", @@ -6024,16 +6037,16 @@ }, { "name": "symfony/config", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4" + "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/5ceefc256caecc3e25147c4e5b933de71d0020c4", - "reference": "5ceefc256caecc3e25147c4e5b933de71d0020c4", + "url": "https://api.github.com/repos/symfony/config/zipball/e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", + "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", "shasum": "" }, "require": { @@ -6083,11 +6096,11 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-05-16T14:33:22+00:00" + "time": "2018-06-20T11:15:17+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -6137,7 +6150,7 @@ }, { "name": "symfony/stopwatch", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -6186,7 +6199,7 @@ }, { "name": "symfony/yaml", - "version": "v4.1.0", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", diff --git a/readme.md b/readme.md index cad2ab8f0a..f6cb936fa2 100644 --- a/readme.md +++ b/readme.md @@ -42,38 +42,38 @@ Personal financial management is pretty difficult, and everybody has their own a By keeping track of your expenses and your income you can budget accordingly and save money. Stop living from paycheck to paycheck but give yourself the financial wiggle room you need. -You can read more about this in the [official documentation](http://firefly-iii.readthedocs.io/en/latest/index.html). +You can read more about this in the [official documentation](https://firefly-iii.readthedocs.io/en/latest/index.html). ### Features Most importantly... * Firefly III runs on your own server, so you are fully in control of your data. It will not contact other sites or servers. -* You can import from over 2500 financial providers, in 55 countries when you enable the [Spectre API](http://firefly-iii.readthedocs.io/en/latest/import/spectre.html). +* You can import from over 2500 financial providers, in 55 countries when you enable the [Spectre API](https://firefly-iii.readthedocs.io/en/latest/import/spectre.html). * You can import from [bunq](https://www.bunq.com/). * You can import CSV files from practically any bank. -* Firefly III features an JSON REST API. +* Firefly III features an [JSON REST API](https://firefly-iii.readthedocs.io/en/latest/api/start.html). * If you feel you’re missing something you can just ask me and I’ll add it! But actually, it features: -* [A double-entry bookkeeping system](http://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html) -* You can store, edit and remove [withdrawals, deposits and transfers](http://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html). This allows you full financial management +* [A double-entry bookkeeping system](https://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html) +* You can store, edit and remove [withdrawals, deposits and transfers](https://firefly-iii.readthedocs.io/en/latest/concepts/transactions.html). This allows you full financial management * You can manage different types of accounts - * [Asset](http://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) accounts - * Shared [asset accounts](http://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) ([household accounts](http://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html)) + * [Asset](https://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) accounts + * Shared [asset accounts](https://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html) ([household accounts](https://firefly-iii.readthedocs.io/en/latest/concepts/accounts.html)) * Saving accounts * Credit cards -* It's possible to create, change and manage money using [budgets](http://firefly-iii.readthedocs.io/en/latest/concepts/budgets.html) -* Organize transactions using [categories](http://firefly-iii.readthedocs.io/en/latest/concepts/categories.html) -* Save towards a goal using [piggy banks](http://firefly-iii.readthedocs.io/en/latest/advanced/piggies.html) -* Predict and anticipate [bills](http://firefly-iii.readthedocs.io/en/latest/advanced/bills.html) -* View income / expense [reports](http://firefly-iii.readthedocs.io/en/latest/advanced/reports.html) -* [Rule based](http://firefly-iii.readthedocs.io/en/latest/advanced/rules.html) transaction handling with the ability to create your own rules. -* The ability to [export data](http://firefly-iii.readthedocs.io/en/latest/import/export.html) so you can move to another system. -* The ability to [import data](http://firefly-iii.readthedocs.io/en/latest/import/csv.html) so you can move _from_ another system. -* Organize expenses using [tags](http://firefly-iii.readthedocs.io/en/latest/concepts/tags.html) +* It's possible to create, change and manage money using [budgets](https://firefly-iii.readthedocs.io/en/latest/concepts/budgets.html) +* Organize transactions using [categories](https://firefly-iii.readthedocs.io/en/latest/concepts/categories.html) +* Save towards a goal using [piggy banks](https://firefly-iii.readthedocs.io/en/latest/advanced/piggies.html) +* Predict and anticipate [bills](https://firefly-iii.readthedocs.io/en/latest/advanced/bills.html) +* View income / expense [reports](https://firefly-iii.readthedocs.io/en/latest/advanced/reports.html) +* [Rule based](https://firefly-iii.readthedocs.io/en/latest/advanced/rules.html) transaction handling with the ability to create your own rules. +* The ability to [export data](https://firefly-iii.readthedocs.io/en/latest/import/export.html) so you can move to another system. +* The ability to [import data](https://firefly-iii.readthedocs.io/en/latest/import/csv.html) so you can move _from_ another system. +* Organize expenses using [tags](https://firefly-iii.readthedocs.io/en/latest/concepts/tags.html) * 2 factor authentication for extra security 🔒 -* Supports any currency you want, including [crypto currencies](http://firefly-iii.readthedocs.io/en/latest/concepts/currencies.html) such as ₿itcoin and Ξthereum +* Supports any currency you want, including [crypto currencies](https://firefly-iii.readthedocs.io/en/latest/concepts/currencies.html) such as ₿itcoin and Ξthereum * Lots of help text in case you don’t get it * Translations into 10(!) languages, proudly powered by Crowdin @@ -92,11 +92,11 @@ This application is for people who want to track their finances, keep an eye on ## Get started There are many ways to run Firefly III 1. There is a [demo site](https://demo.firefly-iii.org) with an example financial administration already present. -2. You can [install it on your server](http://firefly-iii.readthedocs.io/en/latest/installation/server.html). -3. You can [run it using Docker](http://firefly-iii.readthedocs.io/en/latest/installation/docker.html). +2. You can [install it on your server](https://firefly-iii.readthedocs.io/en/latest/installation/server.html). +3. You can [run it using Docker](https://firefly-iii.readthedocs.io/en/latest/installation/docker.html). 4. You can [deploy to Heroku](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master) 5. You can [deploy to Sandstorm.io](https://apps.sandstorm.io/app/uws252ya9mep4t77tevn85333xzsgrpgth8q4y1rhknn1hammw70) -6. You can [install it using Softaculous](https://softaculous.com/). These guys even have made [another demo site](http://www.softaculous.com/softaculous/apps/others/Firefly_III)! +6. You can [install it using Softaculous](https://softaculous.com/). These guys even have made [another demo site](https://www.softaculous.com/softaculous/apps/others/Firefly_III)! 7. You can [install it using AMPPS](https://www.ampps.com/) 8. You can [install it with YunoHost](https://install-app.yunohost.org/?app=firefly-iii). 9. *Even more options are on the way!* @@ -109,7 +109,7 @@ Your help is always welcome! Feel free to open issues, ask questions, talk about Of course there are some [contributing guidelines](https://github.com/firefly-iii/firefly-iii/blob/master/.github/contributing.md) and a [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/master/.github/code_of_conduct.md), which I invite you to check out. -I can always use your help [squashing bugs](http://firefly-iii.readthedocs.io/en/latest/support/contribute.html#bugs), thinking about [new features](http://firefly-iii.readthedocs.io/en/latest/support/contribute.html#feature-requests) or [translating Firefly III](http://firefly-iii.readthedocs.io/en/latest/support/contribute.html#translations) into other languages. +I can always use your help [squashing bugs](https://firefly-iii.readthedocs.io/en/latest/support/contribute.html#bugs), thinking about [new features](https://firefly-iii.readthedocs.io/en/latest/support/contribute.html#feature-requests) or [translating Firefly III](https://firefly-iii.readthedocs.io/en/latest/support/contribute.html#translations) into other languages. For all other contributions, see below. @@ -125,7 +125,7 @@ Over time, [many people have contributed to Firefly III](https://github.com/fire ## Other stuff ### Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository. +We use [SemVer](https://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository. ### License This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under the [GPL v3](https://www.gnu.org/licenses/gpl.html). @@ -141,4 +141,4 @@ If you are looking for alternatives, check out [Kickball's Awesome-Selfhosted li ### Badges I like badges! -[![Travis branch](https://img.shields.io/travis/firefly-iii/firefly-iii/master.svg?style=flat-square)](https://travis-ci.org/firefly-iii/firefly-iii/branches) [![Scrutinizer](https://img.shields.io/scrutinizer/g/firefly-iii/firefly-iii.svg?style=flat-square)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/) [![Coveralls github branch](https://img.shields.io/coveralls/github/firefly-iii/firefly-iii/master.svg?style=flat-square)](https://coveralls.io/github/firefly-iii/firefly-iii) [![Requires PHP7.1](https://img.shields.io/badge/php-7.1-red.svg?style=flat-square)](https://secure.php.net/downloads.php) [![license](https://img.shields.io/github/license/firefly-iii/firefly-iii.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![Patreon page](https://img.shields.io/badge/patreon-JC5-brightgreen.svg?longCache=true&style=flat-square)](http://patreon.com/JC5) +[![Travis branch](https://img.shields.io/travis/firefly-iii/firefly-iii/master.svg?style=flat-square)](https://travis-ci.org/firefly-iii/firefly-iii/branches) [![Scrutinizer](https://img.shields.io/scrutinizer/g/firefly-iii/firefly-iii.svg?style=flat-square)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/) [![Coveralls github branch](https://img.shields.io/coveralls/github/firefly-iii/firefly-iii/master.svg?style=flat-square)](https://coveralls.io/github/firefly-iii/firefly-iii) [![Requires PHP7.1](https://img.shields.io/badge/php-7.1-red.svg?style=flat-square)](https://secure.php.net/downloads.php) [![license](https://img.shields.io/github/license/firefly-iii/firefly-iii.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![Patreon page](https://img.shields.io/badge/patreon-JC5-brightgreen.svg?longCache=true&style=flat-square)](https://patreon.com/JC5) From cf56707b025f28f3930257f127679bc22f82f13b Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 20:10:14 +0200 Subject: [PATCH 133/134] Add more fake entropy. --- .../Import/JobConfigurationControllerTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php b/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php index 07e9769075..da05848ba2 100644 --- a/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php +++ b/tests/Feature/Controllers/Import/JobConfigurationControllerTest.php @@ -53,7 +53,7 @@ class JobConfigurationControllerTest extends TestCase { $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Afake_job_' . random_int(1, 1000); + $job->key = '1Afake_job_' . random_int(1, 1000); $job->status = 'has_prereq'; $job->provider = 'fake'; $job->file_type = ''; @@ -85,7 +85,7 @@ class JobConfigurationControllerTest extends TestCase { $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Bfake_job_' . random_int(1, 1000); + $job->key = '2Bfake_job_' . random_int(1, 1000); $job->status = 'some_bad_state'; $job->provider = 'fake'; $job->file_type = ''; @@ -111,7 +111,7 @@ class JobConfigurationControllerTest extends TestCase { $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Cfake_job_' . random_int(1, 1000); + $job->key = '3Cfake_job_' . random_int(1, 1000); $job->status = 'has_prereq'; $job->provider = 'fake'; $job->file_type = ''; @@ -140,7 +140,7 @@ class JobConfigurationControllerTest extends TestCase $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Dfake_job_' . random_int(1, 1000); + $job->key = '4Dfake_job_' . random_int(1, 1000); $job->status = 'has_prereq'; $job->provider = 'fake'; $job->file_type = ''; @@ -174,7 +174,7 @@ class JobConfigurationControllerTest extends TestCase $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Ffake_job_' . random_int(1, 1000); + $job->key = '5Ffake_job_' . random_int(1, 1000); $job->status = 'some_bad_state'; $job->provider = 'fake'; $job->file_type = ''; @@ -203,7 +203,7 @@ class JobConfigurationControllerTest extends TestCase $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Efake_job_' . random_int(1, 1000); + $job->key = '6Efake_job_' . random_int(1, 1000); $job->status = 'has_prereq'; $job->provider = 'fake'; $job->file_type = ''; @@ -233,7 +233,7 @@ class JobConfigurationControllerTest extends TestCase $file = UploadedFile::fake()->image('avatar.jpg'); $job = new ImportJob; $job->user_id = $this->user()->id; - $job->key = 'Dfake_job_' . random_int(1, 1000); + $job->key = '7Dfake_job_' . random_int(1, 1000); $job->status = 'has_prereq'; $job->provider = 'fake'; $job->file_type = ''; From 339fb5099f08f4e644f7df2fda3e2c6463a8ea89 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 3 Jul 2018 20:33:12 +0200 Subject: [PATCH 134/134] Fix test. --- .../TransactionRules/Triggers/ToAccountStartsTest.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/Unit/TransactionRules/Triggers/ToAccountStartsTest.php b/tests/Unit/TransactionRules/Triggers/ToAccountStartsTest.php index bc5a23f749..fc24e98010 100644 --- a/tests/Unit/TransactionRules/Triggers/ToAccountStartsTest.php +++ b/tests/Unit/TransactionRules/Triggers/ToAccountStartsTest.php @@ -37,12 +37,14 @@ class ToAccountStartsTest extends TestCase public function testTriggered(): void { $count = 0; - while ($count === 0) { + $account = null; + while ($count !== 0 && $account !== null) { $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); $count = $journal->transactions()->where('amount', '>', 0)->count(); $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + $account = $transaction->account; } - $account = $transaction->account; + $trigger = ToAccountStarts::makeFromStrings(substr($account->name, 0, -3), false); $result = $trigger->triggered($journal); @@ -55,12 +57,13 @@ class ToAccountStartsTest extends TestCase public function testTriggeredLonger(): void { $count = 0; - while ($count === 0) { + $account = null; + while ($count !== 0 && $account !== null) { $journal = TransactionJournal::inRandomOrder()->whereNull('deleted_at')->first(); $count = $journal->transactions()->where('amount', '>', 0)->count(); $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + $account = $transaction->account; } - $account = $transaction->account; $trigger = ToAccountStarts::makeFromStrings('bla-bla-bla' . $account->name, false); $result = $trigger->triggered($journal);
    {{ trans('list.description') }} {{ trans('list.source') }} {{ trans('list.destination') }} {{ trans('list.amount') }}
    + {{ transaction.description }} + {{ transaction.source_name }}
  • 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 diff --git a/.sandstorm/screenshots/screenshot-3.png b/.sandstorm/screenshots/screenshot-3.png index c52941aa7b3180fb6b55bf900aeeb3e794467f30..f68d5de0c12c08588edd80dd11fb95bb8016414f 100644 GIT binary patch literal 167155 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

    X+sjrb8iDd3Ss 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 N&&6fMa-QnG|36r)5rqH% diff --git a/composer.lock b/composer.lock index 6cc0129b14..09497bc119 100644 --- a/composer.lock +++ b/composer.lock @@ -2079,16 +2079,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.14", + "version": "v2.0.15", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "f6ce7dd93628088e1017fb5dd73b0b9fec7df9e5" + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/f6ce7dd93628088e1017fb5dd73b0b9fec7df9e5", - "reference": "f6ce7dd93628088e1017fb5dd73b0b9fec7df9e5", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", + "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", "shasum": "" }, "require": { @@ -2124,7 +2124,7 @@ "pseudorandom", "random" ], - "time": "2018-06-06T17:40:22+00:00" + "time": "2018-06-08T15:26:40+00:00" }, { "name": "phpseclib/phpseclib", @@ -5242,12 +5242,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "ff09cbe142c9195e1ed85a409d7940d43d7306c7" + "reference": "0e4ea9f9e1fd3c6a563524f8f399696d98c7c85a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ff09cbe142c9195e1ed85a409d7940d43d7306c7", - "reference": "ff09cbe142c9195e1ed85a409d7940d43d7306c7", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0e4ea9f9e1fd3c6a563524f8f399696d98c7c85a", + "reference": "0e4ea9f9e1fd3c6a563524f8f399696d98c7c85a", "shasum": "" }, "conflict": { @@ -5315,6 +5315,7 @@ "propel/propel1": ">=1,<=1.7.1", "pusher/pusher-php-server": "<2.2.1", "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", + "sensiolabs/connect": "<4.2.3", "shopware/shopware": "<5.3.7", "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", @@ -5400,7 +5401,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-06-06T08:36:30+00:00" + "time": "2018-06-08T09:55:50+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", From 4a548ac2829f67c5aba00e19976823b2fd7cda65 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 9 Jun 2018 05:50:31 +0200 Subject: [PATCH 008/134] Fix #1474 --- app/Import/Converter/Amount.php | 5 +++++ tests/Unit/Import/Converter/AmountTest.php | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php index 21b3769c0c..79a0c218e5 100644 --- a/app/Import/Converter/Amount.php +++ b/app/Import/Converter/Amount.php @@ -109,6 +109,11 @@ class Amount implements ConverterInterface */ private function stripAmount(string $value): string { + if (0 === strpos($value, '--')) { + $value = substr($value, 2); + } + + $str = preg_replace('/[^\-\(\)\.\,0-9 ]/', '', $value); $len = \strlen($str); if ('(' === $str[0] && ')' === $str[$len - 1]) { diff --git a/tests/Unit/Import/Converter/AmountTest.php b/tests/Unit/Import/Converter/AmountTest.php index 40ca358b7a..8211f2539a 100644 --- a/tests/Unit/Import/Converter/AmountTest.php +++ b/tests/Unit/Import/Converter/AmountTest.php @@ -156,6 +156,13 @@ class AmountTest extends TestCase '(33.52)' => '-33.52', '€(63.12)' => '-63.12', '($182.77)' => '-182.77', + + // double minus because why the hell not + '--0.03881677' => '0.03881677', + '--0.33' => '0.33', + '--$1.23' => '1.23', + '--63 5212.4440' => '635212.444', + '--,2' => '0.2', ]; foreach ($values as $value => $expected) { $converter = new Amount; From 8a15cb3a34f2b5d2c52f9b576d338ef67a48a786 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 9 Jun 2018 06:07:40 +0200 Subject: [PATCH 009/134] Remove the option to check for updates from Sandstorm installations. --- app/Handlers/Events/VersionCheckEventHandler.php | 2 +- app/Http/Controllers/Admin/HomeController.php | 3 ++- resources/views/admin/index.twig | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Handlers/Events/VersionCheckEventHandler.php b/app/Handlers/Events/VersionCheckEventHandler.php index 2cecd97947..2b7b9a1f0f 100644 --- a/app/Handlers/Events/VersionCheckEventHandler.php +++ b/app/Handlers/Events/VersionCheckEventHandler.php @@ -41,7 +41,7 @@ class VersionCheckEventHandler /** * @param RequestedVersionCheckStatus $event */ - public function checkForUpdates(RequestedVersionCheckStatus $event) + public function checkForUpdates(RequestedVersionCheckStatus $event): void { // in Sandstorm, cannot check for updates: $sandstorm = 1 === (int)getenv('SANDSTORM'); diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php index 92d0760119..41fb8cdae3 100644 --- a/app/Http/Controllers/Admin/HomeController.php +++ b/app/Http/Controllers/Admin/HomeController.php @@ -51,8 +51,9 @@ class HomeController extends Controller { $title = (string)trans('firefly.administration'); $mainTitleIcon = 'fa-hand-spock-o'; + $sandstorm = 1 === (int)getenv('SANDSTORM'); - return view('admin.index', compact('title', 'mainTitleIcon')); + return view('admin.index', compact('title', 'mainTitleIcon','sandstorm')); } /** diff --git a/resources/views/admin/index.twig b/resources/views/admin/index.twig index 861cbce7b3..0f1274cb2d 100644 --- a/resources/views/admin/index.twig +++ b/resources/views/admin/index.twig @@ -14,7 +14,9 @@

    From dd17f06362f5afd7d9f5c17aed35a22c205023a7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 9 Jun 2018 07:09:43 +0200 Subject: [PATCH 010/134] Fix #1475 --- app/Http/Controllers/TransactionController.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 95cd7d7464..7ad5d34f99 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -34,6 +34,7 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Transformers\TransactionTransformer; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Log; @@ -96,6 +97,9 @@ class TransactionController extends Controller if ($end < $start) { [$start, $end] = [$end, $start]; } + + $path = route('transactions.index', [$what, $start->format('Y-m-d'), $end->format('Y-m-d')]); + $startStr = $start->formatLocalized($this->monthAndDayFormat); $endStr = $end->formatLocalized($this->monthAndDayFormat); $subTitle = trans('firefly.title_' . $what . '_between', ['start' => $startStr, 'end' => $endStr]); @@ -150,9 +154,9 @@ class TransactionController extends Controller /** * @param Request $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function reconcile(Request $request) + public function reconcile(Request $request): JsonResponse { $transactionIds = $request->get('transactions'); foreach ($transactionIds as $transactionId) { From 3440c3e77a0ad5f3db18235d32c14a5b89f8d548 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 10 Jun 2018 07:10:44 +0200 Subject: [PATCH 011/134] Invalidate cache right after storing data #1478 --- app/Import/Storage/ImportArrayStorage.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 2754ad94df..dde6971a8e 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -108,6 +108,8 @@ class ImportArrayStorage $this->setStatus('rules_applied'); } + app('preferences')->mark(); + return $collection; } From 35a5ec78c3c51926981bc941f1fd2cba36b9cb06 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 10 Jun 2018 10:41:45 +0200 Subject: [PATCH 012/134] Fix #1434 --- public/js/ff/transactions/single/create.js | 2 +- resources/views/transactions/single/create.twig | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index 189f7f7b08..d299f9cd4d 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -142,7 +142,7 @@ function updateLayout() { $('#subTitle').text(title[what]); $('.breadcrumb .active').text(breadcrumbs[what]); $('.breadcrumb li:nth-child(2)').html('' + middleCrumbName[what] + ''); - $('#transaction-btn').text(button[what]); + $('.transaction-btn').text(button[what]); } /** diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index bdad680bf9..44202ec883 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -60,7 +60,7 @@ {{ ExpandedForm.date('date', preFilled.date|default(phpdate('Y-m-d'))) }} @@ -196,7 +196,7 @@ {{ ExpandedForm.optionsList('create','transaction') }} From 6743d99d9b649bf21b7e94da0d095f6a7c5b1b07 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 10 Jun 2018 16:59:03 +0200 Subject: [PATCH 013/134] First batch of code for recurring transactions #1469 --- .../Recurring/CreateController.php | 89 ++++++ .../Controllers/Recurring/EditController.php | 66 +++++ .../Controllers/Recurring/IndexController.php | 146 ++++++++++ app/Models/Recurrence.php | 159 +++++++++++ app/Models/RecurrenceMeta.php | 49 ++++ app/Models/RecurrenceRepetition.php | 53 ++++ app/Models/RecurrenceTransaction.php | 105 +++++++ app/Models/RecurrenceTransactionMeta.php | 49 ++++ app/Providers/RecurringServiceProvider.php | 63 +++++ .../Recurring/RecurringRepository.php | 226 ++++++++++++++++ .../RecurringRepositoryInterface.php | 84 ++++++ app/Transformers/RecurrenceTransformer.php | 256 ++++++++++++++++++ app/User.php | 12 + config/app.php | 1 + config/firefly.php | 1 + .../2018_04_29_174524_changes_for_v474.php | 1 - .../2018_06_08_200526_changes_for_v475.php | 129 +++++++++ public/js/ff/recurring/create.js | 170 ++++++++++++ resources/lang/en_US/config.php | 41 +-- resources/lang/en_US/firefly.php | 33 ++- resources/lang/en_US/form.php | 22 +- resources/lang/en_US/list.php | 5 + resources/views/bills/create.twig | 2 +- resources/views/partials/menu-sidebar.twig | 4 + resources/views/recurring/create.twig | 150 ++++++++++ resources/views/recurring/index.twig | 120 ++++++++ resources/views/recurring/show.twig | 190 +++++++++++++ routes/breadcrumbs.php | 25 ++ routes/web.php | 39 ++- 29 files changed, 2242 insertions(+), 48 deletions(-) create mode 100644 app/Http/Controllers/Recurring/CreateController.php create mode 100644 app/Http/Controllers/Recurring/EditController.php create mode 100644 app/Http/Controllers/Recurring/IndexController.php create mode 100644 app/Models/Recurrence.php create mode 100644 app/Models/RecurrenceMeta.php create mode 100644 app/Models/RecurrenceRepetition.php create mode 100644 app/Models/RecurrenceTransaction.php create mode 100644 app/Models/RecurrenceTransactionMeta.php create mode 100644 app/Providers/RecurringServiceProvider.php create mode 100644 app/Repositories/Recurring/RecurringRepository.php create mode 100644 app/Repositories/Recurring/RecurringRepositoryInterface.php create mode 100644 app/Transformers/RecurrenceTransformer.php create mode 100644 database/migrations/2018_06_08_200526_changes_for_v475.php create mode 100644 public/js/ff/recurring/create.js create mode 100644 resources/views/recurring/create.twig create mode 100644 resources/views/recurring/index.twig create mode 100644 resources/views/recurring/show.twig diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php new file mode 100644 index 0000000000..f7f5d06712 --- /dev/null +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -0,0 +1,89 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use Carbon\Carbon; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Http\Request; + +/** + * + * Class CreateController + */ +class CreateController extends Controller +{ + /** @var BudgetRepositoryInterface */ + private $budgets; + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + app('view')->share('subTitle', trans('firefly.create_new_recurrence')); + + $this->recurring = app(RecurringRepositoryInterface::class); + $this->budgets = app(BudgetRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function create(Request $request) + { + // todo refactor to expandedform method. + $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $tomorrow = new Carbon; + $tomorrow->addDay(); + + // flash some data: + $preFilled = [ + 'first_date' => $tomorrow->format('Y-m-d'), + 'transaction_type' => 'withdrawal', + 'active' => $request->old('active') ?? true, + 'apply_rules' => $request->old('apply_rules') ?? true, + ]; + $request->session()->flash('preFilled', $preFilled); + + return view('recurring.create', compact('tomorrow', 'preFilled', 'defaultCurrency','budgets')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php new file mode 100644 index 0000000000..6ef2b0beb2 --- /dev/null +++ b/app/Http/Controllers/Recurring/EditController.php @@ -0,0 +1,66 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Recurrence; + +/** + * + * Class EditController + */ +class EditController extends Controller +{ + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + app('view')->share('subTitle', trans('firefly.recurrences')); + + $this->recurring = app(RecurringRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Recurrence $recurrence + */ + public function edit(Recurrence $recurrence) { + + return view('recurring.edit', compact('recurrence')); + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php new file mode 100644 index 0000000000..8cde8b238f --- /dev/null +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -0,0 +1,146 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Recurring; + + +use Carbon\Carbon; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Transformers\RecurrenceTransformer; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Response; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class IndexController + */ +class IndexController extends Controller +{ + /** @var RecurringRepositoryInterface */ + private $recurring; + + /** + * + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-paint-brush'); + app('view')->share('title', trans('firefly.recurrences')); + + $this->recurring = app(RecurringRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * @param Request $request + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function index(Request $request) + { + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $collection = $this->recurring->getActive(); + + // TODO: split collection into pages + + $transformer = new RecurrenceTransformer(new ParameterBag); + $recurring = []; + /** @var Recurrence $recurrence */ + foreach ($collection as $recurrence) { + $array = $transformer->transform($recurrence); + $array['first_date'] = new Carbon($array['first_date']); + $array['latest_date'] = null === $array['latest_date'] ? null : new Carbon($array['latest_date']); + $recurring[] = $array; + } + + return view('recurring.index', compact('recurring', 'page', 'pageSize')); + } + + /** + * @param Recurrence $recurrence + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function show(Recurrence $recurrence) + { + $transformer = new RecurrenceTransformer(new ParameterBag); + $array = $transformer->transform($recurrence); + + // transform dates back to Carbon objects: + foreach ($array['repetitions'] as $index => $repetition) { + foreach ($repetition['occurrences'] as $item => $occurrence) { + $array['repetitions'][$index]['occurrences'][$item] = new Carbon($occurrence); + } + } + + $subTitle = trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); + + return view('recurring.show', compact('recurrence', 'subTitle', 'array')); + } + + /** + * @param Request $request + * + * @return JsonResponse + */ + public function suggest(Request $request): JsonResponse + { + $today = new Carbon; + $date = Carbon::createFromFormat('Y-m-d', $request->get('date')); + $result = []; + if ($date > $today) { + $weekly = sprintf('weekly,%s', $date->dayOfWeekIso); + $monthly = sprintf('monthly,%s', $date->day); + $dayOfWeek = trans(sprintf('config.dow_%s', $date->dayOfWeekIso)); + $ndom = sprintf('ndom,%s,%s', $date->weekOfMonth, $date->dayOfWeekIso); + $yearly = sprintf('yearly,%s', $date->format('Y-m-d')); + $yearlyDate = $date->formatLocalized(trans('config.month_and_day_no_year')); + $result = [ + 'daily' => trans('firefly.recurring_daily'), + $weekly => trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]), + $monthly => trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]), + $ndom => trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]), + $yearly => trans('firefly.recurring_yearly', ['date' => $yearlyDate]), + ]; + } + + + return Response::json($result); + } + +} \ No newline at end of file diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php new file mode 100644 index 0000000000..9d321a4a25 --- /dev/null +++ b/app/Models/Recurrence.php @@ -0,0 +1,159 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use FireflyIII\User; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Class Recurrence + * + * @property int $id + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * @property int $user_id + * @property int $transaction_type_id + * @property int $transaction_currency_id + * @property string $title + * @property string $description + * @property \Carbon\Carbon $first_date + * @property \Carbon\Carbon $repeat_until + * @property \Carbon\Carbon $latest_date + * @property string $repetition_type + * @property string $repetition_moment + * @property int $repetition_skip + * @property bool $active + * @property bool $apply_rules + * @property \FireflyIII\User $user + * @property \Illuminate\Support\Collection $recurrenceRepetitions + * @property \Illuminate\Support\Collection $recurrenceMeta + * @property \Illuminate\Support\Collection $recurrenceTransactions + * @property \FireflyIII\Models\TransactionType $transactionType + * + */ +class Recurrence extends Model +{ + /** + * The attributes that should be casted to native types. + * + * @var array + */ + protected $casts + = [ + + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'first_date' => 'date', + 'latest_date' => 'date', + 'active' => 'bool', + 'apply_rules' => 'bool', + ]; + protected $table = 'recurrences'; + + /** + * @param string $value + * + * @return Recurrence + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value): Recurrence + { + if (auth()->check()) { + $recurrenceId = (int)$value; + $recurrence = auth()->user()->recurrences()->find($recurrenceId); + if (null !== $recurrence) { + return $recurrence; + } + } + throw new NotFoundHttpException; + } + + /** + * @codeCoverageIgnore + * Get all of the notes. + */ + public function notes() + { + return $this->morphMany(Note::class, 'noteable'); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceMeta(): HasMany + { + return $this->hasMany(RecurrenceMeta::class); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceRepetitions(): HasMany + { + return $this->hasMany(RecurrenceRepetition::class); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceTransactions(): HasMany + { + return $this->hasMany(RecurrenceTransaction::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function transactionCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function transactionType(): BelongsTo + { + return $this->belongsTo(TransactionType::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + +} \ No newline at end of file diff --git a/app/Models/RecurrenceMeta.php b/app/Models/RecurrenceMeta.php new file mode 100644 index 0000000000..a448e015de --- /dev/null +++ b/app/Models/RecurrenceMeta.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + +/** + * Class RecurrenceMeta + * + * @property string $name + * @property string $value + */ +class RecurrenceMeta extends Model +{ + protected $table = 'recurrences_meta'; + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrence(): BelongsTo + { + return $this->belongsTo(Recurrence::class); + } + +} \ No newline at end of file diff --git a/app/Models/RecurrenceRepetition.php b/app/Models/RecurrenceRepetition.php new file mode 100644 index 0000000000..64a28e1309 --- /dev/null +++ b/app/Models/RecurrenceRepetition.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + +/** + * Class RecurrenceRepetition + * + * @property string $repetition_type + * @property string $repetition_moment + * @property int $repetition_skip + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $deleted_at + * @property \Carbon\Carbon $updated_at + * @property int $id + */ +class RecurrenceRepetition extends Model +{ + protected $table = 'recurrences_repetitions'; + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrence(): BelongsTo + { + return $this->belongsTo(Recurrence::class); + } +} \ No newline at end of file diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php new file mode 100644 index 0000000000..6446797ffb --- /dev/null +++ b/app/Models/RecurrenceTransaction.php @@ -0,0 +1,105 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; + +/** + * + * Class RecurrenceTransaction + * + * @property int $transaction_currency_id, + * @property int $foreign_currency_id + * @property int $source_account_id + * @property int $destination_account_id + * @property string $amount + * @property string $foreign_amount + * @property string $description + * @property \FireflyIII\Models\TransactionCurrency $transactionCurrency + * @property \FireflyIII\Models\TransactionCurrency $foreignCurrency + * @property \FireflyIII\Models\Account $sourceAccount + * @property \FireflyIII\Models\Account $destinationAccount + * @property \Illuminate\Support\Collection $recurrenceTransactionMeta + */ +class RecurrenceTransaction extends Model +{ + protected $table = 'recurrences_transactions'; + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function destinationAccount(): BelongsTo + { + return $this->belongsTo(Account::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function foreignCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class); + } + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrence(): BelongsTo + { + return $this->belongsTo(Recurrence::class); + } + + /** + * @return HasMany + * @codeCoverageIgnore + */ + public function recurrenceTransactionMeta(): HasMany + { + return $this->hasMany(recurrenceTransactionMeta::class,'rt_id'); + } + + /** + * @codeCoverageIgnore + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function sourceAccount(): BelongsTo + { + return $this->belongsTo(Account::class); + } + + /** + * @codeCoverageIgnore + * @return BelongsTo + */ + public function transactionCurrency(): BelongsTo + { + return $this->belongsTo(TransactionCurrency::class); + } +} \ No newline at end of file diff --git a/app/Models/RecurrenceTransactionMeta.php b/app/Models/RecurrenceTransactionMeta.php new file mode 100644 index 0000000000..3c9d04a730 --- /dev/null +++ b/app/Models/RecurrenceTransactionMeta.php @@ -0,0 +1,49 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Models; + + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + +/** + * Class RecurrenceTransactionMeta + * + * @property string $name + * @property string $value + */ +class RecurrenceTransactionMeta extends Model +{ + protected $table = 'rt_meta'; + + /** + * @return BelongsTo + * @codeCoverageIgnore + */ + public function recurrenceTransaction(): BelongsTo + { + return $this->belongsTo(RecurrenceTransaction::class); + } + +} \ No newline at end of file diff --git a/app/Providers/RecurringServiceProvider.php b/app/Providers/RecurringServiceProvider.php new file mode 100644 index 0000000000..d41f54e743 --- /dev/null +++ b/app/Providers/RecurringServiceProvider.php @@ -0,0 +1,63 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Providers; + +use FireflyIII\Repositories\Recurring\RecurringRepository; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use Illuminate\Foundation\Application; +use Illuminate\Support\ServiceProvider; + +/** + * @codeCoverageIgnore + * Class RecurringServiceProvider. + */ +class RecurringServiceProvider extends ServiceProvider +{ + /** + * Bootstrap the application services. + */ + public function boot(): void + { + } + + /** + * Register the application services. + */ + public function register(): void + { + $this->app->bind( + RecurringRepositoryInterface::class, + function (Application $app) { + /** @var RecurringRepositoryInterface $repository */ + $repository = app(RecurringRepository::class); + + if ($app->auth->check()) { + $repository->setUser(auth()->user()); + } + + return $repository; + } + ); + } + +} diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php new file mode 100644 index 0000000000..79905c1019 --- /dev/null +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -0,0 +1,226 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\Recurring; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Note; +use FireflyIII\Models\Preference; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\User; +use Illuminate\Support\Collection; + + +/** + * + * Class RecurringRepository + */ +class RecurringRepository implements RecurringRepositoryInterface +{ + /** @var User */ + private $user; + + /** + * Returns all of the user's recurring transactions. + * + * @return Collection + */ + public function getActive(): Collection + { + return $this->user->recurrences()->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])->where( + 'active', 1 + )->get(); + } + + /** + * Get the notes. + * + * @param Recurrence $recurrence + * + * @return string + */ + public function getNoteText(Recurrence $recurrence): string + { + /** @var Note $note */ + $note = $recurrence->notes()->first(); + if (null !== $note) { + return (string)$note->text; + } + + return ''; + } + + /** + * Calculate the next X iterations starting on the date given in $date. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count + * + * @return array + * @throws FireflyException + */ + public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array + { + $return = []; + $mutator = clone $date; + switch ($repetition->repetition_type) { + default: + throw new FireflyException( + sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type) + ); + case 'daily': + for ($i = 0; $i < $count; $i++) { + $mutator->addDay(); + $return[] = clone $mutator; + } + break; + case 'weekly': + // monday = 1 + // sunday = 7 + $mutator->addDay(); // always assume today has passed. + $dayOfWeek = (int)$repetition->repetition_moment; + if ($mutator->dayOfWeekIso > $dayOfWeek) { + // day has already passed this week, add one week: + $mutator->addWeek(); + } + // today is wednesday (3), expected is friday (5): add two days. + // today is friday (5), expected is monday (1), subtract four days. + $dayDifference = $dayOfWeek - $mutator->dayOfWeekIso; + $mutator->addDays($dayDifference); + for ($i = 0; $i < $count; $i++) { + $return[] = clone $mutator; + $mutator->addWeek(); + } + break; + case 'monthly': + $mutator->addDay(); // always assume today has passed. + $dayOfMonth = (int)$repetition->repetition_moment; + if ($mutator->day > $dayOfMonth) { + // day has passed already, add a month. + $mutator->addMonth(); + } + + for ($i = 0; $i < $count; $i++) { + $domCorrected = min($dayOfMonth, $mutator->daysInMonth); + $mutator->day = $domCorrected; + $return[] = clone $mutator; + $mutator->endOfMonth()->addDay(); + } + break; + case 'ndom': + $mutator->addDay(); // always assume today has passed. + $mutator->startOfMonth(); + // this feels a bit like a cop out but why reinvent the wheel? + $string = '%s %s of %s %s'; + $counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',]; + $daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',]; + $parts = explode(',', $repetition->repetition_moment); + for ($i = 0; $i < $count; $i++) { + $string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y')); + $newCarbon = new Carbon($string); + $return[] = clone $newCarbon; + $mutator->endOfMonth()->addDay(); + } + break; + case 'yearly': + $date = new Carbon($repetition->repetition_moment); + $date->year = $mutator->year; + if ($mutator > $date) { + $date->addYear(); + } + for ($i = 0; $i < $count; $i++) { + $obj = clone $date; + $obj->addYears($i); + $return[] = $obj; + } + break; + } + + return $return; + } + + /** + * Parse the repetition in a string that is user readable. + * + * @param RecurrenceRepetition $repetition + * + * @return string + * @throws FireflyException + */ + public function repetitionDescription(RecurrenceRepetition $repetition): string + { + /** @var Preference $pref */ + $pref = app('preferences')->getForUser($this->user, 'language', config('firefly.default_language', 'en_US')); + $language = $pref->data; + switch ($repetition->repetition_type) { + default: + throw new FireflyException(sprintf('Cannot translate recurring transaction repetition type "%s"', $repetition->repetition_type)); + break; + case 'daily': + return trans('firefly.recurring_daily', [], $language); + break; + case 'weekly': + $dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language); + + return trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language); + break; + case 'monthly': + // format a date: + return trans('firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment], $language); + break; + case 'ndom': + $parts = explode(',', $repetition->repetition_moment); + // first part is number of week, second is weekday. + $dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language); + + return trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language); + break; + case 'yearly': + // + $today = new Carbon; + $today->endOfYear(); + $repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment); + $diffInYears = $today->diffInYears($repDate); + $repDate->addYears($diffInYears); // technically not necessary. + $string = $repDate->formatLocalized(trans('config.month_and_day_no_year')); + + return trans('firefly.recurring_yearly', ['date' => $string], $language); + break; + + } + + } + + /** + * Set user for in repository. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } +} \ No newline at end of file diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php new file mode 100644 index 0000000000..d72738b785 --- /dev/null +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\Recurring; + +use Carbon\Carbon; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\User; +use Illuminate\Support\Collection; + + +/** + * Interface RecurringRepositoryInterface + * + * @package FireflyIII\Repositories\Recurring + */ +interface RecurringRepositoryInterface +{ + /** + * Returns all of the user's recurring transactions. + * + * @return Collection + */ + public function getActive(): Collection; + + /** + * Get the notes. + * + * @param Recurrence $recurrence + * + * @return string + */ + public function getNoteText(Recurrence $recurrence): string; + + /** + * Calculate the next X iterations starting on the date given in $date. + * Returns an array of Carbon objects. + * + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count + * + * @return array + */ + public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array; + + /** + * Parse the repetition in a string that is user readable. + * + * @param RecurrenceRepetition $repetition + * + * @return string + */ + public function repetitionDescription(RecurrenceRepetition $repetition): string; + + /** + * Set user for in repository. + * + * @param User $user + */ + public function setUser(User $user): void; + +} \ No newline at end of file diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php new file mode 100644 index 0000000000..db984a4c85 --- /dev/null +++ b/app/Transformers/RecurrenceTransformer.php @@ -0,0 +1,256 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\CategoryFactory; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\RecurrenceMeta; +use FireflyIII\Models\RecurrenceRepetition; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use League\Fractal\Resource\Item; +use League\Fractal\TransformerAbstract; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * + * Class RecurringTransactionTransformer + */ +class RecurrenceTransformer extends TransformerAbstract +{ + /** @noinspection ClassOverridesFieldOfSuperClassInspection */ + /** + * List of resources possible to include. + * + * @var array + */ + protected $availableIncludes = ['user']; + /** + * List of resources to automatically include + * + * @var array + */ + protected $defaultIncludes = []; + /** @var ParameterBag */ + protected $parameters; + + /** @var RecurringRepositoryInterface */ + protected $repository; + + + public function __construct(ParameterBag $parameters) + { + $this->repository = app(RecurringRepositoryInterface::class); + $this->parameters = $parameters; + } + + /** + * Include user data in end result. + * + * @codeCoverageIgnore + * + * @param Recurrence $recurrence + * + * + * @return Item + */ + public function includeUser(Recurrence $recurrence): Item + { + return $this->item($recurrence->user, new UserTransformer($this->parameters), 'user'); + } + + /** + * Transform the piggy bank. + * + * @param Recurrence $recurrence + * + * @return array + * @throws FireflyException + */ + public function transform(Recurrence $recurrence): array + { + $this->repository->setUser($recurrence->user); + $return = [ + 'id' => (int)$recurrence->id, + 'updated_at' => $recurrence->updated_at->toAtomString(), + 'created_at' => $recurrence->created_at->toAtomString(), + 'transaction_type_id' => $recurrence->transaction_type_id, + 'transaction_type' => $recurrence->transactionType->type, + 'title' => $recurrence->title, + 'description' => $recurrence->description, + 'first_date' => $recurrence->first_date->format('Y-m-d'), + 'latest_date' => null === $recurrence->latest_date ? null : $recurrence->latest_date->format('Y-m-d'), + 'repeat_until' => null === $recurrence->repeat_until ? null : $recurrence->repeat_until->format('Y-m-d'), + 'apply_rules' => $recurrence->apply_rules, + 'active' => $recurrence->active, + 'notes' => $this->repository->getNoteText($recurrence), + 'repetitions' => [], + 'transactions' => [], + 'meta' => [], + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/recurring/' . $recurrence->id, + ], + ], + ]; + $fromDate = $recurrence->latest_date ?? $recurrence->first_date; + // date in the past? use today: + $today = new Carbon; + $fromDate = $fromDate->lte($today) ? $today : $fromDate; + + /** @var RecurrenceRepetition $repetition */ + foreach ($recurrence->recurrenceRepetitions as $repetition) { + $repetitionArray = [ + 'id' => $repetition->id, + 'updated_at' => $repetition->updated_at->toAtomString(), + 'created_at' => $repetition->created_at->toAtomString(), + 'repetition_type' => $repetition->repetition_type, + 'repetition_moment' => $repetition->repetition_moment, + 'repetition_skip' => (int)$repetition->repetition_skip, + 'description' => $this->repository->repetitionDescription($repetition), + 'occurrences' => [], + ]; + + // get the (future) occurrences for this specific type of repetition: + $occurrences = $this->repository->getOccurrences($repetition, $fromDate, 5); + /** @var Carbon $carbon */ + foreach ($occurrences as $carbon) { + $repetitionArray['occurrences'][] = $carbon->format('Y-m-d'); + } + + $return['repetitions'][] = $repetitionArray; + } + unset($repetitionArray); + + // get all transactions: + /** @var RecurrenceTransaction $transaction */ + foreach ($recurrence->recurrenceTransactions as $transaction) { + $transactionArray = [ + 'currency_id' => $transaction->transaction_currency_id, + 'currency_code' => $transaction->transactionCurrency->code, + 'currency_symbol' => $transaction->transactionCurrency->symbol, + 'currency_dp' => $transaction->transactionCurrency->decimal_places, + 'foreign_currency_id' => $transaction->foreign_currency_id, + 'source_account_id' => $transaction->source_account_id, + 'source_account_name' => $transaction->sourceAccount->name, + 'destination_account_id' => $transaction->destination_account_id, + 'destination_account_name' => $transaction->destinationAccount->name, + 'amount' => $transaction->amount, + 'foreign_amount' => $transaction->foreign_amount, + 'description' => $transaction->description, + 'meta' => [], + ]; + if (null !== $transaction->foreign_currency_id) { + $transactionArray['foreign_currency_code'] = $transaction->foreignCurrency->code; + $transactionArray['foreign_currency_symbol'] = $transaction->foreignCurrency->symbol; + $transactionArray['foreign_currency_dp'] = $transaction->foreignCurrency->decimal_places; + } + + // get meta data for each transaction: + /** @var RecurrenceTransactionMeta $transactionMeta */ + foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) { + $transactionMetaArray = [ + 'name' => $transactionMeta->name, + 'value' => $transactionMeta->value, + ]; + switch ($transactionMeta->name) { + default: + throw new FireflyException(sprintf('Recurrence transformer cannot handle transaction meta-field "%s"', $transactionMeta->name)); + case 'category_name': + /** @var CategoryFactory $factory */ + $factory = app(CategoryFactory::class); + $factory->setUser($recurrence->user); + $category = $factory->findOrCreate(null, $transactionMeta->value); + if (null !== $category) { + $transactionMetaArray['category_id'] = $category->id; + $transactionMetaArray['category_name'] = $category->name; + } + break; + case 'budget_id': + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); + $budget = $repository->findNull((int)$transactionMeta->value); + if (null !== $budget) { + $transactionMetaArray['budget_id'] = $budget->id; + $transactionMetaArray['budget_name'] = $budget->name; + } + break; + } + // store transaction meta data in transaction + $transactionArray['meta'][] = $transactionMetaArray; + } + // store transaction in recurrence array. + $return['transactions'][] = $transactionArray; + } + // get all meta data for recurrence itself + /** @var RecurrenceMeta $recurrenceMeta */ + foreach ($recurrence->recurrenceMeta as $recurrenceMeta) { + $recurrenceMetaArray = [ + 'name' => $recurrenceMeta->name, + 'value' => $recurrenceMeta->value, + ]; + switch ($recurrenceMeta->name) { + default: + throw new FireflyException(sprintf('Recurrence transformer cannot handle meta-field "%s"', $recurrenceMeta->name)); + case 'tags': + $recurrenceMetaArray['tags'] = explode(',', $recurrenceMeta->value); + break; + case 'notes': + break; + case 'bill_id': + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $bill = $repository->find((int)$recurrenceMeta->value); + if (null !== $bill) { + $recurrenceMetaArray['bill_id'] = $bill->id; + $recurrenceMetaArray['bill_name'] = $bill->name; + } + break; + case 'piggy_bank_id': + /** @var PiggyBankRepositoryInterface $repository */ + $repository = app(PiggyBankRepositoryInterface::class); + $piggy = $repository->findNull((int)$recurrenceMeta->value); + if (null !== $piggy) { + $recurrenceMetaArray['piggy_bank_id'] = $piggy->id; + $recurrenceMetaArray['piggy_bank_name'] = $piggy->name; + } + break; + } + // store meta date in recurring array + $return['meta'][] = $recurrenceMetaArray; + + } + + return $return; + } + +} \ No newline at end of file diff --git a/app/User.php b/app/User.php index 2ffd076912..28525e3ab4 100644 --- a/app/User.php +++ b/app/User.php @@ -36,6 +36,7 @@ use FireflyIII\Models\ExportJob; use FireflyIII\Models\ImportJob; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Preference; +use FireflyIII\Models\Recurrence; use FireflyIII\Models\Role; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; @@ -291,6 +292,17 @@ class User extends Authenticatable return $this->hasMany(Preference::class); } + /** + * @codeCoverageIgnore + * Link to recurring transactions. + * + * @return HasMany + */ + public function recurrences(): HasMany + { + return $this->hasMany(Recurrence::class); + } + /** * @codeCoverageIgnore * Link to roles. diff --git a/config/app.php b/config/app.php index 953ed046ee..5127530c6d 100644 --- a/config/app.php +++ b/config/app.php @@ -98,6 +98,7 @@ return [ FireflyIII\Providers\SearchServiceProvider::class, FireflyIII\Providers\TagServiceProvider::class, FireflyIII\Providers\AdminServiceProvider::class, + FireflyIII\Providers\RecurringServiceProvider::class, ], diff --git a/config/firefly.php b/config/firefly.php index c0fe9ed9ea..7721649b9b 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -271,6 +271,7 @@ return [ 'piggyBank' => \FireflyIII\Models\PiggyBank::class, 'tj' => \FireflyIII\Models\TransactionJournal::class, 'tag' => \FireflyIII\Models\Tag::class, + 'recurrence' => \FireflyIII\Models\Recurrence::class, 'rule' => \FireflyIII\Models\Rule::class, 'ruleGroup' => \FireflyIII\Models\RuleGroup::class, 'exportJob' => \FireflyIII\Models\ExportJob::class, diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php index cdc6208f35..c9c79e6e82 100644 --- a/database/migrations/2018_04_29_174524_changes_for_v474.php +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php @@ -37,7 +37,6 @@ class ChangesForV474 extends Migration */ public function down() { - // } /** diff --git a/database/migrations/2018_06_08_200526_changes_for_v475.php b/database/migrations/2018_06_08_200526_changes_for_v475.php new file mode 100644 index 0000000000..053a6e676b --- /dev/null +++ b/database/migrations/2018_06_08_200526_changes_for_v475.php @@ -0,0 +1,129 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('user_id', false, true); + $table->integer('transaction_type_id', false, true); + + $table->string('title', 1024); + $table->text('description'); + + $table->date('first_date'); + $table->date('repeat_until')->nullable(); + $table->date('latest_date')->nullable(); + + $table->boolean('apply_rules')->default(true); + $table->boolean('active')->default(true); + + // also separate: + // category, budget, tags, notes, bill, piggy bank + + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('transaction_type_id')->references('id')->on('transaction_types')->onDelete('cascade'); + } + ); + + Schema::create( + 'recurrences_transactions', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('recurrence_id', false, true); + $table->integer('transaction_currency_id', false, true); + $table->integer('foreign_currency_id', false, true)->nullable(); + $table->integer('source_account_id', false, true); + $table->integer('destination_account_id', false, true); + + $table->decimal('amount', 22, 12); + $table->decimal('foreign_amount', 22, 12)->nullable(); + $table->string('description', 1024); + + + $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + $table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null'); + $table->foreign('source_account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('destination_account_id')->references('id')->on('accounts')->onDelete('cascade'); + } + ); + + + Schema::create( + 'recurrences_repetitions', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('recurrence_id', false, true); + $table->string('repetition_type', 50); + $table->string('repetition_moment', 50); + $table->smallInteger('repetition_skip', false, true); + + $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); + } + ); + + Schema::create( + 'recurrences_meta', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('recurrence_id', false, true); + + $table->string('name', 50); + $table->text('value'); + + $table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade'); + } + ); + + Schema::create( + 'rt_meta', function (Blueprint $table) { + $table->increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->integer('rt_id', false, true); + + $table->string('name', 50); + $table->text('value'); + + $table->foreign('rt_id')->references('id')->on('recurrences_transactions')->onDelete('cascade'); + } + ); + + + } +} diff --git a/public/js/ff/recurring/create.js b/public/js/ff/recurring/create.js new file mode 100644 index 0000000000..78ff8264b6 --- /dev/null +++ b/public/js/ff/recurring/create.js @@ -0,0 +1,170 @@ +/* + * create.js + * Copyright (c) 2017 thegrumpydictator@gmail.com + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ + +/** global: Modernizr, currencies */ + +$(document).ready(function () { + "use strict"; + if (!Modernizr.inputtypes.date) { + $('input[type="date"]').datepicker( + { + dateFormat: 'yy-mm-dd' + } + ); + } + initializeButtons(); + initializeAutoComplete(); + respondToFirstDateChange(); + $('.switch-button').on('click', switchTransactionType); + $('#ffInput_first_date').on('change', respondToFirstDateChange); + + +}); + +function respondToFirstDateChange() { + var obj = $('#ffInput_first_date'); + var select = $('#ffInput_repetition_type'); + var date = obj.val(); + select.prop('disabled', true); + $.getJSON(suggestUri, {date: date}).fail(function () { + console.error('Could not load repetition suggestions'); + alert('Could not load repetition suggestions'); + }).done(parseRepetitionSuggestions); +} + +function parseRepetitionSuggestions(data) { + + var select = $('#ffInput_repetition_type'); + select.empty(); + for (var k in data) { + if (data.hasOwnProperty(k)) { + select.append($('

    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 q?05MK??->6p=N_}4g)#!M1GN(NW6bqvE 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 AMF0Q* literal 109933 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