From a681f1ce3c37c957d1ef7e361420eabf6c3b7998 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 28 Jul 2018 21:03:08 +0200 Subject: [PATCH 001/166] Empty URL warning --- app/Support/Binder/AccountList.php | 2 +- public/images/logos/ynab.png | Bin 0 -> 5840 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 public/images/logos/ynab.png diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php index f2b6497213..3c0cca2618 100644 --- a/app/Support/Binder/AccountList.php +++ b/app/Support/Binder/AccountList.php @@ -74,7 +74,7 @@ class AccountList implements BinderInterface return $collection; } } - Log::error('User is not logged in.'); + Log::error(sprintf('Trying to show account list (%s), but user is not logged in or list is empty.', $route->uri)); throw new NotFoundHttpException; } } diff --git a/public/images/logos/ynab.png b/public/images/logos/ynab.png new file mode 100644 index 0000000000000000000000000000000000000000..289ea469f504b04368e4c72c5bf21baef5042a4c GIT binary patch literal 5840 zcmZ{o2RK~a*2f7MExPEvMV}ch!5AW<_fDdY!6-A#3`TE*i1sQGokWWS5kmCnf)GKJ zh!DL*j~bGXyzl*PzI^x2^PD+nul4_}z1KQ>?{l6LW1y!_NzO`6KtMpLsiA6geui8e zq=55(`O``C`H2u?q^?3xG047p-XQbTu*47$P|#l-ganz{%mf5PPY@;+SPNYpc^Jw= z!rl?(0GGgdc%G}zBjM!FFFoK`dv2VEI}#(0Q{?@nkUzh^FavqHe@U=zio6!O2Hdw$ zXgIfwgtP>R7fQ~}%?&|2I>{TUs{MnXw-k9@uvkxdAkf#>SHf3H0)=)4g5~7ofFMbr zq@?({LLB3V#M`66d(qMV{{9)Khv)xx zgv9)l)p>qEoV_OyECB-kCkTsh`uFhu4|U=BuNWMM_%GOn=Wpy+G5#JSd`hl@~K8x;-WQs+br8ncyiy!1VI=K4(F-oPhk=8y<<2c>cK z>IvID*@+rPD-&tah~qB7j-gD2?m0V8^QoFnI~#3#HALxywk~;OCI!p#xVF>6@^bv= zz_n3f?{d#=H-UQ#I#pd~f=t>kjR4ky%eHc;*sZ(TSIj-|6y%I2#>UZ!N826L5(g!% z3%!_%>%=py=}BK}<|l^$qPqP}KaLO5<~eMFP8F3xPTstI%hFIT&Og7dw7F->W)Ce3 z`cawGyTz!RSItX9dPGNvFg{aMlQwqaaC_`L3MU(B;WL}uQ-<8Xzg)(h`)M4i^x|y3 z#a1Sga*#2rnAEd-TuW-d>+$O1akDa*aRsP4EF^{0i!Ui^>5QOgNyD)+-eI2!7IPWQUGZpp9_ML~OnA+uLtv?~oU#>20?noUke~hgbQgqMl8=b>P+Ryfoli(}vt* z+Ts7q-%(JX{5_vRB3m-xroGp3``lcX+Ws~735^_T^?`$IQyy-wL8A6=XXd4bu7+Gf zb*5US28n*pW&q9TTG;TRVK}Ix@z5JMT!!9VAIXu&)Ody=GujS@yDtsHe74fTX^OXOuOGx$hTa zPBN#bVxnA16HCNC)I-{)9(*xV{YeXGS4k&jqyPG-Zb=(TrDyL{w@KW;oYiiHh$-}l zG!YmZz`Wn|vW!co-AocnMKFAWJOg~#A?kVYlYS#!D=R)>{#5HtLWo+UTPdg2 zm*Gap>_|G`eB|G@!5Xsm++z5cH5TIt^zlvMD-eUIxOlI&>~^8J`Lund$5fn80*J8TG+w2NATPvdRo1Y3B2Al5s+Aa5SuQe?(i=T*ioZ7J6 zZG^OCInr?S@=xM!lALYUzD~%Tkf__hjup ziaoHQxSU|seS8)}pwVj}wBUEMEaSW+CTMXflWLG|3XTp%BzbQr{O!t}vG+z`3TiFl1%|%L;_>&qdCvpT!crpH!@Q1c9qG5cSibqbogW>)=b$;meAwTTq16 zWR<3I|3m++&pGGOVe9*y5xCpzHTQ4<(w>jSl69F$wHfFki6E-S=@I$hQ>O#48#6Gh z?b^-WFGli8VU}mCp;qst$NeCKxr%;|7e`|#StjS7?o}lxm~DOD$sJ#sjx8R{?a)1d zpjQ=!rMjCYKcaW}9+J@RWXXCvs5`%US~2x{{>6cmZclRy_8oJM6k}Di=wg|=UNH4o zP{PVjt!2vqZ|7yuIXTl-X0dmTO7rI_DGiJrwm^&i`e@%&2tIOcBG9fPg@U28+-WKlPs5s6ko+ z9(gsq%6F?a9bGYDpcKm#**H4?8;@$4xw|?P^UYxKi>iHO7yOW--|7|{iAZ{E)VyjM z%+g;Z$>(&TDkjbDu=N?E{d8_zy?EgmhHBiz#ArM`oP{MJWo(tDE0I*K%~9uHIiq>12YJ z`-|ITFa4#^gV5s{%Ko5L;ngtsqg^WXh3i$Izu)>Z(CkJtLmJxnEX>+Byo4>1v`%C2 zl&LU_njktWAfPF!N4#|A)m|6wnEp~Vh0fq+hYXXOx5OoixnwBG*PgKX%HGk&-n2a< zZ~dXz3cpzcYef|%U5b!#XG_duiq}k+?dhEsECqq|M&jaP83Dex1`>Q7REqi>+EsK4 zUedv6O(7{1FSJS=b$f+%zc2N!tyQ#k$E#Q&EE!I>OYE2cR{5}u?k2zDov}N5dI&_- z>d_d?S|CTYUqk%U@Dqv zmoK(8vRD_6769xpkvR*#o7edXKq?L!GZ)Mr<~BEyF!uKi)6%XJK2;~KZS&z;76jbf z>XN?`VOZ$Wn3wu%W8;s-@u%S|@nMyJRdEJEY_3z6G+mZwQA<_Yi8W@XdcGhi4$op+ zqc7Yq3_~Wk7h%P}Wj=7^2x`0SOm6Q~?B#1?!wJ91EP0H_I$kqoMHGF0UN}e zJBuh(eS1&Ky|(^p1uaj%o4}QOgoNe*KQ9#?j+Z7L2@6^CS^kFSo1^^wj5@;aH8@s( zs5T;hc#v&&#Dm-`R-M4YyHYSMf-_VZFyqpO2awIW`B$mov!Y~uhRub?bNeg>3$jqi83JDkO9<}BPFNt7%*H!1c3jyEs4l|m~`c|}%yIHe5 zl~>sZ41pTAiY(J=uJDwNAhzPl6Cc@itNO!aN=Oh3qth_#iT>foP{Kygq$h0y&iixoJA;;xCnu>sU*Vq7fK1c^ zYuyc|CGp~nmqb86N)q2MqhKo`as{h1f>1qr#ds-42PtVX zPEuws_DDg{;&>`zKp!Try2fz-OUBX&CsD(z9HcAf1Gyz2jT~dkbw`ePvd@W?Snl#A&W)5tBGTcBdU#0i|qQ|h9WWu zB{G7Z_>A|*yp1g$_;K~hZV(FZ$Z@{3X%n)=6L~qv8UcoBu(**8p=uys=u`NT?=@n7wI%ae$z(P&=yuco^Wbu zU5QQ+5|IeNXUx%NZojl>B$miQsyuP+Nci@7r>N+G`m>~LDKA>^5v@cSSYm@9-41f= z#2vlbc#p%#+{-3YN@&f$E6547n=DZL!mA))6Oz4ddZYIU3CU6a`95dZLKP{XV0Y|i zLLG-FF4(#qBo#M78MF)WUjcJm%LA&6tJfC{IMc9yj%sNp_Y4Z1e!G<}nOitOBr_bP ztaZjsEPP2-%XUr0QNX}9n^~wa<%IX4sl{VEY0_cVoBFTR^5QWDV>RuC%ExydG5rX? zMur&|MKtppwa}CIU+f+Pv??=`iP{T(es^kKJ0M0-L z57zW9vx1zeDng3#>U30AkCWipARm7m{sD;IXO5%m&6VMJEY`b-N?crgy1XPy*4c3x z0B$x?4j@b`i`-JWYg@hUMdXa+gf%!Pu$vdv*~GE))lT%h>QRFErY;KgAQeYm2I-ht z8Mub!Fe{LoDyhbiE;{m;Bi-FiGV1AH$FBsJD7??>~QF=y7<&ZzeOzCvZU~ zIEz~K;A>xjWU;RaLW8Pg5(WK9lyCWRyL{3q6*!m_o^W|( zG0Xv$kzl63HpHJR8PpG^NLAF~OsX=HeKzBQmKJ(l&0pX+Ao2}|&pAe zTD&Zw=~UbI<0q6tD5n=FkCd19o^hcgX2R{c!VJc!)?56Op2@_27iEp{by&SVB9boY z7GryF`7f>+D(XHw8)Z*qpw-)jyCHJoQqCa(+8CJY*c*|NN=Ew%M?uNWeJZZbPuVwI zu{MfTRUeOPAGelGc~(@hhJNpwd!H=IXC!Mqy1UL%k~Jkr+y~vNJ`%klU`>$ujWaHT z)E|Ry+homv)|QqR)xXQI%6nscK1pYGW0dSxp7X`37kFWHBfJ$VKe2bHIgsPGVKp@+ zaMcR@!!B78&el$aZAtdaSf*Okq{Ifq!=u{-Is@7`;jVy8A928HOy2zPaoDAa&Asg^b7d=F`(p$P@I8 zPwla%n%Slc|8_OEOj5{mUz|!5qeig)^|MIUU39k+UfEkeyv!zOC9;@URw|@S$zsRY zzeHwwvo=xvbIo$f3a@#OyW)4uPcWbSFn$|CP z0#r?oaPL+oZ|qMui>K{rIo9y_KsKkzHFF#NeZ+!r3C3NiKc+eKz$`8us!C$*vXE3Y zMM1??b|U(rOAY64hGfn=7#@NH#K=<)Sj9mOy`FsRq)#iQd1t6NPTc6h`RD_8%;B4| zY?@YJ*LtWSn{@E@U=QtjU>tH)xv$}^aEJHT?^NSv8|ayx#9&I0B>?r`}NF;Lh zH2+mq;ib#i^1P}-|I(tx!tXcpnP|bCO!c=b-|Ek*!!4W~_0!DUc<27O+98zO7#Pe4`2mMBJ#GQnVHp%cu`!Su($>=W`ak zen+pf6vR8o;qBeH8WC#lZ@(`xzoqI~PlvJOH4*$y4y8%c?lJVNw>+ZkMxROx)JneUV7teCJmR_2BBu5 zRnaBy%32|HV5qUdd~MCOS=Plip)3iO7QKTn9r1w3z6dzHx_4#s?hiImFRYo%0^bDjU@p*nQ zqMj_4tXXA!C9_^zSLx-$GFC_?C*uU@?VQ#e_kxN{Q15GgXX&8?ID@_)rQ6oP!#k+y zCvhq7^-5qjjX^hNsr6RX-I7aEMFg^=W(Rq4qA4xEgfV=5?jc#5bd=Ms?RiEBuP<;@ zICP+>uTX-TKSREeP&8?~p}xiXb=NWTmhXF>c^21bo)JDmE_bfFvujEO5tsLVMsCu- zS}7SY$P+$Auq8c3#)$I&v+P$qQO3d zOLMnfs{qNaa!d$rwmBsfQ~O}g7z1B9vDty$nuhy+62 Date: Sun, 29 Jul 2018 06:53:08 +0200 Subject: [PATCH 002/166] Expand views with CSRF token, to prevent error in console. #1575 --- resources/views/layout/empty.twig | 1 + resources/views/layout/guest.twig | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/views/layout/empty.twig b/resources/views/layout/empty.twig index 59982fae3c..b0af1b15c0 100644 --- a/resources/views/layout/empty.twig +++ b/resources/views/layout/empty.twig @@ -6,6 +6,7 @@ Firefly III + diff --git a/resources/views/layout/guest.twig b/resources/views/layout/guest.twig index d886823751..2fbd205824 100644 --- a/resources/views/layout/guest.twig +++ b/resources/views/layout/guest.twig @@ -15,6 +15,7 @@ {% endif %} + From 8efbeb14d206598ded27be045b08cc228860a48d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Jul 2018 07:30:06 +0200 Subject: [PATCH 003/166] First code for YNAB import #145 --- .../Controllers/Import/CallbackController.php | 70 +++++++++ .../Controllers/Import/IndexController.php | 15 +- .../JobConfiguration/YnabJobConfiguration.php | 125 +++++++++++++++ .../Prerequisites/YnabPrerequisites.php | 145 ++++++++++++++++++ app/Import/Routine/BunqRoutine.php | 4 +- app/Import/Routine/YnabRoutine.php | 85 ++++++++++ app/Support/Binder/ImportProvider.php | 7 +- .../Routine/Ynab/StageGetAccessHandler.php | 86 +++++++++++ config/import.php | 14 ++ resources/lang/en_US/form.php | 1 + resources/lang/en_US/import.php | 9 +- resources/views/import/index.twig | 7 + resources/views/import/spectre/redirect.twig | 2 +- .../views/import/ynab/prerequisites.twig | 53 +++++++ resources/views/import/ynab/redirect.twig | 14 ++ routes/web.php | 3 + 16 files changed, 627 insertions(+), 13 deletions(-) create mode 100644 app/Http/Controllers/Import/CallbackController.php create mode 100644 app/Import/JobConfiguration/YnabJobConfiguration.php create mode 100644 app/Import/Prerequisites/YnabPrerequisites.php create mode 100644 app/Import/Routine/YnabRoutine.php create mode 100644 app/Support/Import/Routine/Ynab/StageGetAccessHandler.php create mode 100644 resources/views/import/ynab/prerequisites.twig create mode 100644 resources/views/import/ynab/redirect.twig diff --git a/app/Http/Controllers/Import/CallbackController.php b/app/Http/Controllers/Import/CallbackController.php new file mode 100644 index 0000000000..9f7f7c62f6 --- /dev/null +++ b/app/Http/Controllers/Import/CallbackController.php @@ -0,0 +1,70 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Import; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Http\Request; +use Log; + +/** + * Class CallbackController + */ +class CallbackController extends Controller +{ + + /** + * @param Request $request + * + * @param ImportJobRepositoryInterface $repository + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View + */ + public function ynab(Request $request, ImportJobRepositoryInterface $repository) + { + $code = (string)$request->get('code'); + $jobKey = (string)$request->get('state'); + $importJob = $repository->findByKey($jobKey); + if ('' === $code) { + return view('error')->with('message', 'You Need A Budget did not reply with a valid authorization code. Firefly III cannot continue.'); + } + if ('' === $jobKey || null === $importJob) { + return view('error')->with('message', 'You Need A Budget did not reply with the correct state identifier. Firefly III cannot continue.'); + } + Log::debug(sprintf('Got a code from YNAB: %s', $code)); + + // we have a code. Make the job ready for the next step, and then redirect the user. + $configuration = $repository->getConfiguration($importJob); + $configuration['auth_code'] = $code; + $repository->setConfiguration($importJob, $configuration); + + // set stage to make the import routine take the correct action: + $repository->setStatus($importJob, 'ready_to_run'); + $repository->setStage($importJob, 'get_access_token'); + + return redirect(route('import.job.status.index', [$importJob->key])); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 799904d0c2..bf45a8c6c3 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -78,9 +78,15 @@ class IndexController extends Controller { Log::debug(sprintf('Will create job for provider "%s"', $importProvider)); - $importJob = $this->repository->create($importProvider); - $hasPreReq = (bool)config(sprintf('import.has_prereq.%s', $importProvider)); - $hasConfig = (bool)config(sprintf('import.has_job_config.%s', $importProvider)); + $importJob = $this->repository->create($importProvider); + $hasPreReq = (bool)config(sprintf('import.has_prereq.%s', $importProvider)); + $hasConfig = (bool)config(sprintf('import.has_job_config.%s', $importProvider)); + $allowedForDemo = (bool)config(sprintf('import.allowed_for_demo.%s', $importProvider)); + $isDemoUser = $this->userRepository->hasRole(auth()->user(), 'demo'); + + if ($isDemoUser && !$allowedForDemo) { + return redirect(route('import.index')); + } Log::debug(sprintf('Created job #%d for provider %s', $importJob->id, $importProvider)); @@ -180,7 +186,8 @@ class IndexController extends Controller $providers = $this->providers; $subTitle = (string)trans('import.index_breadcrumb'); $subTitleIcon = 'fa-home'; + $isDemoUser = $this->userRepository->hasRole(auth()->user(), 'demo'); - return view('import.index', compact('subTitle', 'subTitleIcon', 'providers')); + return view('import.index', compact('subTitle', 'subTitleIcon', 'providers', 'isDemoUser')); } } diff --git a/app/Import/JobConfiguration/YnabJobConfiguration.php b/app/Import/JobConfiguration/YnabJobConfiguration.php new file mode 100644 index 0000000000..a42055b970 --- /dev/null +++ b/app/Import/JobConfiguration/YnabJobConfiguration.php @@ -0,0 +1,125 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Import\JobConfiguration; + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\MessageBag; +use Log; + +/** + * Class YnabJobConfiguration + */ +class YnabJobConfiguration implements JobConfigurationInterface +{ + /** @var ImportJob The import job */ + private $importJob; + /** @var ImportJobRepositoryInterface Import job repository */ + private $repository; + + /** + * Returns true when the initial configuration for this job is complete. + * + * @return bool + */ + public function configurationComplete(): bool + { + // config is only needed when the job is in stage "new". + if ($this->importJob->stage === 'new') { + Log::debug('YNAB configurationComplete: stage is new, return false'); + + return false; + } + + Log::debug('YNAB configurationComplete: stage is not new, return true'); + + return true; + } + + /** + * Store any data from the $data array into the job. Anything in the message bag will be flashed + * as an error to the user, regardless of its content. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag + { + Log::debug('YNAB configureJob: nothing to do.'); + + // there is never anything to store from this job. + return new MessageBag; + } + + /** + * Return the data required for the next step in the job configuration. + * + * @return array + */ + public function getNextData(): array + { + $data = []; + // here we update the job so it can redirect properly to YNAB + if ($this->importJob->stage === 'new') { + + // update stage to make sure we catch the token. + $this->repository->setStage($this->importJob, 'catch-auth-code'); + $clientId = (string)config('import.options.ynab.client_id'); + $callBackUri = route('import.callback.ynab'); + $uri = sprintf( + 'https://app.youneedabudget.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s', $clientId, $callBackUri, + $this->importJob->key + ); + $data['token-url'] = $uri; + Log::debug(sprintf('YNAB getNextData: URI to redirect to is %s', $uri)); + } + + return $data; + + } + + /** + * Returns the view of the next step in the job configuration. + * + * @return string + */ + public function getNextView(): string + { + Log::debug('Return YNAB redirect view.'); + return 'import.ynab.redirect'; + } + + /** + * Set import job. + * + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Import/Prerequisites/YnabPrerequisites.php b/app/Import/Prerequisites/YnabPrerequisites.php new file mode 100644 index 0000000000..2079cdd21d --- /dev/null +++ b/app/Import/Prerequisites/YnabPrerequisites.php @@ -0,0 +1,145 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Import\Prerequisites; + +use FireflyIII\User; +use Illuminate\Support\MessageBag; +use Log; + +/** + * Class YnabPrerequisites + */ +class YnabPrerequisites implements PrerequisitesInterface +{ + /** @var User The current user */ + private $user; + + /** + * Returns view name that allows user to fill in prerequisites. + * + * @return string + */ + public function getView(): string + { + return 'import.ynab.prerequisites'; + } + + /** + * Returns any values required for the prerequisites-view. + * + * @return array + */ + public function getViewParameters(): array + { + Log::debug('Now in YnabPrerequisites::getViewParameters()'); + $clientId = ''; + $clientSecret = ''; + if ($this->hasClientId()) { + $clientId = app('preferences')->getForUser($this->user, 'ynab_client_id', null)->data; + } + if ($this->hasClientSecret()) { + $clientSecret = app('preferences')->getForUser($this->user, 'ynab_client_secret', null)->data; + } + + $callBackUri = route('import.callback.ynab'); + + return ['client_id' => $clientId, 'client_secret' => $clientSecret, 'callback_uri' => $callBackUri]; + } + + /** + * Indicate if all prerequisites have been met. + * + * @return bool + */ + public function isComplete(): bool + { + return $this->hasClientId() && $this->hasClientSecret(); + } + + /** + * Set the user for this Prerequisites-routine. Class is expected to implement and save this. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + + /** + * This method responds to the user's submission of an API key. Should do nothing but store the value. + * + * Errors must be returned in the message bag under the field name they are requested by. + * + * @param array $data + * + * @return MessageBag + */ + public function storePrerequisites(array $data): MessageBag + { + $clientId = $data['client_id'] ?? ''; + $clientSecret = $data['client_secret'] ?? ''; + Log::debug('Storing YNAB client data'); + app('preferences')->setForUser($this->user, 'ynab_client_id', $clientId); + app('preferences')->setForUser($this->user, 'ynab_client_secret', $clientSecret); + + return new MessageBag; + } + + /** + * Check if we have the client ID. + * + * @return bool + */ + private function hasClientId(): bool + { + $clientId = app('preferences')->getForUser($this->user, 'ynab_client_id', null); + if (null === $clientId) { + return false; + } + if ('' === (string)$clientId->data) { + return false; + } + + return true; + } + + /** + * Check if we have the client secret + * + * @return bool + */ + private function hasClientSecret(): bool + { + $clientSecret = app('preferences')->getForUser($this->user, 'ynab_client_secret', null); + if (null === $clientSecret) { + return false; + } + if ('' === (string)$clientSecret->data) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/app/Import/Routine/BunqRoutine.php b/app/Import/Routine/BunqRoutine.php index d450655345..192ff26b4b 100644 --- a/app/Import/Routine/BunqRoutine.php +++ b/app/Import/Routine/BunqRoutine.php @@ -50,12 +50,12 @@ class BunqRoutine implements RoutineInterface */ public function run(): void { - Log::debug(sprintf('Now in SpectreRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); + Log::debug(sprintf('Now in BunqRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); $valid = ['ready_to_run']; // should be only ready_to_run if (\in_array($this->importJob->status, $valid, true)) { switch ($this->importJob->stage) { default: - throw new FireflyException(sprintf('SpectreRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore + throw new FireflyException(sprintf('BunqRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore case 'new': // list all of the users accounts. $this->repository->setStatus($this->importJob, 'running'); diff --git a/app/Import/Routine/YnabRoutine.php b/app/Import/Routine/YnabRoutine.php new file mode 100644 index 0000000000..687aa74c40 --- /dev/null +++ b/app/Import/Routine/YnabRoutine.php @@ -0,0 +1,85 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Import\Routine; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\Routine\Ynab\StageGetAccessHandler; +use Log; + +/** + * Class YnabRoutine + */ +class YnabRoutine implements RoutineInterface +{ + /** @var ImportJob The import job */ + private $importJob; + + /** @var ImportJobRepositoryInterface Import job repository */ + private $repository; + + /** + * At the end of each run(), the import routine must set the job to the expected status. + * + * The final status of the routine must be "provider_finished". + * + * @throws FireflyException + */ + public function run(): void + { + Log::debug(sprintf('Now in YNAB routine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); + $valid = ['ready_to_run']; // should be only ready_to_run + if (\in_array($this->importJob->status, $valid, true)) { + + // get access token from YNAB + if ('get_access_token' === $this->importJob->stage) { + // list all of the users accounts. + $this->repository->setStatus($this->importJob, 'running'); + /** @var StageGetAccessHandler $handler */ + $handler = app(StageGetAccessHandler::class); + $handler->setImportJob($this->importJob); + $handler->run(); + $this->repository->setStage($this->importJob, 'get_transactions'); + return; + } + throw new FireflyException(sprintf('YNAB import routine cannot handle stage "%s"', $this->importJob->stage)); + } + throw new FireflyException(sprintf('YNAB import routine cannot handle status "%s"', $this->importJob->status)); + } + + /** + * Set the import job. + * + * @param ImportJob $importJob + * + * @return void + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Support/Binder/ImportProvider.php b/app/Support/Binder/ImportProvider.php index 989a616936..926696b8ab 100644 --- a/app/Support/Binder/ImportProvider.php +++ b/app/Support/Binder/ImportProvider.php @@ -54,21 +54,18 @@ class ImportProvider implements BinderInterface foreach ($providerNames as $providerName) { // only consider enabled providers $enabled = (bool)config(sprintf('import.enabled.%s', $providerName)); - $allowedForDemo = (bool)config(sprintf('import.allowed_for_demo.%s', $providerName)); $allowedForUser = (bool)config(sprintf('import.allowed_for_user.%s', $providerName)); if (false === $enabled) { continue; } - if (true === $isDemoUser && false === $allowedForDemo) { - continue; - } if (false === $isDemoUser && false === $allowedForUser && false === $isDebug) { continue; // @codeCoverageIgnore } $providers[$providerName] = [ - 'has_prereq' => (bool)config('import.has_prereq.' . $providerName), + 'has_prereq' => (bool)config('import.has_prereq.' . $providerName), + 'allowed_for_demo' => (bool)config(sprintf('import.allowed_for_demo.%s', $providerName)), ]; $class = (string)config(sprintf('import.prerequisites.%s', $providerName)); $result = false; diff --git a/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php b/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php new file mode 100644 index 0000000000..9c364f1719 --- /dev/null +++ b/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php @@ -0,0 +1,86 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Ynab; + +use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Log; + +/** + * Class StageGetAccessHandler + */ +class StageGetAccessHandler +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Send a token request to YNAB. Return with access token (if all goes well). + * + * @throws FireflyException + */ + public function run(): void + { + $config = $this->repository->getConfiguration($this->importJob); + $clientId = app('preferences')->get('ynab_client_id', '')->data; + $clientSecret = app('preferences')->get('ynab_client_secret', '')->data; + $redirectUri = route('import.callback.ynab'); + $code = $config['auth_code']; + $uri = sprintf( + 'https://app.youneedabudget.com/oauth/token?client_id=%s&client_secret=%s&redirect_uri=%s&grant_type=authorization_code&code=%s', $clientId, + $clientSecret, $redirectUri, $code + ); + $client = new Client; + try { + $res = $client->request('POST', $uri); + } catch (GuzzleException|Exception $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException($e->getMessage()); + } + $statusCode = $res->getStatusCode(); + $content = trim($res->getBody()->getContents()); + $json = json_decode($content, true) ?? []; + Log::debug(sprintf('Status code from YNAB is %d', $statusCode)); + Log::debug(sprintf('Body of result is %s', $content), $json); + Log::error('Hard exit'); + exit; + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/config/import.php b/config/import.php index ed49fa8417..17854afa90 100644 --- a/config/import.php +++ b/config/import.php @@ -26,13 +26,16 @@ use FireflyIII\Import\JobConfiguration\BunqJobConfiguration; use FireflyIII\Import\JobConfiguration\FakeJobConfiguration; use FireflyIII\Import\JobConfiguration\FileJobConfiguration; use FireflyIII\Import\JobConfiguration\SpectreJobConfiguration; +use FireflyIII\Import\JobConfiguration\YnabJobConfiguration; use FireflyIII\Import\Prerequisites\BunqPrerequisites; use FireflyIII\Import\Prerequisites\FakePrerequisites; use FireflyIII\Import\Prerequisites\SpectrePrerequisites; +use FireflyIII\Import\Prerequisites\YnabPrerequisites; use FireflyIII\Import\Routine\BunqRoutine; use FireflyIII\Import\Routine\FakeRoutine; use FireflyIII\Import\Routine\FileRoutine; use FireflyIII\Import\Routine\SpectreRoutine; +use FireflyIII\Import\Routine\YnabRoutine; use FireflyIII\Support\Import\Routine\File\CSVProcessor; return [ @@ -42,6 +45,7 @@ return [ 'file' => true, 'bunq' => true, 'spectre' => true, + 'ynab' => true, 'plaid' => false, 'quovo' => false, 'yodlee' => false, @@ -53,6 +57,7 @@ return [ 'file' => false, 'bunq' => false, 'spectre' => false, + 'ynab' => false, 'plaid' => false, 'quovo' => false, 'yodlee' => false, @@ -63,6 +68,7 @@ return [ 'file' => true, 'bunq' => true, 'spectre' => true, + 'ynab' => true, 'plaid' => true, 'quovo' => true, 'yodlee' => true, @@ -73,6 +79,7 @@ return [ 'file' => false, 'bunq' => true, 'spectre' => true, + 'ynab' => true, 'plaid' => true, 'quovo' => true, 'yodlee' => true, @@ -83,6 +90,7 @@ return [ 'file' => false, 'bunq' => BunqPrerequisites::class, 'spectre' => SpectrePrerequisites::class, + 'ynab' => YnabPrerequisites::class, 'plaid' => false, 'quovo' => false, 'yodlee' => false, @@ -93,6 +101,7 @@ return [ 'file' => true, 'bunq' => true, 'spectre' => true, + 'ynab' => true, 'plaid' => false, 'quovo' => false, 'yodlee' => false, @@ -103,6 +112,7 @@ return [ 'file' => FileJobConfiguration::class, 'bunq' => BunqJobConfiguration::class, 'spectre' => SpectreJobConfiguration::class, + 'ynab' => YnabJobConfiguration::class, 'plaid' => false, 'quovo' => false, 'yodlee' => false, @@ -113,6 +123,7 @@ return [ 'file' => FileRoutine::class, 'bunq' => BunqRoutine::class, 'spectre' => SpectreRoutine::class, + 'ynab' => YnabRoutine::class, 'plaid' => false, 'quovo' => false, 'yodlee' => false, @@ -140,6 +151,9 @@ return [ 'spectre' => [ 'server' => 'www.saltedge.com', ], + 'ynab' => [ + 'client_id' => '666db19f6c5a2299bf44999a6ea802e96a5f488c3a5c8a5cbb417b59dcf18b72', + ], 'plaid' => [], 'quovo' => [], 'yodlee' => [], diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index fe18364b8e..c3d02d9cee 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index f6b93c10eb..a0160663e5 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -28,9 +28,11 @@ return [ 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', '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.', + 'disabled_for_demo_user' => 'disabled in demo', // index page: 'general_index_title' => 'Import a file', @@ -43,6 +45,7 @@ return [ 'button_plaid' => 'Import using Plaid', 'button_yodlee' => 'Import using Yodlee', 'button_quovo' => 'Import using Quovo', + 'button_ynab' => 'Import from You Need A Budget', // 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.', @@ -76,10 +79,14 @@ return [ 'prereq_bunq_title' => 'Prerequisites for an import from bunq', 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', @@ -219,7 +226,7 @@ return [ 'column_account-iban' => 'Asset account (IBAN)', 'column_account-id' => 'Asset account ID (matching FF3)', 'column_account-name' => 'Asset account (name)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Amount', 'column_amount_foreign' => 'Amount (in foreign currency)', 'column_amount_debit' => 'Amount (debit column)', diff --git a/resources/views/import/index.twig b/resources/views/import/index.twig index e1d542d513..f729647d09 100644 --- a/resources/views/import/index.twig +++ b/resources/views/import/index.twig @@ -18,11 +18,18 @@ {% for name, provider in providers %} {# button for each import thing: #}
+ {% if not provider.allowed_for_demo and isDemoUser %} + {{ trans(('import.button_'~name)) }}
+ {{ trans(('import.button_'~name)) }}
+ ({{ trans('import.disabled_for_demo_user') }}) + {% else %} {{ trans(('import.button_'~name)) }}
{{ trans(('import.button_'~name)) }}
+ {% endif %}
+ {% endfor %} diff --git a/resources/views/import/spectre/redirect.twig b/resources/views/import/spectre/redirect.twig index 72f9ca282b..84d4547cad 100644 --- a/resources/views/import/spectre/redirect.twig +++ b/resources/views/import/spectre/redirect.twig @@ -11,4 +11,4 @@ If you are not redirected automatically, follow this link to Spectre.. -#} + diff --git a/resources/views/import/ynab/prerequisites.twig b/resources/views/import/ynab/prerequisites.twig new file mode 100644 index 0000000000..a2c15ebf00 --- /dev/null +++ b/resources/views/import/ynab/prerequisites.twig @@ -0,0 +1,53 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
+
+ +
+
+
+

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

+
+
+
+
+

+ {{ trans('import.prereq_ynab_text')|raw }} +

+

+ {{ trans('import.prereq_ynab_redirect')|raw }} +

+ {{ callback_uri }} +

+
+
+ +
+
+ {{ ExpandedForm.text('client_id', client_id) }} +
+
+
+
+ {{ ExpandedForm.text('client_secret', client_secret) }} +
+
+
+ +
+
+
+
+{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} diff --git a/resources/views/import/ynab/redirect.twig b/resources/views/import/ynab/redirect.twig new file mode 100644 index 0000000000..56b0a159d4 --- /dev/null +++ b/resources/views/import/ynab/redirect.twig @@ -0,0 +1,14 @@ + + + + + + + Page Redirection + + +If you are not redirected automatically, follow this link to YNAB.. + + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index aabc5e1214..46fc37283b 100755 --- a/routes/web.php +++ b/routes/web.php @@ -503,6 +503,9 @@ Route::group( // download config: Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'job.download']); + + // callback URI for YNAB OAuth. Sadly, needs a custom solution. + Route::get('ynab-callback', ['uses' => 'Import\CallbackController@ynab', 'as' => 'callback.ynab']); } ); From 7ad09da4e9fa2d051235a20ffa55cc81f228500c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Jul 2018 16:04:22 +0200 Subject: [PATCH 004/166] Fix #1576 --- app/Services/Internal/Support/JournalServiceTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index 3b97e9d375..a5de54011c 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -75,7 +75,7 @@ trait JournalServiceTrait /** @var BillFactory $factory */ $factory = app(BillFactory::class); $factory->setUser($journal->user); - $bill = $factory->find($data['bill_id'], $data['bill_name']); + $bill = $factory->find((int)$data['bill_id'], $data['bill_name']); if (null !== $bill) { $journal->bill_id = $bill->id; From dfd9cf08740b752f731cb91f880d008fb00f849e Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 29 Jul 2018 21:02:03 +0200 Subject: [PATCH 005/166] New code for YNAB import. --- .../Import/JobStatusController.php | 9 +- .../JobConfiguration/YnabJobConfiguration.php | 79 ++-- app/Import/Routine/YnabRoutine.php | 63 ++- .../ImportJob/ImportJobRepository.php | 1 - .../Ynab/Request/GetAccountsRequest.php | 53 +++ .../Ynab/Request/GetBudgetsRequest.php | 66 +++ app/Services/Ynab/Request/YnabRequest.php | 100 ++++ .../Ynab/NewYnabJobHandler.php | 252 ++++++++++ .../Ynab/SelectBudgetsHandler.php | 126 +++++ .../Ynab/YnabJobConfigurationInterface.php | 72 +++ .../Routine/Ynab/GetAccountsHandler.php | 80 ++++ .../Routine/Ynab/StageGetAccessHandler.php | 26 +- .../Routine/Ynab/StageGetBudgetsHandler.php | 75 +++ .../Ynab/StageGetTransactionsHandler.php | 55 +++ .../Ynab/StageMatchAccountsHandler.php | 57 +++ app/Support/Preferences.php | 2 +- config/import.php | 5 +- resources/lang/en_US/import.php | 445 +++++++++--------- .../views/import/ynab/select-budgets.twig | 39 ++ 19 files changed, 1341 insertions(+), 264 deletions(-) create mode 100644 app/Services/Ynab/Request/GetAccountsRequest.php create mode 100644 app/Services/Ynab/Request/GetBudgetsRequest.php create mode 100644 app/Services/Ynab/Request/YnabRequest.php create mode 100644 app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php create mode 100644 app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php create mode 100644 app/Support/Import/JobConfiguration/Ynab/YnabJobConfigurationInterface.php create mode 100644 app/Support/Import/Routine/Ynab/GetAccountsHandler.php create mode 100644 app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php create mode 100644 app/Support/Import/Routine/Ynab/StageGetTransactionsHandler.php create mode 100644 app/Support/Import/Routine/Ynab/StageMatchAccountsHandler.php create mode 100644 resources/views/import/ynab/select-budgets.twig diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index 62adfeb7eb..56e7df8005 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -132,11 +132,12 @@ class JobStatusController extends Controller */ public function start(ImportJob $importJob): JsonResponse { + Log::debug('Now in JobStatusController::start'); // catch impossible status: $allowed = ['ready_to_run', 'need_job_config']; if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { - Log::error('Job is not ready.'); + Log::error(sprintf('Job is not ready. Status should be in array, but is %s', $importJob->status), $allowed); $this->repository->setStatus($importJob, 'error'); return response()->json( @@ -157,7 +158,11 @@ class JobStatusController extends Controller /** @var RoutineInterface $routine */ $routine = app($className); $routine->setImportJob($importJob); + + Log::debug(sprintf('Created class of type %s', $className)); + try { + Log::debug(sprintf('Try to call %s:run()', $className)); $routine->run(); } catch (FireflyException|Exception $e) { $message = 'The import routine crashed: ' . $e->getMessage(); @@ -189,7 +194,7 @@ class JobStatusController extends Controller // catch impossible status: $allowed = ['provider_finished', 'storing_data']; if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { - Log::error('Job is not ready.'); + Log::error(sprintf('Job is not ready. Status should be in array, but is %s', $importJob->status), $allowed); return response()->json( ['status' => 'NOK', 'message' => sprintf('JobStatusController::start expects status "provider_finished" instead of "%s".', $importJob->status)] diff --git a/app/Import/JobConfiguration/YnabJobConfiguration.php b/app/Import/JobConfiguration/YnabJobConfiguration.php index a42055b970..e557560244 100644 --- a/app/Import/JobConfiguration/YnabJobConfiguration.php +++ b/app/Import/JobConfiguration/YnabJobConfiguration.php @@ -23,8 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Import\JobConfiguration; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\JobConfiguration\Ynab\NewYnabJobHandler; +use FireflyIII\Support\Import\JobConfiguration\Ynab\SelectBudgetsHandler; +use FireflyIII\Support\Import\JobConfiguration\Ynab\YnabJobConfigurationInterface; use Illuminate\Support\MessageBag; use Log; @@ -33,6 +37,8 @@ use Log; */ class YnabJobConfiguration implements JobConfigurationInterface { + /** @var YnabJobConfigurationInterface The job handler. */ + private $handler; /** @var ImportJob The import job */ private $importJob; /** @var ImportJobRepositoryInterface Import job repository */ @@ -45,16 +51,7 @@ class YnabJobConfiguration implements JobConfigurationInterface */ public function configurationComplete(): bool { - // config is only needed when the job is in stage "new". - if ($this->importJob->stage === 'new') { - Log::debug('YNAB configurationComplete: stage is new, return false'); - - return false; - } - - Log::debug('YNAB configurationComplete: stage is not new, return true'); - - return true; + return $this->handler->configurationComplete(); } /** @@ -67,10 +64,7 @@ class YnabJobConfiguration implements JobConfigurationInterface */ public function configureJob(array $data): MessageBag { - Log::debug('YNAB configureJob: nothing to do.'); - - // there is never anything to store from this job. - return new MessageBag; + return $this->handler->configureJob($data); } /** @@ -80,24 +74,7 @@ class YnabJobConfiguration implements JobConfigurationInterface */ public function getNextData(): array { - $data = []; - // here we update the job so it can redirect properly to YNAB - if ($this->importJob->stage === 'new') { - - // update stage to make sure we catch the token. - $this->repository->setStage($this->importJob, 'catch-auth-code'); - $clientId = (string)config('import.options.ynab.client_id'); - $callBackUri = route('import.callback.ynab'); - $uri = sprintf( - 'https://app.youneedabudget.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s', $clientId, $callBackUri, - $this->importJob->key - ); - $data['token-url'] = $uri; - Log::debug(sprintf('YNAB getNextData: URI to redirect to is %s', $uri)); - } - - return $data; - + return $this->handler->getNextData(); } /** @@ -107,19 +84,53 @@ class YnabJobConfiguration implements JobConfigurationInterface */ public function getNextView(): string { - Log::debug('Return YNAB redirect view.'); - return 'import.ynab.redirect'; + return $this->handler->getNextView(); } /** * Set import job. * * @param ImportJob $importJob + * + * @throws FireflyException */ public function setImportJob(ImportJob $importJob): void { $this->importJob = $importJob; $this->repository = app(ImportJobRepositoryInterface::class); $this->repository->setUser($importJob->user); + $this->handler = $this->getHandler(); + } + + /** + * Get correct handler. + * + * @return YnabJobConfigurationInterface + * @throws FireflyException + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function getHandler(): YnabJobConfigurationInterface + { + Log::debug(sprintf('Now in YnabJobConfiguration::getHandler() with stage "%s"', $this->importJob->stage)); + $handler = null; + switch ($this->importJob->stage) { + case 'new': + /** @var NewYnabJobHandler $handler */ + $handler = app(NewYnabJobHandler::class); + $handler->setImportJob($this->importJob); + break; + case 'select_budgets': + /** @var SelectBudgetsHandler $handler */ + $handler = app(SelectBudgetsHandler::class); + $handler->setImportJob($this->importJob); + break; + default: + // @codeCoverageIgnoreStart + throw new FireflyException(sprintf('Firefly III cannot create a YNAB configuration handler for stage "%s"', $this->importJob->stage)); + // @codeCoverageIgnoreEnd + } + + return $handler; } } \ No newline at end of file diff --git a/app/Import/Routine/YnabRoutine.php b/app/Import/Routine/YnabRoutine.php index 687aa74c40..fda1ed5611 100644 --- a/app/Import/Routine/YnabRoutine.php +++ b/app/Import/Routine/YnabRoutine.php @@ -26,7 +26,9 @@ namespace FireflyIII\Import\Routine; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Support\Import\Routine\Ynab\GetAccountsHandler; use FireflyIII\Support\Import\Routine\Ynab\StageGetAccessHandler; +use FireflyIII\Support\Import\Routine\Ynab\StageGetBudgetsHandler; use Log; /** @@ -61,12 +63,69 @@ class YnabRoutine implements RoutineInterface $handler = app(StageGetAccessHandler::class); $handler->setImportJob($this->importJob); $handler->run(); - $this->repository->setStage($this->importJob, 'get_transactions'); + + // back to correct stage: + $this->repository->setStatus($this->importJob, 'ready_to_run'); + $this->repository->setStage($this->importJob, 'get_budgets'); + return; } + if ('get_budgets' === $this->importJob->stage) { + $this->repository->setStatus($this->importJob, 'running'); + /** @var StageGetBudgetsHandler $handler */ + $handler = app(StageGetBudgetsHandler::class); + $handler->setImportJob($this->importJob); + $handler->run(); + + // count budgets in job, to determine next step. + $configuration = $this->repository->getConfiguration($this->importJob); + $budgets = $configuration['budgets'] ?? []; + + // if more than 1 budget, select budget first. + if (\count($budgets) > 0) { // TODO should be 1 + $this->repository->setStage($this->importJob, 'select_budgets'); + $this->repository->setStatus($this->importJob, 'need_job_config'); + return; + } + + if (\count($budgets) === 1) { + $this->repository->setStage($this->importJob, 'match_accounts'); + } + + return; + } + if('get_accounts' === $this->importJob->stage) { + $this->repository->setStatus($this->importJob, 'running'); + + /** @var GetAccountsHandler $handler */ + $handler = app(GetAccountsHandler::class); + $handler->setImportJob($this->importJob); + $handler->run(); + + $this->repository->setStage($this->importJob, 'select_accounts'); + $this->repository->setStatus($this->importJob, 'need_job_config'); + + } + +// if ('match_accounts' === $this->importJob->stage) { +// // $this->repository->setStatus($this->importJob, 'running'); +// /** @var StageGetBudgetsHandler $handler */ +// $handler = app(StageGetBudgetsHandler::class); +// $handler->setImportJob($this->importJob); +// $handler->run(); +// $this->repository->setStage($this->importJob, 'get_transactions'); +// } +// +// if ('get_transactions' === $this->importJob->stage) { +// // $this->repository->setStatus($this->importJob, 'running'); +// /** @var StageGetBudgetsHandler $handler */ +// $handler = app(StageGetBudgetsHandler::class); +// $handler->setImportJob($this->importJob); +// $handler->run(); +// $this->repository->setStage($this->importJob, 'get_transactions'); +// } throw new FireflyException(sprintf('YNAB import routine cannot handle stage "%s"', $this->importJob->stage)); } - throw new FireflyException(sprintf('YNAB import routine cannot handle status "%s"', $this->importJob->status)); } /** diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 120e612f40..43efe32767 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -184,7 +184,6 @@ 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/Services/Ynab/Request/GetAccountsRequest.php b/app/Services/Ynab/Request/GetAccountsRequest.php new file mode 100644 index 0000000000..25e15bdf43 --- /dev/null +++ b/app/Services/Ynab/Request/GetAccountsRequest.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Ynab\Request; + +use Log; + +/** + * Class GetAccountsRequest + */ +class GetAccountsRequest extends YnabRequest +{ + /** @var array */ + public $accounts; + /** @var string */ + public $budgetId; + + /** + * + */ + public function call(): void + { + Log::debug('Now in GetAccountsRequest::call()'); + $uri = $this->api . sprintf('/budgets/%s/accounts', $this->budgetId); + + Log::debug(sprintf('URI is %s', $uri)); + + $result = $this->authenticatedGetRequest($uri, []); + + // expect data in [data][accounts] + $this->accounts = $result['data']['accounts'] ?? []; + } +} \ No newline at end of file diff --git a/app/Services/Ynab/Request/GetBudgetsRequest.php b/app/Services/Ynab/Request/GetBudgetsRequest.php new file mode 100644 index 0000000000..cf7948f319 --- /dev/null +++ b/app/Services/Ynab/Request/GetBudgetsRequest.php @@ -0,0 +1,66 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Ynab\Request; + +use Log; + +/** + * Class GetBudgetsRequest + */ +class GetBudgetsRequest extends YnabRequest +{ + /** @var array */ + public $budgets; + + public function __construct() + { + parent::__construct(); + $this->budgets = []; + } + + /** + * + */ + public function call(): void + { + Log::debug('Now in GetBudgetsRequest::call()'); + $uri = $this->api . '/budgets'; + + Log::debug(sprintf('URI is %s', $uri)); + + $result = $this->authenticatedGetRequest($uri, []); + + // expect data in [data][budgets] + $rawBudgets = $result['data']['budgets'] ?? []; + $freshBudgets = []; + foreach ($rawBudgets as $rawBudget) { + $freshBudgets[] = [ + 'id' => $rawBudget['id'], + 'name' => $rawBudget['name'], + 'currency_code' => $rawBudget['currency_format']['iso_code'], + ]; + } + $this->budgets = $freshBudgets; + } +} \ No newline at end of file diff --git a/app/Services/Ynab/Request/YnabRequest.php b/app/Services/Ynab/Request/YnabRequest.php new file mode 100644 index 0000000000..3e6a00a185 --- /dev/null +++ b/app/Services/Ynab/Request/YnabRequest.php @@ -0,0 +1,100 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Ynab\Request; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Log; +use RuntimeException; + +/** + * Class YnabRequest + */ +abstract class YnabRequest +{ + /** @var string */ + protected $api; + + /** @var string */ + protected $token; + + public function __construct() + { + $this->api = 'https://' . config('import.options.ynab.live') . '/' . config('import.options.ynab.version'); + } + + /** + * @param string $uri + * @param array|null $params + * + * @return array + */ + public function authenticatedGetRequest(string $uri, array $params = null): array + { + Log::debug(sprintf('Now in YnabRequest::authenticatedGetRequest(%s)', $uri), $params); + $client = new Client; + $params = $params ?? []; + $options = [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->token, + ], + ]; + if (\count($params) > 0) { + $uri = $uri . '?' . http_build_query($params); + } + Log::debug(sprintf('Going to call YNAB on URI: %s', $uri), $options); + try { + $res = $client->request('get', $uri, $options); + } catch (GuzzleException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + + return []; + } + try { + $content = trim($res->getBody()->getContents()); + } catch (RuntimeException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + + return []; + } + + return json_decode($content, true) ?? []; + } + + /** + * + */ + abstract public function call(): void; + + /** + * @param string $token + */ + public function setAccessToken(string $token): void + { + $this->token = $token; + } + +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php b/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php new file mode 100644 index 0000000000..725bd38766 --- /dev/null +++ b/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php @@ -0,0 +1,252 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\JobConfiguration\Ynab; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Illuminate\Support\MessageBag; +use Log; +use RuntimeException; + +/** + * Class NewYnabJobHandler + */ +class NewYnabJobHandler implements YnabJobConfigurationInterface +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Return true when this stage is complete. + * + * @return bool + * @throws FireflyException + */ + public function configurationComplete(): bool + { + if (!$this->hasRefreshToken()) { + Log::debug('YNAB NewYnabJobHandler configurationComplete: stage is new, no refresh token, return false'); + + return false; + } + if ($this->hasRefreshToken() && $this->hasClientId() && $this->hasClientSecret()) { + Log::debug('YNAB NewYnabJobHandler configurationComplete: stage is new, has a refresh token, return true'); + // need to grab access token using refresh token + $this->getAccessToken(); + $this->repository->setStatus($this->importJob, 'ready_to_run'); + $this->repository->setStage($this->importJob, 'get_budgets'); + + return true; + } + Log::error('YNAB NewYnabJobHandler configurationComplete: something broke, return true'); + + return true; + } + + /** + * Store the job configuration. There is never anything to store for this stage. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag + { + Log::debug('YNAB NewYnabJobHandler configureJob: nothing to do.'); + + return new MessageBag; + } + + /** + * Get data for config view. + * + * @return array + */ + public function getNextData(): array + { + $data = []; + // here we update the job so it can redirect properly to YNAB + if (!$this->hasRefreshToken() && $this->hasClientSecret() && $this->hasClientId()) { + // update stage to make sure we catch the token. + $this->repository->setStage($this->importJob, 'catch-auth-code'); + $clientId = app('preferences')->get('ynab_client_id')->data; + $callBackUri = route('import.callback.ynab'); + $uri = sprintf( + 'https://app.youneedabudget.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s', $clientId, $callBackUri, + $this->importJob->key + ); + $data['token-url'] = $uri; + Log::debug(sprintf('YNAB getNextData: URI to redirect to is %s', $uri)); + } + + return $data; + } + + /** + * Get the view for this stage. + * + * @return string + */ + public function getNextView(): string + { + Log::debug('Return YNAB redirect view.'); + + return 'import.ynab.redirect'; + } + + /** + * Set the import job. + * + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } + + + /** + * @throws FireflyException + */ + private function getAccessToken(): void + { + $clientId = app('preferences')->get('ynab_client_id')->data; + $clientSecret = app('preferences')->get('ynab_client_secret')->data; + $refreshToken = app('preferences')->get('ynab_refresh_token')->data; + $uri = sprintf( + 'https://app.youneedabudget.com/oauth/token?client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s', $clientId, $clientSecret, + $refreshToken + ); + + $client = new Client(); + try { + $res = $client->request('post', $uri, []); + } catch (GuzzleException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException($e->getMessage()); + } + $statusCode = $res->getStatusCode(); + try { + $content = trim($res->getBody()->getContents()); + } catch (RuntimeException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException($e->getMessage()); + } + $json = json_decode($content, true) ?? []; + Log::debug(sprintf('Status code from YNAB is %d', $statusCode)); + Log::debug(sprintf('Body of result is %s', $content), $json); + + // store refresh token (if present?) as preference + // store token in job: + $configuration = $this->repository->getConfiguration($this->importJob); + $configuration['access_token'] = $json['access_token']; + $configuration['access_token_expires'] = (int)$json['created_at'] + (int)$json['expires_in']; + $this->repository->setConfiguration($this->importJob, $configuration); + + // also store new refresh token: + $refreshToken = (string)($json['refresh_token'] ?? ''); + if ('' !== $refreshToken) { + app('preferences')->set('ynab_refresh_token', $refreshToken); + } + + + Log::debug('end of NewYnabJobHandler::getAccessToken()'); + } + + /** + * Check if we have the client ID. + * + * @return bool + */ + private function hasClientId(): bool + { + $clientId = app('preferences')->getForUser($this->importJob->user, 'ynab_client_id', null); + if (null === $clientId) { + Log::debug('user has no YNAB client ID'); + + return false; + } + if ('' === (string)$clientId->data) { + Log::debug('user has no YNAB client ID (empty)'); + + return false; + } + Log::debug('user has a YNAB client ID'); + + return true; + } + + /** + * Check if we have the client secret + * + * @return bool + */ + private function hasClientSecret(): bool + { + $clientSecret = app('preferences')->getForUser($this->importJob->user, 'ynab_client_secret', null); + if (null === $clientSecret) { + Log::debug('user has no YNAB client secret'); + + return false; + } + if ('' === (string)$clientSecret->data) { + Log::debug('user has no YNAB client secret (empty)'); + + return false; + } + Log::debug('user has a YNAB client secret'); + + return true; + } + + /** + * @return bool + */ + private function hasRefreshToken(): bool + { + $preference = app('preferences')->get('ynab_refresh_token'); + if (null === $preference) { + Log::debug('user has no YNAB refresh token.'); + + return false; + } + if ('' === (string)$preference->data) { + Log::debug('user has no YNAB refresh token (empty).'); + + return false; + } + Log::debug(sprintf('user has YNAB refresh token: %s', $preference->data)); + + return true; + } +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php new file mode 100644 index 0000000000..e0e43a2916 --- /dev/null +++ b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php @@ -0,0 +1,126 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\JobConfiguration\Ynab; + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\MessageBag; +use Log; + +/** + * Class SelectBudgetsHandler + */ +class SelectBudgetsHandler implements YnabJobConfigurationInterface +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Return true when this stage is complete. + * + * @return bool + */ + public function configurationComplete(): bool + { + Log::debug('Now in SelectBudgetsHandler::configComplete'); + $configuration = $this->repository->getConfiguration($this->importJob); + $selectedBudget = $configuration['selected_budget'] ?? ''; + if ($selectedBudget !== '') { + Log::debug(sprintf('Selected budget is %s, config is complete. Return true.', $selectedBudget)); + $this->repository->setStage($this->importJob, 'get_accounts'); + return true; + } + Log::debug('User has not selected a budget yet, config is not yet complete.'); + + return false; + } + + /** + * Store the job configuration. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag + { + Log::debug('Now in SelectBudgetsHandler::configureJob'); + $configuration = $this->repository->getConfiguration($this->importJob); + $configuration['selected_budget'] = $data['budget_id']; + + Log::debug(sprintf('Set selected budget to %s', $data['budget_id'])); + Log::debug('Mark job as ready for next stage.'); + + + $this->repository->setConfiguration($this->importJob, $configuration); + + return new MessageBag; + } + + /** + * Get data for config view. + * + * @return array + */ + public function getNextData(): array + { + Log::debug('Now in SelectBudgetsHandler::getNextData'); + $configuration = $this->repository->getConfiguration($this->importJob); + $budgets = $configuration['budgets'] ?? []; + $return = []; + foreach ($budgets as $budget) { + $return[$budget['id']] = $budget['name'] . ' (' . $budget['currency_code'] . ')'; + } + + return [ + 'budgets' => $return, + ]; + } + + /** + * Get the view for this stage. + * + * @return string + */ + public function getNextView(): string + { + Log::debug('Now in SelectBudgetsHandler::getNextView'); + + return 'import.ynab.select-budgets'; + } + + /** + * Set the import job. + * + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Ynab/YnabJobConfigurationInterface.php b/app/Support/Import/JobConfiguration/Ynab/YnabJobConfigurationInterface.php new file mode 100644 index 0000000000..5458d1ef53 --- /dev/null +++ b/app/Support/Import/JobConfiguration/Ynab/YnabJobConfigurationInterface.php @@ -0,0 +1,72 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\JobConfiguration\Ynab; +use FireflyIII\Models\ImportJob; +use Illuminate\Support\MessageBag; + +/** + * Interface YnabJobConfigurationInterface + * + * @package FireflyIII\Support\Import\JobConfiguration\Ynab + */ +interface YnabJobConfigurationInterface +{ + /** + * Return true when this stage is complete. + * + * @return bool + */ + public function configurationComplete(): bool; + + + /** + * Store the job configuration. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag; + + /** + * Get data for config view. + * + * @return array + */ + public function getNextData(): array; + + /** + * Get the view for this stage. + * + * @return string + */ + public function getNextView(): string; + + /** + * Set the import job. + * + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void; +} \ No newline at end of file diff --git a/app/Support/Import/Routine/Ynab/GetAccountsHandler.php b/app/Support/Import/Routine/Ynab/GetAccountsHandler.php new file mode 100644 index 0000000000..a0c05fd195 --- /dev/null +++ b/app/Support/Import/Routine/Ynab/GetAccountsHandler.php @@ -0,0 +1,80 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Ynab; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Services\Ynab\Request\GetAccountsRequest; + +/** + * Class GetAccountsHandler + */ +class GetAccountsHandler +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Get list of accounts for the selected budget. + * + * @throws FireflyException + */ + public function run(): void + { + + $config = $this->repository->getConfiguration($this->importJob); + $selectedBudget = $config['selected_budget'] ?? ''; + if ('' === $selectedBudget) { + $firstBudget = $config['budgets'][0] ?? false; + if (false === $firstBudget) { + throw new FireflyException('The configuration contains no budget. Erroring out.'); + } + $selectedBudget = $firstBudget['id']; + $config['selected_budget'] = $selectedBudget; + } + $token = $config['access_token']; + $request = new GetAccountsRequest; + $request->budgetId = $selectedBudget; + $request->setAccessToken($token); + $request->call(); + $config['accounts'] = $request->accounts; + $this->repository->setConfiguration($this->importJob, $config); + if (0 === \count($config['accounts'])) { + throw new FireflyException('This budget contains zero accounts.'); + } + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php b/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php index 9c364f1719..489a75766d 100644 --- a/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php +++ b/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php @@ -30,6 +30,7 @@ use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; use Log; +use RuntimeException; /** * Class StageGetAccessHandler @@ -66,12 +67,31 @@ class StageGetAccessHandler throw new FireflyException($e->getMessage()); } $statusCode = $res->getStatusCode(); - $content = trim($res->getBody()->getContents()); + try { + $content = trim($res->getBody()->getContents()); + } catch(RuntimeException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException($e->getMessage()); + } + $json = json_decode($content, true) ?? []; Log::debug(sprintf('Status code from YNAB is %d', $statusCode)); Log::debug(sprintf('Body of result is %s', $content), $json); - Log::error('Hard exit'); - exit; + + // store refresh token (if present?) as preference + // store token in job: + $configuration = $this->repository->getConfiguration($this->importJob); + $configuration['access_token'] = $json['access_token']; + $configuration['access_token_expires'] = (int)$json['created_at'] + (int)$json['expires_in']; + $this->repository->setConfiguration($this->importJob, $configuration); + + Log::debug('end of StageGetAccessHandler::run()'); + + $refreshToken = (string)($json['refresh_token'] ?? ''); + if ('' !== $refreshToken) { + app('preferences')->set('ynab_refresh_token', $refreshToken); + } } /** diff --git a/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php b/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php new file mode 100644 index 0000000000..642298d4bb --- /dev/null +++ b/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php @@ -0,0 +1,75 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Ynab; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Services\Ynab\Request\GetBudgetsRequest; +use Log; + +/** + * Class StageGetBudgetsHandler + */ +class StageGetBudgetsHandler +{ + + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * + * @throws FireflyException + */ + public function run(): void + { + Log::debug('Now in StageGetBudgetsHandler::run()'); + // grab access token from job: + $configuration = $this->repository->getConfiguration($this->importJob); + $token = $configuration['access_token']; + $request = new GetBudgetsRequest; + $request->setAccessToken($token); + $request->call(); + + // store budgets in users preferences. + $configuration['budgets'] = $request->budgets; + $this->repository->setConfiguration($this->importJob, $configuration); + Log::debug(sprintf('Found %d budgets', \count($request->budgets))); + if (\count($request->budgets) === 0) { + throw new FireflyException('It seems this user has zero budgets or an error prevented Firefly III from reading them.'); + } + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Support/Import/Routine/Ynab/StageGetTransactionsHandler.php b/app/Support/Import/Routine/Ynab/StageGetTransactionsHandler.php new file mode 100644 index 0000000000..9c87cf78d5 --- /dev/null +++ b/app/Support/Import/Routine/Ynab/StageGetTransactionsHandler.php @@ -0,0 +1,55 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Ynab; + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; + +/** + * Class StageGetTransactionsHandler + */ +class StageGetTransactionsHandler +{ + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * + */ + public function run(): void + { + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Support/Import/Routine/Ynab/StageMatchAccountsHandler.php b/app/Support/Import/Routine/Ynab/StageMatchAccountsHandler.php new file mode 100644 index 0000000000..5944f8514c --- /dev/null +++ b/app/Support/Import/Routine/Ynab/StageMatchAccountsHandler.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Ynab; + +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; + +/** + * Class StageMatchAccountsHandler + */ +class StageMatchAccountsHandler +{ + + + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * + */ + public function run(): void + { + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($importJob->user); + } +} \ No newline at end of file diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index a27ab81001..a7f3690562 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -172,7 +172,7 @@ class Preferences $lastActivity = implode(',', $lastActivity); } $hash = md5($lastActivity); - Log::debug(sprintf('Value of last activity is %s, hash is %s', $lastActivity, $hash)); + //Log::debug(sprintf('Value of last activity is %s, hash is %s', $lastActivity, $hash)); return $hash; } diff --git a/config/import.php b/config/import.php index 17854afa90..5f6a724700 100644 --- a/config/import.php +++ b/config/import.php @@ -151,8 +151,9 @@ return [ 'spectre' => [ 'server' => 'www.saltedge.com', ], - 'ynab' => [ - 'client_id' => '666db19f6c5a2299bf44999a6ea802e96a5f488c3a5c8a5cbb417b59dcf18b72', + 'ynab' => [ + 'live' => 'api.youneedabudget.com', + 'version' => 'v1', ], 'plaid' => [], 'quovo' => [], diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index a0160663e5..036dcfdbfa 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -24,252 +24,259 @@ 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', - 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', - '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.', - 'disabled_for_demo_user' => 'disabled in demo', + '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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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.', + 'disabled_for_demo_user' => 'disabled in demo', // 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' => '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.', // 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_ynab' => 'Import from You Need A Budget', + '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_ynab' => 'Import from You Need A Budget', // 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' => 'Global import configuration', + '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_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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Import configuration', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', - 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', - 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', - 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Upload files', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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_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', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (comma separated values)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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_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', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + // job configuration for YNAB: + 'job_config_select_budgets' => 'Select your budget', + 'job_config_spectre_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + // 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_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', - 'spectre_extra_key_current_date' => 'Current date', - 'spectre_extra_key_cards' => 'Cards', - 'spectre_extra_key_units' => 'Units', - 'spectre_extra_key_unit_price' => 'Unit price', - 'spectre_extra_key_transactions_count' => 'Transaction count', + '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_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', // specifics: - 'specific_ing_name' => 'ING NL', - 'specific_ing_descr' => 'Create better descriptions in ING exports', - 'specific_sns_name' => 'SNS / Volksbank NL', - 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', - 'specific_abn_name' => 'ABN AMRO NL', - 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', - 'specific_rabo_name' => 'Rabobank NL', - 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', - 'specific_pres_name' => 'President\'s Choice Financial CA', - 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', // 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_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_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_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 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', - 'map_do_not_map' => '(do not map)', - 'job_config_map_submit' => 'Start the import', + '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', + 'map_do_not_map' => '(do not map)', + 'job_config_map_submit' => 'Start the import', // import status page: - 'import_with_key' => 'Import with key \':key\'', - 'status_wait_title' => 'Please hold...', - 'status_wait_text' => 'This box will disappear in a moment.', - 'status_running_title' => 'The import is running', - '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_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.', - '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.', + 'import_with_key' => 'Import with key \':key\'', + 'status_wait_title' => 'Please hold...', + 'status_wait_text' => 'This box will disappear in a moment.', + 'status_running_title' => 'The import is running', + '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_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.', + '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.', // general errors and warnings: - 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', // column roles for CSV import: - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', - 'column_account-name' => 'Asset account (name)', - 'column_account-bic' => 'Asset account (BIC)', - 'column_amount' => 'Amount', - 'column_amount_foreign' => 'Amount (in foreign currency)', - 'column_amount_debit' => 'Amount (debit column)', - 'column_amount_credit' => 'Amount (credit column)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching FF3)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching FF3)', - 'column_category-name' => 'Category name', - '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_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', - 'column_external-id' => 'External ID', - '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_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', - 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', + 'column__ignore' => '(ignore this column)', + 'column_account-iban' => 'Asset account (IBAN)', + 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-name' => 'Asset account (name)', + 'column_account-bic' => 'Asset account (BIC)', + 'column_amount' => 'Amount', + 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_debit' => 'Amount (debit column)', + 'column_amount_credit' => 'Amount (credit column)', + 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_bill-name' => 'Bill name', + 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-name' => 'Budget name', + 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-name' => 'Category name', + '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_date-transaction' => 'Date', + 'column_date-due' => 'Transaction due date', + 'column_date-payment' => 'Transaction payment date', + 'column_date-invoice' => 'Transaction invoice date', + 'column_description' => 'Description', + 'column_opposing-iban' => 'Opposing account (IBAN)', + 'column_opposing-bic' => 'Opposing account (BIC)', + 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_external-id' => 'External ID', + '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_tags-comma' => 'Tags (comma separated)', + 'column_tags-space' => 'Tags (space separated)', + 'column_account-number' => 'Asset account (account number)', + 'column_opposing-number' => 'Opposing account (account number)', + 'column_note' => 'Note(s)', + 'column_internal-reference' => 'Internal reference', ]; diff --git a/resources/views/import/ynab/select-budgets.twig b/resources/views/import/ynab/select-budgets.twig new file mode 100644 index 0000000000..1ab9b8efbc --- /dev/null +++ b/resources/views/import/ynab/select-budgets.twig @@ -0,0 +1,39 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
+
+ + +
+
+
+

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

+
+
+
+
+

+ {{ trans('import.job_config_spectre_select_budgets_text', {count: data.budgets|length}) }} +

+ {{ ExpandedForm.select('budget_id', data.budgets) }} +
+
+ +
+
+
+
+
+{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} From 2add64470678c6f1127c874b1703d80986700387 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 30 Jul 2018 20:39:19 +0200 Subject: [PATCH 006/166] First basic import #145 --- .../JobConfiguration/YnabJobConfiguration.php | 11 +- app/Import/Routine/YnabRoutine.php | 14 +- .../Ynab/Request/GetTransactionsRequest.php | 57 ++++ .../Ynab/SelectAccountsHandler.php | 237 +++++++++++++++++ ...etsHandler.php => SelectBudgetHandler.php} | 14 +- .../Import/Routine/Ynab/ImportDataHandler.php | 248 ++++++++++++++++++ resources/lang/en_US/import.php | 230 ++++++++-------- resources/views/import/ynab/accounts.twig | 100 +++++++ 8 files changed, 794 insertions(+), 117 deletions(-) create mode 100644 app/Services/Ynab/Request/GetTransactionsRequest.php create mode 100644 app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php rename app/Support/Import/JobConfiguration/Ynab/{SelectBudgetsHandler.php => SelectBudgetHandler.php} (89%) create mode 100644 app/Support/Import/Routine/Ynab/ImportDataHandler.php create mode 100644 resources/views/import/ynab/accounts.twig diff --git a/app/Import/JobConfiguration/YnabJobConfiguration.php b/app/Import/JobConfiguration/YnabJobConfiguration.php index e557560244..b74ef30fbb 100644 --- a/app/Import/JobConfiguration/YnabJobConfiguration.php +++ b/app/Import/JobConfiguration/YnabJobConfiguration.php @@ -27,7 +27,8 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\JobConfiguration\Ynab\NewYnabJobHandler; -use FireflyIII\Support\Import\JobConfiguration\Ynab\SelectBudgetsHandler; +use FireflyIII\Support\Import\JobConfiguration\Ynab\SelectAccountsHandler; +use FireflyIII\Support\Import\JobConfiguration\Ynab\SelectBudgetHandler; use FireflyIII\Support\Import\JobConfiguration\Ynab\YnabJobConfigurationInterface; use Illuminate\Support\MessageBag; use Log; @@ -121,8 +122,12 @@ class YnabJobConfiguration implements JobConfigurationInterface $handler->setImportJob($this->importJob); break; case 'select_budgets': - /** @var SelectBudgetsHandler $handler */ - $handler = app(SelectBudgetsHandler::class); + /** @var SelectBudgetHandler $handler */ + $handler = app(SelectBudgetHandler::class); + $handler->setImportJob($this->importJob); + break; + case 'select_accounts': + $handler = app(SelectAccountsHandler::class); $handler->setImportJob($this->importJob); break; default: diff --git a/app/Import/Routine/YnabRoutine.php b/app/Import/Routine/YnabRoutine.php index fda1ed5611..38c0d791c4 100644 --- a/app/Import/Routine/YnabRoutine.php +++ b/app/Import/Routine/YnabRoutine.php @@ -27,6 +27,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Import\Routine\Ynab\GetAccountsHandler; +use FireflyIII\Support\Import\Routine\Ynab\ImportDataHandler; use FireflyIII\Support\Import\Routine\Ynab\StageGetAccessHandler; use FireflyIII\Support\Import\Routine\Ynab\StageGetBudgetsHandler; use Log; @@ -104,7 +105,18 @@ class YnabRoutine implements RoutineInterface $this->repository->setStage($this->importJob, 'select_accounts'); $this->repository->setStatus($this->importJob, 'need_job_config'); - + return; + } + if('go-for-import' === $this->importJob->stage) { + $this->repository->setStatus($this->importJob, 'running'); + $this->repository->setStage($this->importJob, 'do_import'); + /** @var ImportDataHandler $handler */ + $handler = app(ImportDataHandler::class); + $handler->setImportJob($this->importJob); + $handler->run(); + $this->repository->setStatus($this->importJob, 'provider_finished'); + $this->repository->setStage($this->importJob, 'final'); + return; } // if ('match_accounts' === $this->importJob->stage) { diff --git a/app/Services/Ynab/Request/GetTransactionsRequest.php b/app/Services/Ynab/Request/GetTransactionsRequest.php new file mode 100644 index 0000000000..72d75929ef --- /dev/null +++ b/app/Services/Ynab/Request/GetTransactionsRequest.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Ynab\Request; + +use Log; + +/** + * Class GetTransactionsRequest + */ +class GetTransactionsRequest extends YnabRequest +{ + /** @var string */ + public $accountId; + /** @var array */ + public $accounts; + /** @var string */ + public $budgetId; + /** @var array */ + public $transactions; + + /** + * + */ + public function call(): void + { + Log::debug('Now in GetTransactionsRequest::call()'); + $uri = $this->api . sprintf('/budgets/%s/accounts/%s/transactions', $this->budgetId, $this->accountId); + + Log::debug(sprintf('URI is %s', $uri)); + + $result = $this->authenticatedGetRequest($uri, []); + + // expect data in [data][transactions] + $this->transactions = $result['data']['transactions'] ?? []; + } +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php new file mode 100644 index 0000000000..411eb78301 --- /dev/null +++ b/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php @@ -0,0 +1,237 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\JobConfiguration\Ynab; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\ImportJob; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\MessageBag; +use Log; + +/** + * Class SelectAccountsHandler + */ +class SelectAccountsHandler implements YnabJobConfigurationInterface +{ + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + /** @var ImportJob */ + private $importJob; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Return true when this stage is complete. + * + * @return bool + */ + public function configurationComplete(): bool + { + Log::debug('Now in SelectAccountsHandler::configurationComplete()'); + $config = $this->importJob->configuration; + $mapping = $config['mapping'] ?? []; + if (\count($mapping) > 0) { + // mapping is complete. + Log::debug('Looks like user has mapped YNAB accounts to Firefly III accounts', $mapping); + $this->repository->setStage($this->importJob, 'go-for-import'); + + return true; + } + + return false; + } + + /** + * Store the job configuration. + * + * @param array $data + * + * @return MessageBag + */ + public function configureJob(array $data): MessageBag + { + Log::debug('Now in SelectAccountsHandler::configureJob()', $data); + $config = $this->importJob->configuration; + $mapping = $data['account_mapping'] ?? []; + $final = []; + $applyRules = 1 === (int)($data['apply_rules'] ?? 0); + foreach ($mapping as $ynabId => $localId) { + // validate each + $ynabId = $this->validYnabAccount($ynabId); + $accountId = $this->validLocalAccount((int)$localId); + $final[$ynabId] = $accountId; + + } + Log::debug('Final mapping is:', $final); + $messages = new MessageBag; + $config['mapping'] = $final; + $config['apply-rules'] = $applyRules; + $this->repository->setConfiguration($this->importJob, $config); + if ($final === ['' => 0] || 0 === \count($final)) { + $messages->add('count', (string)trans('import.ynab_no_mapping')); + } + + return $messages; + } + + /** + * Get data for config view. + * + * @return array + * @throws FireflyException + */ + public function getNextData(): array + { + + Log::debug('Now in ChooseAccountsHandler::getnextData()'); + $config = $this->importJob->configuration; + $ynabAccounts = $config['accounts'] ?? []; + $budget = $this->getSelectedBudget(); + if (0 === \count($ynabAccounts)) { + throw new FireflyException('It seems you have no accounts with this budget. The import cannot continue.'); // @codeCoverageIgnore + } + // list the users accounts: + $ffAccounts = $this->accountRepository->getAccountsByType([AccountType::ASSET]); + + $array = []; + /** @var Account $account */ + foreach ($ffAccounts as $account) { + $accountId = $account->id; + $currencyId = (int)$this->accountRepository->getMetaValue($account, 'currency_id'); + $currency = $this->getCurrency($currencyId); + $array[$accountId] = [ + 'name' => $account->name, + 'iban' => $account->iban, + 'code' => $currency->code, + ]; + } + + return [ + 'budget' => $budget, + 'ynab_accounts' => $ynabAccounts, + 'ff_accounts' => $array, + ]; + } + + /** + * Get the view for this stage. + * + * @return string + */ + public function getNextView(): string + { + return 'import.ynab.accounts'; + } + + /** + * @codeCoverageIgnore + * Set the import job. + * + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->repository->setUser($importJob->user); + $this->currencyRepository->setUser($importJob->user); + $this->accountRepository->setUser($importJob->user); + } + + /** + * @param int $currencyId + * + * @return TransactionCurrency + */ + private function getCurrency(int $currencyId): TransactionCurrency + { + $currency = $this->currencyRepository->findNull($currencyId); + if (null === $currency) { + return app('amount')->getDefaultCurrencyByUser($this->importJob->user); + } + + return $currency; + + } + + /** + * @return array + */ + private function getSelectedBudget(): array + { + $config = $this->repository->getConfiguration($this->importJob); + $budgets = $config['budgets'] ?? []; + $selected = $config['selected_budget'] ?? ''; + foreach ($budgets as $budget) { + if ($budget['id'] === $selected) { + return $budget; + } + } + + return $budgets[0] ?? []; + } + + /** + * @param int $accountId + * + * @return int + */ + private function validLocalAccount(int $accountId): int + { + $account = $this->accountRepository->findNull($accountId); + if (null === $account) { + return 0; + } + + return $accountId; + } + + /** + * @param int $accountId + * + * @return string + */ + private function validYnabAccount(string $accountId): string + { + $config = $this->importJob->configuration; + $accounts = $config['accounts'] ?? []; + foreach ($accounts as $account) { + if ($account['id'] === $accountId) { + return $accountId; + } + } + + return ''; + } +} \ No newline at end of file diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php similarity index 89% rename from app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php rename to app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php index e0e43a2916..244eba2b5b 100644 --- a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetsHandler.php +++ b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php @@ -1,6 +1,6 @@ repository->getConfiguration($this->importJob); $selectedBudget = $configuration['selected_budget'] ?? ''; if ($selectedBudget !== '') { @@ -67,7 +67,7 @@ class SelectBudgetsHandler implements YnabJobConfigurationInterface */ public function configureJob(array $data): MessageBag { - Log::debug('Now in SelectBudgetsHandler::configureJob'); + Log::debug('Now in SelectBudgetHandler::configureJob'); $configuration = $this->repository->getConfiguration($this->importJob); $configuration['selected_budget'] = $data['budget_id']; @@ -87,7 +87,7 @@ class SelectBudgetsHandler implements YnabJobConfigurationInterface */ public function getNextData(): array { - Log::debug('Now in SelectBudgetsHandler::getNextData'); + Log::debug('Now in SelectBudgetHandler::getNextData'); $configuration = $this->repository->getConfiguration($this->importJob); $budgets = $configuration['budgets'] ?? []; $return = []; @@ -107,7 +107,7 @@ class SelectBudgetsHandler implements YnabJobConfigurationInterface */ public function getNextView(): string { - Log::debug('Now in SelectBudgetsHandler::getNextView'); + Log::debug('Now in SelectBudgetHandler::getNextView'); return 'import.ynab.select-budgets'; } diff --git a/app/Support/Import/Routine/Ynab/ImportDataHandler.php b/app/Support/Import/Routine/Ynab/ImportDataHandler.php new file mode 100644 index 0000000000..9b90d510a3 --- /dev/null +++ b/app/Support/Import/Routine/Ynab/ImportDataHandler.php @@ -0,0 +1,248 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Import\Routine\Ynab; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Services\Ynab\Request\GetTransactionsRequest; +use FireflyIII\Support\Import\Routine\File\OpposingAccountMapper; +use Log; + +/** + * Class ImportDataHandler + */ +class ImportDataHandler +{ + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var ImportJob */ + private $importJob; + /** @var OpposingAccountMapper */ + private $mapper; + /** @var ImportJobRepositoryInterface */ + private $repository; + + /** + * Get list of accounts for the selected budget. + * + * @throws FireflyException + */ + public function run(): void + { + $config = $this->repository->getConfiguration($this->importJob); + $token = $config['access_token']; + // make request for each mapping: + $mapping = $config['mapping'] ?? []; + $total = [[]]; + + /** + * @var string $ynabId + * @var int $localId + */ + foreach ($mapping as $ynabId => $localId) { + $localAccount = $this->getLocalAccount((int)$localId); + $transactions = $this->getTransactions($token, $ynabId); + $converted = $this->convertToArray($transactions, $localAccount); + $total[] = $converted; + } + + $totalSet = array_merge(...$total); + Log::debug(sprintf('Found %d transactions in total.', \count($totalSet))); + $this->repository->setTransactions($this->importJob, $totalSet); + } + + /** + * @param ImportJob $importJob + */ + public function setImportJob(ImportJob $importJob): void + { + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->mapper = app(OpposingAccountMapper::class); + $this->accountRepository->setUser($importJob->user); + $this->repository->setUser($importJob->user); + $this->mapper->setUser($importJob->user); + } + + /** + * @param array $transactions + * @param Account $localAccount + * + * @return array + * @throws FireflyException + */ + private function convertToArray(array $transactions, Account $localAccount): array + { + $array = []; + $total = \count($transactions); + $budget = $this->getSelectedBudget(); + Log::debug(sprintf('Now in StageImportDataHandler::convertToArray() with count %d', \count($transactions))); + /** @var array $transaction */ + foreach ($transactions as $index => $transaction) { + Log::debug(sprintf('Now creating array for transaction %d of %d', $index + 1, $total)); + $amount = (string)($transaction['amount'] ?? 0); + if ('0' === $amount) { + continue; + } + $source = $localAccount; + $type = 'withdrawal'; + $tags = [ + $transaction['cleared'] ?? '', + $transaction['approved'] ? 'approved' : 'not-approved', + $transaction['flag_color'] ?? '', + ]; + $destinationData = [ + 'name' => $transaction['payee_name'], + 'iban' => null, + 'number' => $transaction['payee_id'], + 'bic' => null, + ]; + + $destination = $this->mapper->map(null, $amount, $destinationData); + + if (1 === bccomp($amount, '0')) { + [$source, $destination] = [$destination, $source]; + $type = 'deposit'; + } + + $entry = [ + 'type' => $type, + 'date' => $transaction['date'] ?? date('Y-m-d'), + 'tags' => $tags, // TODO + 'user' => $this->importJob->user_id, + 'notes' => null, // TODO + + // all custom fields: + 'external_id' => $transaction['id'] ?? '', + + // journal data: + 'description' => $transaction['memo'] ?? '(empty)', + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + + // transaction data: + 'transactions' => [ + [ + 'currency_id' => null, + 'currency_code' => $budget['currency_code'], + 'description' => null, + 'amount' => bcdiv((string)$transaction['amount'], '1000'), + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => $transaction['category_name'], + 'source_id' => $source->id, + 'source_name' => null, + 'destination_id' => $destination->id, + 'destination_name' => null, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'reconciled' => false, + 'identifier' => 0, + ], + ], + ]; + $array[] = $entry; + } + + return $array; + } + + /** + * @param int $accountId + * + * @return Account + * @throws FireflyException + */ + private function getLocalAccount(int $accountId): Account + { + $account = $this->accountRepository->findNull($accountId); + if (null === $account) { + throw new FireflyException(sprintf('Cannot find Firefly III asset account with ID #%d. Job must stop now.', $accountId)); // @codeCoverageIgnore + } + if ($account->accountType->type !== AccountType::ASSET) { + throw new FireflyException(sprintf('Account with ID #%d is not an asset account. Job must stop now.', $accountId)); // @codeCoverageIgnore + } + + return $account; + } + + /** + * @return array + * @throws FireflyException + */ + private function getSelectedBudget(): array + { + $config = $this->repository->getConfiguration($this->importJob); + $budgets = $config['budgets'] ?? []; + $selected = $config['selected_budget'] ?? ''; + + if ('' === $selected) { + $firstBudget = $config['budgets'][0] ?? false; + if (false === $firstBudget) { + throw new FireflyException('The configuration contains no budget. Erroring out.'); + } + $selected = $firstBudget['id']; + } + + foreach ($budgets as $budget) { + if ($budget['id'] === $selected) { + return $budget; + } + } + + return $budgets[0] ?? []; + } + + /** + * @param string $token + * @param string $budget + * @param string $account + * + * @return array + * @throws FireflyException + */ + private function getTransactions(string $token, string $account): array + { + $budget = $this->getSelectedBudget(); + $request = new GetTransactionsRequest; + $request->budgetId = $budget['id']; + $request->accountId = $account; + + // todo grab latest date for $ynabId + $request->setAccessToken($token); + $request->call(); + + return $request->transactions; + } +} \ No newline at end of file diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 036dcfdbfa..1c0d1004e4 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -24,132 +24,150 @@ 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', - 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', - '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.', - 'disabled_for_demo_user' => 'disabled in demo', + '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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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.', + 'disabled_for_demo_user' => 'disabled in demo', // 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' => '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.', // 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_ynab' => 'Import from You Need A Budget', + '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_ynab' => 'Import from You Need A Budget', // 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' => 'Global import configuration', + '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_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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', - 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', // provider config box (index) - 'can_config_title' => 'Import configuration', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', - 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', - 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', - 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', - 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Upload files', - 'import_file_type_csv' => 'CSV (comma separated values)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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_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', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (comma separated values)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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_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', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: 'job_config_select_budgets' => 'Select your budget', 'job_config_spectre_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/views/import/ynab/accounts.twig b/resources/views/import/ynab/accounts.twig new file mode 100644 index 0000000000..d4e3b8a718 --- /dev/null +++ b/resources/views/import/ynab/accounts.twig @@ -0,0 +1,100 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.render }} +{% endblock %} +{% block content %} +
+
+ + +
+
+
+

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

+
+
+
+
+

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

+ {{ ExpandedForm.checkbox('apply_rules', 1, true) }} +
+
+ +
+
+
+ +
+
+
+

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

+
+
+
+
+

+ {{ trans('import.job_config_ynab_accounts_text', {count: data.accounts|length}) }} +

+
+
+
+
+ + + + + + + + + {% for account in data.ynab_accounts %} + + + + + + {% endfor %} + +
{{ trans('list.account_on_ynab') }}{{ trans('list.account') }}
+ {{ account.name }} ({{ trans('import.ynab_account_type_'~account.type) }}) + {% if account.closed %} +
{{ trans('import.ynab_account_closed') }} + {% endif %} + {% if account.deleted %} +
{{ trans('import.ynab_account_deleted') }} + {% endif %} +
+ +
+ + +
+
+ +
+
+ +
+{% endblock %} +{% block scripts %} +{% endblock %} +{% block styles %} +{% endblock %} From 41da7d9f9a1e71dc342e386b623785f697a83bae Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 05:27:33 +0200 Subject: [PATCH 007/166] Fix #1580 --- resources/lang/en_US/firefly.php | 1 + resources/views/accounts/reconcile/overview.twig | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index ef30dadf9d..793735db9f 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Cash accounts', 'Cash account' => 'Cash account', 'reconcile_account' => 'Reconcile account ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Delete reconciliation', 'update_reconciliation' => 'Update reconciliation', 'amount_cannot_be_zero' => 'The amount cannot be zero', diff --git a/resources/views/accounts/reconcile/overview.twig b/resources/views/accounts/reconcile/overview.twig index 78502d20af..a7c9287d36 100644 --- a/resources/views/accounts/reconcile/overview.twig +++ b/resources/views/accounts/reconcile/overview.twig @@ -3,7 +3,8 @@ @@ -20,7 +21,7 @@ - + @@ -32,7 +33,7 @@ - + From 7843c55409b1aec81fb4e2f156471e494b3cfdbb Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 05:30:27 +0200 Subject: [PATCH 008/166] Expand helptext for #1581 --- 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 793735db9f..3e1165760e 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Home screen accounts', 'pref_home_screen_accounts_help' => 'Which accounts should be displayed on the home page?', 'pref_view_range' => 'View range', - 'pref_view_range_help' => 'Some charts are automatically grouped in periods. What period would you prefer?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'One day', 'pref_1W' => 'One week', 'pref_1M' => 'One month', From a004f273613d2cc051bc10a0a19ba5645f611a00 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 05:35:25 +0200 Subject: [PATCH 009/166] Updated strings --- resources/lang/de_DE/firefly.php | 3 +- resources/lang/de_DE/form.php | 1 + resources/lang/de_DE/import.php | 235 +++++++++++++++------------ resources/lang/en_US/import.php | 269 ++++++++++++++++--------------- resources/lang/es_ES/firefly.php | 3 +- resources/lang/es_ES/form.php | 1 + resources/lang/es_ES/import.php | 233 ++++++++++++++------------ resources/lang/fr_FR/firefly.php | 3 +- resources/lang/fr_FR/form.php | 1 + resources/lang/fr_FR/import.php | 233 ++++++++++++++------------ resources/lang/id_ID/firefly.php | 3 +- resources/lang/id_ID/form.php | 1 + resources/lang/id_ID/import.php | 233 ++++++++++++++------------ resources/lang/it_IT/firefly.php | 7 +- resources/lang/it_IT/form.php | 1 + resources/lang/it_IT/import.php | 233 ++++++++++++++------------ resources/lang/nl_NL/firefly.php | 3 +- resources/lang/nl_NL/form.php | 1 + resources/lang/nl_NL/import.php | 233 ++++++++++++++------------ resources/lang/pl_PL/firefly.php | 3 +- resources/lang/pl_PL/form.php | 1 + resources/lang/pl_PL/import.php | 233 ++++++++++++++------------ resources/lang/pt_BR/firefly.php | 3 +- resources/lang/pt_BR/form.php | 1 + resources/lang/pt_BR/import.php | 233 ++++++++++++++------------ resources/lang/ru_RU/firefly.php | 3 +- resources/lang/ru_RU/form.php | 1 + resources/lang/ru_RU/import.php | 233 ++++++++++++++------------ resources/lang/tr_TR/firefly.php | 3 +- resources/lang/tr_TR/form.php | 1 + resources/lang/tr_TR/import.php | 233 ++++++++++++++------------ 31 files changed, 1509 insertions(+), 1136 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 8ef2322118..b947fd5730 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Konten auf dem Startbildschirm', 'pref_home_screen_accounts_help' => 'Welche Konten sollen auf dem Startbildschirm angezeigt werden?', 'pref_view_range' => 'Sichtbare Zeiträume', - 'pref_view_range_help' => 'Einige Diagramme werden automatisch nach Zeiträumen gruppiert. Welchen Zeitraum wollen Sie festlegen?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Ein Tag', 'pref_1W' => 'Eine Woche', 'pref_1M' => 'Ein Monat', @@ -702,6 +702,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'cash_accounts' => 'Bargeldkonten', 'Cash account' => 'Bargeldkonto', 'reconcile_account' => 'Konto ":account" ausgleichen', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Kontenabgleich löschen', 'update_reconciliation' => 'Kontenabgleich aktualisieren', 'amount_cannot_be_zero' => 'Der Betrag darf nicht Null sein', diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index cc1d5e1783..dbe1081bd2 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Wiederholungen', 'calendar' => 'Kalender', 'weekend' => 'Wochenende', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index 13bf4e3781..c656f54b79 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Daten in Firefly III importieren', - 'prerequisites_breadcrumb_fake' => 'Voraussetzungen für den Scheinimportanbieter', - 'prerequisites_breadcrumb_spectre' => 'Voraussetzungen für Spectre', - 'prerequisites_breadcrumb_bunq' => 'Voraussetzungen für bunq', - 'job_configuration_breadcrumb' => 'Konfiguration für „:key”', - 'job_status_breadcrumb' => 'Importstatus für „:key”', - 'cannot_create_for_provider' => 'Firefly III konnte keine Aufgabe für den Anbieter „:provider” erstellen.', + 'index_breadcrumb' => 'Daten in Firefly III importieren', + 'prerequisites_breadcrumb_fake' => 'Voraussetzungen für den Scheinimportanbieter', + 'prerequisites_breadcrumb_spectre' => 'Voraussetzungen für Spectre', + 'prerequisites_breadcrumb_bunq' => 'Voraussetzungen für bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'job_configuration_breadcrumb' => 'Konfiguration für „:key”', + 'job_status_breadcrumb' => 'Importstatus für „:key”', + 'cannot_create_for_provider' => 'Firefly III konnte keine Aufgabe für den Anbieter „:provider” erstellen.', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - 'general_index_title' => 'Datei importieren', - 'general_index_intro' => 'Willkommen beim Importassistenten von Firefly III. Es gibt einige Möglichkeiten, Daten in Firefly III zu importieren, die hier als Schaltflächen angezeigt werden.', + 'general_index_title' => 'Datei importieren', + 'general_index_intro' => 'Willkommen beim Importassistenten von Firefly III. Es gibt einige Möglichkeiten, Daten in Firefly III zu importieren, die hier als Schaltflächen angezeigt werden.', // import provider strings (index): - 'button_fake' => 'Importfunktion testen', - 'button_file' => 'Datei importieren', - 'button_bunq' => 'Von „bunq” importieren', - 'button_spectre' => 'Importieren mit Spectre', - 'button_plaid' => 'Import mit Plaid', - 'button_yodlee' => 'Importieren mit Yodlee', - 'button_quovo' => 'Import mit Quovo', + 'button_fake' => 'Importfunktion testen', + 'button_file' => 'Datei importieren', + 'button_bunq' => 'Von „bunq” importieren', + 'button_spectre' => 'Importieren mit Spectre', + 'button_plaid' => 'Import mit Plaid', + 'button_yodlee' => 'Importieren mit Yodlee', + 'button_quovo' => 'Import mit Quovo', + 'button_ynab' => 'Import from You Need A Budget', // global config box (index) - 'global_config_title' => 'Allgemeine Importkonfiguration', - 'global_config_text' => 'In Zukunft wird dieses Feld Einstellungen enthalten, die für ALLE oben genannten Importanbieter gelten.', + 'global_config_title' => 'Allgemeine Importkonfiguration', + 'global_config_text' => 'In Zukunft wird dieses Feld Einstellungen enthalten, die für ALLE oben genannten Importanbieter gelten.', // prerequisites box (index) - 'need_prereq_title' => 'Importvoraussetzungen', - 'need_prereq_intro' => 'Einige Importmethoden benötigen Ihre Aufmerksamkeit, bevor sie verwendet werden können. Beispielsweise benötigen sie spezielle API-Schlüssel oder Anwendungsgeheimnisse. Sie können sie hier konfigurieren. Das Symbol zeigt an, ob diese Voraussetzungen erfüllt sind.', - 'do_prereq_fake' => 'Voraussetzungen für den Scheinanbieter', - 'do_prereq_file' => 'Voraussetzungen für den Dateiimport', - 'do_prereq_bunq' => 'Voraussetzungen für den Import aus Bunq', - 'do_prereq_spectre' => 'Voraussetzungen für den Import mit Spectre', - 'do_prereq_plaid' => 'Voraussetzungen für den Import mit Plaid', - 'do_prereq_yodlee' => 'Voraussetzungen für den Import mit Yodlee', - 'do_prereq_quovo' => 'Voraussetzungen für den Import mit Quovo', + 'need_prereq_title' => 'Importvoraussetzungen', + 'need_prereq_intro' => 'Einige Importmethoden benötigen Ihre Aufmerksamkeit, bevor sie verwendet werden können. Beispielsweise benötigen sie spezielle API-Schlüssel oder Anwendungsgeheimnisse. Sie können sie hier konfigurieren. Das Symbol zeigt an, ob diese Voraussetzungen erfüllt sind.', + 'do_prereq_fake' => 'Voraussetzungen für den Scheinanbieter', + 'do_prereq_file' => 'Voraussetzungen für den Dateiimport', + 'do_prereq_bunq' => 'Voraussetzungen für den Import aus Bunq', + 'do_prereq_spectre' => 'Voraussetzungen für den Import mit Spectre', + 'do_prereq_plaid' => 'Voraussetzungen für den Import mit Plaid', + 'do_prereq_yodlee' => 'Voraussetzungen für den Import mit Yodlee', + 'do_prereq_quovo' => 'Voraussetzungen für den Import mit Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Einstellungen importieren', - 'can_config_intro' => 'Einige Importmethoden können nach Ihren Wünschen konfiguriert werden. Sie verfügen über zusätzliche Einstellungen, die Sie anpassen können.', - 'do_config_fake' => 'Konfiguration für den Scheinanbieter', - 'do_config_file' => 'Konfiguration für Dateiimporte', - 'do_config_bunq' => 'Konfiguration für den Import aus bunq', - 'do_config_spectre' => 'Konfiguration für den Import aus Spectre', - 'do_config_plaid' => 'Konfiguration für den Import aus Plaid', - 'do_config_yodlee' => 'Konfiguration für den Import aus Yodlee', - 'do_config_quovo' => 'Konfiguration für den Import aus Quovo', + 'can_config_title' => 'Einstellungen importieren', + 'can_config_intro' => 'Einige Importmethoden können nach Ihren Wünschen konfiguriert werden. Sie verfügen über zusätzliche Einstellungen, die Sie anpassen können.', + 'do_config_fake' => 'Konfiguration für den Scheinanbieter', + 'do_config_file' => 'Konfiguration für Dateiimporte', + 'do_config_bunq' => 'Konfiguration für den Import aus bunq', + 'do_config_spectre' => 'Konfiguration für den Import aus Spectre', + 'do_config_plaid' => 'Konfiguration für den Import aus Plaid', + 'do_config_yodlee' => 'Konfiguration für den Import aus Yodlee', + 'do_config_quovo' => 'Konfiguration für den Import aus Quovo', // prerequisites: - 'prereq_fake_title' => 'Voraussetzungen für einen Import vom Scheinimportanbieter', - 'prereq_fake_text' => 'Dieser Scheinanbieter benötigt einen eigenen API-Schlüssel. Dieser muss 32 Zeichen lang sein. Sie können diese hier verwenden: 123456789012345678901234567890', - 'prereq_spectre_title' => 'Voraussetzungen für einen Import durch Verwendung der Spectre-API', - 'prereq_spectre_text' => 'Um Daten über die Spectre-API (v4) zu importieren, müssen Sie Firefly III zwei geheime Werte zur Verfügung stellen. Diese können auf der Geheimnisse-Seite gefunden werden.', - 'prereq_spectre_pub' => 'Ebenso muss die Spectre-API den öffentlichen Schlüssel kennen, der unten angezeigt wird. Ohne diesen wird sie Sie nicht erkennen. Bitte geben Sie diesen öffentlichen Schlüssel auf Ihrer Geheimnisse-Seite ein.', - 'prereq_bunq_title' => 'Voraussetzungen für einen Import von bunq', - 'prereq_bunq_text' => 'Um aus „bunq” importieren zu können, benötigen Sie einen API-Schlüssel. Sie können diesen über die App bekommen. Bitte beachten Sie, dass sich die Importfunktion von „bunq” noch im BETA-Stadium befindet. Es wurde nur gegen die Sandbox-API getestet.', - 'prereq_bunq_ip' => '„bunq” benötigt Ihre öffentlich zugängliche IP-Adresse. Firefly III versuchte, diese mithilfe des ipify-Diensts auszufüllen. Stellen Sie sicher, dass diese IP-Adresse korrekt ist, da sonst der Import fehlschlägt.', + 'prereq_fake_title' => 'Voraussetzungen für einen Import vom Scheinimportanbieter', + 'prereq_fake_text' => 'Dieser Scheinanbieter benötigt einen eigenen API-Schlüssel. Dieser muss 32 Zeichen lang sein. Sie können diese hier verwenden: 123456789012345678901234567890', + 'prereq_spectre_title' => 'Voraussetzungen für einen Import durch Verwendung der Spectre-API', + 'prereq_spectre_text' => 'Um Daten über die Spectre-API (v4) zu importieren, müssen Sie Firefly III zwei geheime Werte zur Verfügung stellen. Diese können auf der Geheimnisse-Seite gefunden werden.', + 'prereq_spectre_pub' => 'Ebenso muss die Spectre-API den öffentlichen Schlüssel kennen, der unten angezeigt wird. Ohne diesen wird sie Sie nicht erkennen. Bitte geben Sie diesen öffentlichen Schlüssel auf Ihrer Geheimnisse-Seite ein.', + 'prereq_bunq_title' => 'Voraussetzungen für einen Import von bunq', + 'prereq_bunq_text' => 'Um aus „bunq” importieren zu können, benötigen Sie einen API-Schlüssel. Sie können diesen über die App bekommen. Bitte beachten Sie, dass sich die Importfunktion von „bunq” noch im BETA-Stadium befindet. Es wurde nur gegen die Sandbox-API getestet.', + 'prereq_bunq_ip' => '„bunq” benötigt Ihre öffentlich zugängliche IP-Adresse. Firefly III versuchte, diese mithilfe des ipify-Diensts auszufüllen. Stellen Sie sicher, dass diese IP-Adresse korrekt ist, da sonst der Import fehlschlägt.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Schein-API-Schlüssel erfolgreich gespeichert!', - 'prerequisites_saved_for_spectre' => 'App-ID und Geheimnis gespeichert!', - 'prerequisites_saved_for_bunq' => 'API-Schlüssel und IP gespeichert!', + 'prerequisites_saved_for_fake' => 'Schein-API-Schlüssel erfolgreich gespeichert!', + 'prerequisites_saved_for_spectre' => 'App-ID und Geheimnis gespeichert!', + 'prerequisites_saved_for_bunq' => 'API-Schlüssel und IP gespeichert!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Aufgabenkonfiguration - Regeln übernehmen?', - 'job_config_apply_rules_text' => 'Sobald der Scheinanbieter ausgeführt wird, können Ihre Regeln auf die Buchungen angewendet werden. Dies erhöht die Zeit für den Import.', - 'job_config_input' => 'Ihre Eingaben', + 'job_config_apply_rules_title' => 'Aufgabenkonfiguration - Regeln übernehmen?', + 'job_config_apply_rules_text' => 'Sobald der Scheinanbieter ausgeführt wird, können Ihre Regeln auf die Buchungen angewendet werden. Dies erhöht die Zeit für den Import.', + 'job_config_input' => 'Ihre Eingaben', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Albumname eingeben', - 'job_config_fake_artist_text' => 'Viele Importassistent haben einige Konfigurationsschritte, die Sie ausführen müssen. Im Falle des gefälschten Importanbieters müssen Sie einige seltsame Fragen beantworten. Geben Sie in diesem Fall „David Bowie” ein, um fortzufahren.', - 'job_config_fake_song_title' => 'Titelnamen eingeben', - 'job_config_fake_song_text' => 'Nennen Sie den Song „Goldene Jahre”, um mit dem Scheinimport fortzufahren.', - 'job_config_fake_album_title' => 'Albumname eingeben', - 'job_config_fake_album_text' => 'Einige Importassistenten benötigen nach der Hälfte des Imports zusätzliche Daten. Im Falle des Scheinimportanbieter müssen Sie einige seltsame Fragen beantworten. Geben Sie „Station zu Station” ein, um fortzufahren.', + 'job_config_fake_artist_title' => 'Albumname eingeben', + 'job_config_fake_artist_text' => 'Viele Importassistent haben einige Konfigurationsschritte, die Sie ausführen müssen. Im Falle des gefälschten Importanbieters müssen Sie einige seltsame Fragen beantworten. Geben Sie in diesem Fall „David Bowie” ein, um fortzufahren.', + 'job_config_fake_song_title' => 'Titelnamen eingeben', + 'job_config_fake_song_text' => 'Nennen Sie den Song „Goldene Jahre”, um mit dem Scheinimport fortzufahren.', + 'job_config_fake_album_title' => 'Albumname eingeben', + 'job_config_fake_album_text' => 'Einige Importassistenten benötigen nach der Hälfte des Imports zusätzliche Daten. Im Falle des Scheinimportanbieter müssen Sie einige seltsame Fragen beantworten. Geben Sie „Station zu Station” ein, um fortzufahren.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import einrichten (1/4) • Ihre Datei hochladen', - 'job_config_file_upload_text' => 'Diese Assistent wird Ihnen helfen, Dateien von Ihrer Bank in Firefly III zu importieren. ', - 'job_config_file_upload_help' => 'Wählen Sie Ihre Datei aus. Bitte stellen Sie sicher, dass die Datei UTF-8-kodiert ist.', - 'job_config_file_upload_config_help' => 'Wenn Sie zuvor Daten in Firefly III importiert haben, verfügen Sie möglicherweise über eine Konfigurationsdatei, die Ihnen die Konfigurationswerte vorgibt. Für einige Banken haben andere Benutzer freundlicherweise ihreKonfigurationsdatei zur Verfügung gestellt.', - 'job_config_file_upload_type_help' => 'Typ der hochzuladenden Datei auswählen', - 'job_config_file_upload_submit' => 'Dateien hochladen', - 'import_file_type_csv' => 'CSV (Kommagetrennte Werte)', - 'file_not_utf8' => 'Die hochgeladene Datei ist nicht als UTF-8 oder ASCII kodiert. Firefly III kann mit solchen Dateien nicht umgehen. Bitte verwenden Sie „Notepad++” oder „Sublime”, um Ihre Datei in UTF-8 zu konvertieren.', - 'job_config_uc_title' => 'Import einrichten (2/4) • Allgemeine Datei-Einstellungen', - 'job_config_uc_text' => 'Um Ihre Datei korrekt importieren zu können, überprüfen Sie bitte die folgenden Optionen.', - 'job_config_uc_header_help' => 'Markieren Sie dieses Feld, wenn die erste Zeile Ihrer CSV-Datei die Spaltenüberschriften enthält.', - 'job_config_uc_date_help' => 'Datumsformat in Ihrer Datei. Folgen Sie dem auf dieser Seite angegebenen Format. Der Standardwert analysiert Daten, die wie folgt aussehen: :dateExample.', - 'job_config_uc_delimiter_help' => 'Wählen Sie das Feldtrennzeichen, das in Ihrer Eingabedatei verwendet wird. Wenn nicht sicher, ist das Komma die sicherste Option.', - 'job_config_uc_account_help' => 'Wenn Ihre Datei KEINE Informationen über Ihr(e) Anlagenkont(o/en) enthält, wählen Sie über dieses Auswahlmenü aus, zu welchem Konto die Buchungen in der Datei gehören.', - 'job_config_uc_apply_rules_title' => 'Regeln anwenden', - 'job_config_uc_apply_rules_text' => 'Wendet Ihre Regeln auf jede importierte Buchung an. Beachten Sie, dass dies den Import erheblich verlangsamt.', - 'job_config_uc_specifics_title' => 'Bankspezifische Einstellungen', - 'job_config_uc_specifics_txt' => 'Einige Banken liefern schlecht formatierte Dateien. Firefly III kann diese automatisch korrigieren. Wenn Ihre Bank solche Dateien liefert, diese aber hier nicht aufgeführt sind, öffnen Sie bitte einen Fehlerbericht auf GitHub.', - 'job_config_uc_submit' => 'Fortsetzen', - 'invalid_import_account' => 'Sie haben ein ungültiges Konto zum Importieren ausgewählt.', + 'job_config_file_upload_title' => 'Import einrichten (1/4) • Ihre Datei hochladen', + 'job_config_file_upload_text' => 'Diese Assistent wird Ihnen helfen, Dateien von Ihrer Bank in Firefly III zu importieren. ', + 'job_config_file_upload_help' => 'Wählen Sie Ihre Datei aus. Bitte stellen Sie sicher, dass die Datei UTF-8-kodiert ist.', + 'job_config_file_upload_config_help' => 'Wenn Sie zuvor Daten in Firefly III importiert haben, verfügen Sie möglicherweise über eine Konfigurationsdatei, die Ihnen die Konfigurationswerte vorgibt. Für einige Banken haben andere Benutzer freundlicherweise ihreKonfigurationsdatei zur Verfügung gestellt.', + 'job_config_file_upload_type_help' => 'Typ der hochzuladenden Datei auswählen', + 'job_config_file_upload_submit' => 'Dateien hochladen', + 'import_file_type_csv' => 'CSV (Kommagetrennte Werte)', + 'file_not_utf8' => 'Die hochgeladene Datei ist nicht als UTF-8 oder ASCII kodiert. Firefly III kann mit solchen Dateien nicht umgehen. Bitte verwenden Sie „Notepad++” oder „Sublime”, um Ihre Datei in UTF-8 zu konvertieren.', + 'job_config_uc_title' => 'Import einrichten (2/4) • Allgemeine Datei-Einstellungen', + 'job_config_uc_text' => 'Um Ihre Datei korrekt importieren zu können, überprüfen Sie bitte die folgenden Optionen.', + 'job_config_uc_header_help' => 'Markieren Sie dieses Feld, wenn die erste Zeile Ihrer CSV-Datei die Spaltenüberschriften enthält.', + 'job_config_uc_date_help' => 'Datumsformat in Ihrer Datei. Folgen Sie dem auf dieser Seite angegebenen Format. Der Standardwert analysiert Daten, die wie folgt aussehen: :dateExample.', + 'job_config_uc_delimiter_help' => 'Wählen Sie das Feldtrennzeichen, das in Ihrer Eingabedatei verwendet wird. Wenn nicht sicher, ist das Komma die sicherste Option.', + 'job_config_uc_account_help' => 'Wenn Ihre Datei KEINE Informationen über Ihr(e) Anlagenkont(o/en) enthält, wählen Sie über dieses Auswahlmenü aus, zu welchem Konto die Buchungen in der Datei gehören.', + 'job_config_uc_apply_rules_title' => 'Regeln anwenden', + 'job_config_uc_apply_rules_text' => 'Wendet Ihre Regeln auf jede importierte Buchung an. Beachten Sie, dass dies den Import erheblich verlangsamt.', + 'job_config_uc_specifics_title' => 'Bankspezifische Einstellungen', + 'job_config_uc_specifics_txt' => 'Einige Banken liefern schlecht formatierte Dateien. Firefly III kann diese automatisch korrigieren. Wenn Ihre Bank solche Dateien liefert, diese aber hier nicht aufgeführt sind, öffnen Sie bitte einen Fehlerbericht auf GitHub.', + 'job_config_uc_submit' => 'Fortsetzen', + 'invalid_import_account' => 'Sie haben ein ungültiges Konto zum Importieren ausgewählt.', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Wählen Sie Ihre Zugangsdaten', - 'job_config_spectre_login_text' => 'Firefly III hat :count bestehende Zugangsdaten in Ihrem Spectre-Konto gefunden. Von welchem möchten Sie importieren?', - 'spectre_login_status_active' => 'Aktiv', - 'spectre_login_status_inactive' => 'Inaktiv', - 'spectre_login_status_disabled' => 'Deaktiviert', - 'spectre_login_new_login' => 'Melden Sie sich bei einer anderen Bank oder einer dieser Banken mit anderen Zugangsdaten an.', - 'job_config_spectre_accounts_title' => 'Import-Konten auswählen', - 'job_config_spectre_accounts_text' => 'Sie haben „:name” (:country) gewählt. Sie haben :count Konto(s) bei diesem Anbieter. Bitte wählen Sie das/die Firefly III-Kont(o/en), auf dem/denen die Buchungen von diesen Konten gespeichert werden sollen. Denken Sie daran, dass zum Importieren von Daten sowohl das Firefly III-Konto als auch das „:name”-Konto dieselbe Währung haben müssen.', - 'spectre_no_supported_accounts' => 'Von diesem Konto können Sie nicht importieren, da die Währungen nicht übereinstimmen.', - 'spectre_do_not_import' => '(Nicht importieren)', - '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' => '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_config_spectre_login_title' => 'Wählen Sie Ihre Zugangsdaten', + 'job_config_spectre_login_text' => 'Firefly III hat :count bestehende Zugangsdaten in Ihrem Spectre-Konto gefunden. Von welchem möchten Sie importieren?', + 'spectre_login_status_active' => 'Aktiv', + 'spectre_login_status_inactive' => 'Inaktiv', + 'spectre_login_status_disabled' => 'Deaktiviert', + 'spectre_login_new_login' => 'Melden Sie sich bei einer anderen Bank oder einer dieser Banken mit anderen Zugangsdaten an.', + 'job_config_spectre_accounts_title' => 'Import-Konten auswählen', + 'job_config_spectre_accounts_text' => 'Sie haben „:name” (:country) gewählt. Sie haben :count Konto(s) bei diesem Anbieter. Bitte wählen Sie das/die Firefly III-Kont(o/en), auf dem/denen die Buchungen von diesen Konten gespeichert werden sollen. Denken Sie daran, dass zum Importieren von Daten sowohl das Firefly III-Konto als auch das „:name”-Konto dieselbe Währung haben müssen.', + 'spectre_no_supported_accounts' => 'Von diesem Konto können Sie nicht importieren, da die Währungen nicht übereinstimmen.', + 'spectre_do_not_import' => '(Nicht importieren)', + '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' => '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' => '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.', + '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' => '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT Code', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Bestandskonto (IBAN)', 'column_account-id' => 'Kennung des Bestandkontos (passend zu FF3)', 'column_account-name' => 'Bestandskonto (Name)', - 'column_account-bic' => 'Bestandskonto (BIC)', + 'column_account-bic' => 'Bestandskonto (BIC)', 'column_amount' => 'Betrag', 'column_amount_foreign' => 'Betrag (in Fremdwährung)', 'column_amount_debit' => 'Betrag (Debitoren-Spalte)', @@ -230,7 +265,7 @@ return [ 'column_budget-id' => 'Kostenrahmen-ID (übereinstimmend mit FF3)', 'column_budget-name' => 'Kostenrahmenname', 'column_category-id' => 'Kategorie (ID übereinstimmend mit FF3)', - 'column_category-name' => 'Name der Kategorie', + 'column_category-name' => 'Kategorie (Name)', 'column_currency-code' => 'Währungsstandard (ISO 4217)', 'column_foreign-currency-code' => 'Fremdwährungscode (ISO 4217)', 'column_currency-id' => 'Währung (ID übereinstimmend mit FF3)', diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 1c0d1004e4..1788206605 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -138,6 +138,7 @@ return [ '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.', @@ -147,154 +148,156 @@ return [ '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.', - 'ynab_account_closed' => 'Account is closed!', - 'ynab_account_deleted' => 'Account is deleted!', - 'ynab_account_type_savings' => 'savings account', - 'ynab_account_type_checking' => 'checking account', - 'ynab_account_type_cash' => 'cash account', - 'ynab_account_type_creditCard' => 'credit card', - 'ynab_account_type_lineOfCredit' => 'line of credit', - 'ynab_account_type_otherAsset' => 'other asset account', - 'ynab_account_type_otherLiability' => 'other liabilities', - 'ynab_account_type_payPal' => 'Paypal', - 'ynab_account_type_merchantAccount' => 'merchant account', - 'ynab_account_type_investmentAccount' => 'investment account', - 'ynab_account_type_mortgage' => 'mortgage', - 'ynab_do_not_import' => '(do not import)', - 'job_config_ynab_apply_rules' => 'Apply rules', - 'job_config_ynab_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.', + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: - 'job_config_select_budgets' => 'Select your budget', - 'job_config_spectre_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', - 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', // 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_expiry_date' => 'Expiry date', - 'spectre_extra_key_open_date' => 'Open date', - 'spectre_extra_key_current_time' => 'Current time', - 'spectre_extra_key_current_date' => 'Current date', - 'spectre_extra_key_cards' => 'Cards', - 'spectre_extra_key_units' => 'Units', - 'spectre_extra_key_unit_price' => 'Unit price', - 'spectre_extra_key_transactions_count' => 'Transaction count', + '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_expiry_date' => 'Expiry date', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Current time', + 'spectre_extra_key_current_date' => 'Current date', + 'spectre_extra_key_cards' => 'Cards', + 'spectre_extra_key_units' => 'Units', + 'spectre_extra_key_unit_price' => 'Unit price', + 'spectre_extra_key_transactions_count' => 'Transaction count', // specifics: - 'specific_ing_name' => 'ING NL', - 'specific_ing_descr' => 'Create better descriptions in ING exports', - 'specific_sns_name' => 'SNS / Volksbank NL', - 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', - 'specific_abn_name' => 'ABN AMRO NL', - 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', - 'specific_rabo_name' => 'Rabobank NL', - 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', - 'specific_pres_name' => 'President\'s Choice Financial CA', - 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Create better descriptions in ING exports', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', // 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_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_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_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 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', - 'map_do_not_map' => '(do not map)', - 'job_config_map_submit' => 'Start the import', + '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', + 'map_do_not_map' => '(do not map)', + 'job_config_map_submit' => 'Start the import', // import status page: - 'import_with_key' => 'Import with key \':key\'', - 'status_wait_title' => 'Please hold...', - 'status_wait_text' => 'This box will disappear in a moment.', - 'status_running_title' => 'The import is running', - '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_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.', - '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.', + 'import_with_key' => 'Import with key \':key\'', + 'status_wait_title' => 'Please hold...', + 'status_wait_text' => 'This box will disappear in a moment.', + 'status_running_title' => 'The import is running', + '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_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.', + '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.', // general errors and warnings: - 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', // column roles for CSV import: - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching FF3)', - 'column_account-name' => 'Asset account (name)', - 'column_account-bic' => 'Asset account (BIC)', - 'column_amount' => 'Amount', - 'column_amount_foreign' => 'Amount (in foreign currency)', - 'column_amount_debit' => 'Amount (debit column)', - 'column_amount_credit' => 'Amount (credit column)', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching FF3)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching FF3)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching FF3)', - 'column_category-name' => 'Category name', - '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_date-transaction' => 'Date', - 'column_date-due' => 'Transaction due date', - 'column_date-payment' => 'Transaction payment date', - 'column_date-invoice' => 'Transaction invoice date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-bic' => 'Opposing account (BIC)', - 'column_opposing-id' => 'Opposing account ID (matching FF3)', - 'column_external-id' => 'External ID', - '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_tags-comma' => 'Tags (comma separated)', - 'column_tags-space' => 'Tags (space separated)', - 'column_account-number' => 'Asset account (account number)', - 'column_opposing-number' => 'Opposing account (account number)', - 'column_note' => 'Note(s)', - 'column_internal-reference' => 'Internal reference', + 'column__ignore' => '(ignore this column)', + 'column_account-iban' => 'Asset account (IBAN)', + 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-name' => 'Asset account (name)', + 'column_account-bic' => 'Asset account (BIC)', + 'column_amount' => 'Amount', + 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_debit' => 'Amount (debit column)', + 'column_amount_credit' => 'Amount (credit column)', + 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_bill-name' => 'Bill name', + 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-name' => 'Budget name', + 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-name' => 'Category name', + '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_date-transaction' => 'Date', + 'column_date-due' => 'Transaction due date', + 'column_date-payment' => 'Transaction payment date', + 'column_date-invoice' => 'Transaction invoice date', + 'column_description' => 'Description', + 'column_opposing-iban' => 'Opposing account (IBAN)', + 'column_opposing-bic' => 'Opposing account (BIC)', + 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_external-id' => 'External ID', + '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_tags-comma' => 'Tags (comma separated)', + 'column_tags-space' => 'Tags (space separated)', + 'column_account-number' => 'Asset account (account number)', + 'column_opposing-number' => 'Opposing account (account number)', + 'column_note' => 'Note(s)', + 'column_internal-reference' => 'Internal reference', ]; diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 86d0b21cfb..eeecbbfb2a 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Cuentas de la pantalla de inicio', 'pref_home_screen_accounts_help' => '¿Qué cuentas se deben mostrar en la página de inicio?', 'pref_view_range' => 'Rango de vision', - 'pref_view_range_help' => 'Algunos gráficos se agrupan automáticamente en periodos. ¿que periodo prefiere usted?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Un dia', 'pref_1W' => 'Una semana', 'pref_1M' => 'Un mes', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Cuentas de efectivo', 'Cash account' => 'Cuenta de efectivo', 'reconcile_account' => 'Reconciliar cuenta ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Eliminar reconciliacion', 'update_reconciliation' => 'Actualizar reconciliacion', 'amount_cannot_be_zero' => 'El monto no puede ser cero', diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index b0ec046275..8518b5faae 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/es_ES/import.php b/resources/lang/es_ES/import.php index a5540e4879..0ae2be314c 100644 --- a/resources/lang/es_ES/import.php +++ b/resources/lang/es_ES/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Importar datos a Firefly III', - 'prerequisites_breadcrumb_fake' => 'Requisitos para el proveedor de importación falso', - 'prerequisites_breadcrumb_spectre' => 'Requisitos previos para Spectre', - 'prerequisites_breadcrumb_bunq' => 'Requisitos previos para bunq', - 'job_configuration_breadcrumb' => 'Configuración para ":key"', - 'job_status_breadcrumb' => 'Import status for ":key"', - 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + 'index_breadcrumb' => 'Importar datos a Firefly III', + 'prerequisites_breadcrumb_fake' => 'Requisitos para el proveedor de importación falso', + 'prerequisites_breadcrumb_spectre' => 'Requisitos previos para Spectre', + 'prerequisites_breadcrumb_bunq' => 'Requisitos previos para bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'job_configuration_breadcrumb' => 'Configuración para ":key"', + 'job_status_breadcrumb' => 'Import status for ":key"', + 'cannot_create_for_provider' => 'Firefly III cannot create a job for the ":provider"-provider.', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - 'general_index_title' => 'Importar un archivo', - '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' => 'Importar un archivo', + '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' => 'Importar un archivo', - 'button_bunq' => 'Importar desde bunq', - 'button_spectre' => 'Importar usando Spectre', - 'button_plaid' => 'Importar usando Plaid', - 'button_yodlee' => 'Importar usando Yodlee', - 'button_quovo' => 'Importar usando Quovo', + 'button_fake' => 'Fake an import', + 'button_file' => 'Importar un archivo', + 'button_bunq' => 'Importar desde bunq', + 'button_spectre' => 'Importar usando Spectre', + 'button_plaid' => 'Importar usando Plaid', + 'button_yodlee' => 'Importar usando Yodlee', + 'button_quovo' => 'Importar usando Quovo', + 'button_ynab' => 'Import from You Need A Budget', // 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' => 'Global import configuration', + 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', // prerequisites box (index) - 'need_prereq_title' => 'Importar requisitos previos', - '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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'need_prereq_title' => 'Importar requisitos previos', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Import configuration', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => '¡App ID y secreto guardados!', - 'prerequisites_saved_for_bunq' => '¡Clave de API e IP almacenadas!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => '¡App ID y secreto guardados!', + 'prerequisites_saved_for_bunq' => '¡Clave de API e IP almacenadas!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Upload files', - 'import_file_type_csv' => 'CSV (valores separados por comas)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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' => 'Aplicar reglas', - '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', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (valores separados por comas)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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' => 'Aplicar reglas', + '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', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Caja de ahorros (IBAN)', 'column_account-id' => 'Identificación de Cuenta de ingresos (coincide con FF3)', 'column_account-name' => 'Caja de ahorros (nombre)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Cantidad', 'column_amount_foreign' => 'Monto ( en moneda extranjera)', 'column_amount_debit' => 'Cantidad (columna de débito)', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 9a812d40da..9fbab65594 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Comptes de l’écran d’accueil', 'pref_home_screen_accounts_help' => 'Quels sont les comptes à afficher sur la page d’accueil ?', 'pref_view_range' => 'Voir l\'étendue', - 'pref_view_range_help' => 'Certains graphiques sont automatiquement groupés par périodes. Quelle période préférez-vous ?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Un jour', 'pref_1W' => 'Une semaine', 'pref_1M' => 'Un mois', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Comptes de trésorerie', 'Cash account' => 'Compte de trésorerie', 'reconcile_account' => 'Rapprocher le compte ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Supprimer le rapprochement', 'update_reconciliation' => 'Mettre à jour le rapprochement', 'amount_cannot_be_zero' => 'Le montant ne peut pas être zéro', diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index c535a3db39..24b59b598b 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Répétitions', 'calendar' => 'Calendrier', 'weekend' => 'Week-end', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index 4c50f8363b..f2f55531e9 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - '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_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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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".', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - '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.', + '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' => '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', + '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', + 'button_ynab' => 'Import from You Need A Budget', // global config box (index) - '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.', + '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' => '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' => 'Prérequis pour les importations depuis Bunq', - 'do_prereq_spectre' => 'Prérequis pour les importations depuis Spectre', - 'do_prereq_plaid' => 'Prérequis pour les importations depuis Plaid', - 'do_prereq_yodlee' => 'Prérequis pour les importations depuis Yodlee', - 'do_prereq_quovo' => 'Prérequis pour les importations depuis Quovo', + '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' => 'Prérequis pour les importations depuis Bunq', + 'do_prereq_spectre' => 'Prérequis pour les importations depuis Spectre', + 'do_prereq_plaid' => 'Prérequis pour les importations depuis Plaid', + 'do_prereq_yodlee' => 'Prérequis pour les importations depuis Yodlee', + 'do_prereq_quovo' => 'Prérequis pour les importations depuis Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Configuration d\'importation', - 'can_config_intro' => 'Certaines méthodes d’importation peuvent être configurées selon vos préférences. Elles ont des paramètres supplémentaires que vous pouvez modifier.', - 'do_config_fake' => 'Configuration du simulateur d\'importation', - 'do_config_file' => 'Configuration pour l’importation de fichier', - 'do_config_bunq' => 'Configuration pour les importations depuis Bunq', - 'do_config_spectre' => 'Configuration pour les importations depuis Spectre', - 'do_config_plaid' => 'Configuration pour les importations depuis Plaid', - 'do_config_yodlee' => 'Configuration pour les importations depuis Yodlee', - 'do_config_quovo' => 'Configuration pour les importations depuis Quovo', + 'can_config_title' => 'Configuration d\'importation', + 'can_config_intro' => 'Certaines méthodes d’importation peuvent être configurées selon vos préférences. Elles ont des paramètres supplémentaires que vous pouvez modifier.', + 'do_config_fake' => 'Configuration du simulateur d\'importation', + 'do_config_file' => 'Configuration pour l’importation de fichier', + 'do_config_bunq' => 'Configuration pour les importations depuis Bunq', + 'do_config_spectre' => 'Configuration pour les importations depuis Spectre', + 'do_config_plaid' => 'Configuration pour les importations depuis Plaid', + 'do_config_yodlee' => 'Configuration pour les importations depuis Yodlee', + 'do_config_quovo' => 'Configuration pour les importations depuis Quovo', // prerequisites: - 'prereq_fake_title' => 'Prérequis pour une importation utilisant le simulateur d\'importation', - 'prereq_fake_text' => 'Le simulateur d\'importation nécessite une fausse clé d\'API. Vous pouvez utiliser la clé suivante : 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prérequis à l\'importation de données avec Spectre', - 'prereq_spectre_text' => 'Pour importer des données avec l\'API Spectre (v4) vous devez fournir à Firefly III deux secrets. Vous les trouverez sur la page des secrets.', - 'prereq_spectre_pub' => 'De même, l\'API Spectre doit connaitre votre clé publique affichée ci-dessous. Sans elle, vous ne serez pas reconnu. Merci de renseigner votre clé publique dans la page des secrets.', - 'prereq_bunq_title' => 'Prérequis à l\'importation de données depuis bunq', - 'prereq_bunq_text' => 'Pour importer des données depuis bunq vous devez obtenir un clé d\'API. Cette clé peut être obtenue depuis l\'application. Merci de prendre en compte que la fonction d\'importation depuis bunq est en BETA. Elle a été testée uniquement au travers de l\'API bac à sable.', - 'prereq_bunq_ip' => 'bunq a besoin de votre adresse IP publique. Firefly III a tenté de la déterminer grâce au service ipify. Assurez-vous que l\'adresse IP est correcte ou l\'importation échouera.', + 'prereq_fake_title' => 'Prérequis pour une importation utilisant le simulateur d\'importation', + 'prereq_fake_text' => 'Le simulateur d\'importation nécessite une fausse clé d\'API. Vous pouvez utiliser la clé suivante : 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prérequis à l\'importation de données avec Spectre', + 'prereq_spectre_text' => 'Pour importer des données avec l\'API Spectre (v4) vous devez fournir à Firefly III deux secrets. Vous les trouverez sur la page des secrets.', + 'prereq_spectre_pub' => 'De même, l\'API Spectre doit connaitre votre clé publique affichée ci-dessous. Sans elle, vous ne serez pas reconnu. Merci de renseigner votre clé publique dans la page des secrets.', + 'prereq_bunq_title' => 'Prérequis à l\'importation de données depuis bunq', + 'prereq_bunq_text' => 'Pour importer des données depuis bunq vous devez obtenir un clé d\'API. Cette clé peut être obtenue depuis l\'application. Merci de prendre en compte que la fonction d\'importation depuis bunq est en BETA. Elle a été testée uniquement au travers de l\'API bac à sable.', + 'prereq_bunq_ip' => 'bunq a besoin de votre adresse IP publique. Firefly III a tenté de la déterminer grâce au service ipify. Assurez-vous que l\'adresse IP est correcte ou l\'importation échouera.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fausse clé API enregistrée avec succès !', - 'prerequisites_saved_for_spectre' => 'ID App et secret enregistrés !', - 'prerequisites_saved_for_bunq' => 'Clé API et adresse IP enregistrées !', + 'prerequisites_saved_for_fake' => 'Fausse clé API enregistrée avec succès !', + 'prerequisites_saved_for_spectre' => 'ID App et secret enregistrés !', + 'prerequisites_saved_for_bunq' => 'Clé API et adresse IP enregistrées !', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Configuration de la tâche - Appliquer vos règles ?', - 'job_config_apply_rules_text' => 'Une fois le fournisseur de la simulation exécuté, vos règles peuvent être appliquées aux transactions. Notez que ceci allongera le temps de l\'importation.', - 'job_config_input' => 'Vos données d\'entrée', + 'job_config_apply_rules_title' => 'Configuration de la tâche - Appliquer vos règles ?', + 'job_config_apply_rules_text' => 'Une fois le fournisseur de la simulation exécuté, vos règles peuvent être appliquées aux transactions. Notez que ceci allongera le temps de l\'importation.', + 'job_config_input' => 'Vos données d\'entrée', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Saisir un nom d\'album', - 'job_config_fake_artist_text' => 'Beaucoup de routines d\'importation ont quelques étapes de configuration par lesquelles vous devez passer. Dans le cas du fournisseur du simulateur d\'importation, vous devez répondre à des questions étranges. Dans ce cas, saisissez "David Bowie" pour continuer.', - 'job_config_fake_song_title' => 'Saisir un nom de chanson', - 'job_config_fake_song_text' => 'Citez la chanson "Golden years" pour continuer la simulation d\'importation.', - 'job_config_fake_album_title' => 'Saisir un nom d\'album', - 'job_config_fake_album_text' => 'Certaines routines d\'importation nécessitent des données complémentaires en milieu d\'exécution. Dans le cas du fournisseur du simulateur d\'importation, vous devez répondre à des questions étranges. Saisissez "Station to station" pour continuer.', + 'job_config_fake_artist_title' => 'Saisir un nom d\'album', + 'job_config_fake_artist_text' => 'Beaucoup de routines d\'importation ont quelques étapes de configuration par lesquelles vous devez passer. Dans le cas du fournisseur du simulateur d\'importation, vous devez répondre à des questions étranges. Dans ce cas, saisissez "David Bowie" pour continuer.', + 'job_config_fake_song_title' => 'Saisir un nom de chanson', + 'job_config_fake_song_text' => 'Citez la chanson "Golden years" pour continuer la simulation d\'importation.', + 'job_config_fake_album_title' => 'Saisir un nom d\'album', + 'job_config_fake_album_text' => 'Certaines routines d\'importation nécessitent des données complémentaires en milieu d\'exécution. Dans le cas du fournisseur du simulateur d\'importation, vous devez répondre à des questions étranges. Saisissez "Station to station" pour continuer.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Configuration de l\'importation (1/4) - Téléchargez votre fichier', - 'job_config_file_upload_text' => 'Cette routine vous aidera à importer des fichiers depuis votre banque vers Firefly III. ', - 'job_config_file_upload_help' => 'Choisissez votre fichier. Veuillez vous assurer qu\'il est encodé en UTF-8.', - 'job_config_file_upload_config_help' => 'Si vous avez précédemment importé des données dans Firefly III, vous avez peut-être téléchargé un fichier de configuration qui définit les relations entre les différents champs. Pour certaines banques, des utilisateurs ont bien voulu partager leur fichier ici : fichiers de configuration', - 'job_config_file_upload_type_help' => 'Sélectionnez le type de fichier que vous allez télécharger', - 'job_config_file_upload_submit' => 'Envoyer des fichiers', - 'import_file_type_csv' => 'CSV (valeurs séparées par des virgules)', - 'file_not_utf8' => 'Le fichier téléchargé n\'est pas encodé en UTF-8 ou en ASCII. Firefly ne peut pas gérer un tel fichier. Veuillez utiliser Notepad++ ou Sublime Text pour convertir votre fichier en UTF-8.', - 'job_config_uc_title' => 'Configuration de l\'importation (2/4) - Configuration du fichier importé', - 'job_config_uc_text' => 'Pour pouvoir importer votre fichier correctement, veuillez valider les options ci-dessous.', - 'job_config_uc_header_help' => 'Cochez cette case si la première ligne de votre fichier CSV contient les entêtes des colonnes.', - 'job_config_uc_date_help' => 'Le format de la date et de l’heure dans votre fichier. Suivez les options de formatage décrites sur cette page. La valeur par défaut va analyser les dates ayant cette syntaxe : :dateExample.', - 'job_config_uc_delimiter_help' => 'Choisissez le délimiteur de champ qui est utilisé dans votre fichier d’entrée. Si vous n\'en êtes pas certain, la virgule est l’option la plus sûre.', - 'job_config_uc_account_help' => 'Si votre fichier ne contient AUCUNE information concernant vos compte(s) actif, utilisez cette liste déroulante pour choisir à quel compte les opérations contenues dans le fichier s\'appliquent.', - 'job_config_uc_apply_rules_title' => 'Appliquer les règles', - 'job_config_uc_apply_rules_text' => 'Appliquer vos règles à chaque opération importée. Notez que cela peut ralentir significativement l\'importation .', - 'job_config_uc_specifics_title' => 'Options spécifiques à la banque', - 'job_config_uc_specifics_txt' => 'Certaines banques délivrent des fichiers mal formatés. Firefly III peut les corriger automatiquement. Si votre banque délivre de tels fichiers mais qu\'elle n\'est pas listée ici, merci d\'ouvrir une demande sur GitHub.', - 'job_config_uc_submit' => 'Continuer', - 'invalid_import_account' => 'Vous avez sélectionné un compte non valide pour l\'importation.', + 'job_config_file_upload_title' => 'Configuration de l\'importation (1/4) - Téléchargez votre fichier', + 'job_config_file_upload_text' => 'Cette routine vous aidera à importer des fichiers depuis votre banque vers Firefly III. ', + 'job_config_file_upload_help' => 'Choisissez votre fichier. Veuillez vous assurer qu\'il est encodé en UTF-8.', + 'job_config_file_upload_config_help' => 'Si vous avez précédemment importé des données dans Firefly III, vous avez peut-être téléchargé un fichier de configuration qui définit les relations entre les différents champs. Pour certaines banques, des utilisateurs ont bien voulu partager leur fichier ici : fichiers de configuration', + 'job_config_file_upload_type_help' => 'Sélectionnez le type de fichier que vous allez télécharger', + 'job_config_file_upload_submit' => 'Envoyer des fichiers', + 'import_file_type_csv' => 'CSV (valeurs séparées par des virgules)', + 'file_not_utf8' => 'Le fichier téléchargé n\'est pas encodé en UTF-8 ou en ASCII. Firefly ne peut pas gérer un tel fichier. Veuillez utiliser Notepad++ ou Sublime Text pour convertir votre fichier en UTF-8.', + 'job_config_uc_title' => 'Configuration de l\'importation (2/4) - Configuration du fichier importé', + 'job_config_uc_text' => 'Pour pouvoir importer votre fichier correctement, veuillez valider les options ci-dessous.', + 'job_config_uc_header_help' => 'Cochez cette case si la première ligne de votre fichier CSV contient les entêtes des colonnes.', + 'job_config_uc_date_help' => 'Le format de la date et de l’heure dans votre fichier. Suivez les options de formatage décrites sur cette page. La valeur par défaut va analyser les dates ayant cette syntaxe : :dateExample.', + 'job_config_uc_delimiter_help' => 'Choisissez le délimiteur de champ qui est utilisé dans votre fichier d’entrée. Si vous n\'en êtes pas certain, la virgule est l’option la plus sûre.', + 'job_config_uc_account_help' => 'Si votre fichier ne contient AUCUNE information concernant vos compte(s) actif, utilisez cette liste déroulante pour choisir à quel compte les opérations contenues dans le fichier s\'appliquent.', + 'job_config_uc_apply_rules_title' => 'Appliquer les règles', + 'job_config_uc_apply_rules_text' => 'Appliquer vos règles à chaque opération importée. Notez que cela peut ralentir significativement l\'importation .', + 'job_config_uc_specifics_title' => 'Options spécifiques à la banque', + 'job_config_uc_specifics_txt' => 'Certaines banques délivrent des fichiers mal formatés. Firefly III peut les corriger automatiquement. Si votre banque délivre de tels fichiers mais qu\'elle n\'est pas listée ici, merci d\'ouvrir une demande sur GitHub.', + 'job_config_uc_submit' => 'Continuer', + 'invalid_import_account' => 'Vous avez sélectionné un compte non valide pour l\'importation.', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Choisissez votre identifiant', - 'job_config_spectre_login_text' => 'Firefly III a trouvé :count identifiant·s dans votre compte Spectre. Lequel voulez-vous utiliser pour importer des données ?', - 'spectre_login_status_active' => 'Actif', - 'spectre_login_status_inactive' => 'Inactif', - 'spectre_login_status_disabled' => 'Désactivé', - 'spectre_login_new_login' => 'S\'identifier avec une autre banque, ou à une de ces banques avec un autre identifiant.', - 'job_config_spectre_accounts_title' => 'Sélectionnez le·s compte·s à importer', - 'job_config_spectre_accounts_text' => 'Vous avez sélectionné ":name" (:country). Vous avez :count compte·s disponible·s chez ce fournisseur. Veuillez sélectionner le·s compte·s d\'actifs Firefly III dans le·s·quel·s enregistrer les opérations. Souvenez-vous, pour importer des données, le compte Firefly III et le compte ":name" doivent avoir la même devise.', - 'spectre_no_supported_accounts' => 'Vous ne pouvez pas importer de données depuis ce compte car les devises ne sont pas identiques.', - 'spectre_do_not_import' => '(ne pas importer)', - 'spectre_no_mapping' => 'Il semble que vous n\'avez sélectionné aucun compte depuis lequel importer.', - 'imported_from_account' => 'Importé depuis ":account"', - 'spectre_account_with_number' => 'Compte :number', - 'job_config_spectre_apply_rules' => 'Appliquer les règles', - 'job_config_spectre_apply_rules_text' => 'Par défaut vos règles seront appliquées aux opérations créées pendant l\'importation. Si vous ne voulez pas que vos règles s\'appliquent, décochez cette case.', + 'job_config_spectre_login_title' => 'Choisissez votre identifiant', + 'job_config_spectre_login_text' => 'Firefly III a trouvé :count identifiant·s dans votre compte Spectre. Lequel voulez-vous utiliser pour importer des données ?', + 'spectre_login_status_active' => 'Actif', + 'spectre_login_status_inactive' => 'Inactif', + 'spectre_login_status_disabled' => 'Désactivé', + 'spectre_login_new_login' => 'S\'identifier avec une autre banque, ou à une de ces banques avec un autre identifiant.', + 'job_config_spectre_accounts_title' => 'Sélectionnez le·s compte·s à importer', + 'job_config_spectre_accounts_text' => 'Vous avez sélectionné ":name" (:country). Vous avez :count compte·s disponible·s chez ce fournisseur. Veuillez sélectionner le·s compte·s d\'actifs Firefly III dans le·s·quel·s enregistrer les opérations. Souvenez-vous, pour importer des données, le compte Firefly III et le compte ":name" doivent avoir la même devise.', + 'spectre_no_supported_accounts' => 'Vous ne pouvez pas importer de données depuis ce compte car les devises ne sont pas identiques.', + 'spectre_do_not_import' => '(ne pas importer)', + 'spectre_no_mapping' => 'Il semble que vous n\'avez sélectionné aucun compte depuis lequel importer.', + 'imported_from_account' => 'Importé depuis ":account"', + 'spectre_account_with_number' => 'Compte :number', + 'job_config_spectre_apply_rules' => 'Appliquer les règles', + 'job_config_spectre_apply_rules_text' => 'Par défaut vos règles seront appliquées aux opérations créées pendant l\'importation. Si vous ne voulez pas que vos règles s\'appliquent, décochez cette case.', + // job configuration for bunq: - 'job_config_bunq_accounts_title' => 'Comptes bunq', - 'job_config_bunq_accounts_text' => 'Voici les comptes associés à votre compte bunq. Veuillez sélectionner les comptes depuis lesquels vous voulez importer et le compte vers lequel vous voulez importer les opérations.', - 'bunq_no_mapping' => 'Il semble que vous n\'avez sélectionné aucun compte.', - 'should_download_config' => 'Vous devriez télécharger le fichier de configuration de cette tâche. Cela rendra vos futures importations plus faciles.', - 'share_config_file' => 'Si vous avez importé des données depuis une banque publique, vous devriez partager votre fichier de configuration. Il sera ainsi plus facile pour les autres utilisateurs d\'importer leurs données. Le partage de votre fichier de configuration n\'expose pas vos informations financières.', - 'job_config_bunq_apply_rules' => 'Appliquer les règles', - 'job_config_bunq_apply_rules_text' => 'Par défaut vos règles seront appliquées aux opérations créées pendant l\'importation. Si vous ne voulez pas que vos règles s\'appliquent, décochez cette case.', + 'job_config_bunq_accounts_title' => 'Comptes bunq', + 'job_config_bunq_accounts_text' => 'Voici les comptes associés à votre compte bunq. Veuillez sélectionner les comptes depuis lesquels vous voulez importer et le compte vers lequel vous voulez importer les opérations.', + 'bunq_no_mapping' => 'Il semble que vous n\'avez sélectionné aucun compte.', + 'should_download_config' => 'Vous devriez télécharger le fichier de configuration de cette tâche. Cela rendra vos futures importations plus faciles.', + 'share_config_file' => 'Si vous avez importé des données depuis une banque publique, vous devriez partager votre fichier de configuration. Il sera ainsi plus facile pour les autres utilisateurs d\'importer leurs données. Le partage de votre fichier de configuration n\'expose pas vos informations financières.', + 'job_config_bunq_apply_rules' => 'Appliquer les règles', + 'job_config_bunq_apply_rules_text' => 'Par défaut vos règles seront appliquées aux opérations créées pendant l\'importation. Si vous ne voulez pas que vos règles s\'appliquent, décochez cette case.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Compte d’actif (IBAN)', 'column_account-id' => 'Compte d\'actif (ID correspondant à FF3)', 'column_account-name' => 'Compte d’actif (nom)', - 'column_account-bic' => 'Compte d’actif (BIC)', + 'column_account-bic' => 'Compte d’actif (BIC)', 'column_amount' => 'Montant', 'column_amount_foreign' => 'Montant (en devise étrangère)', 'column_amount_debit' => 'Montant (colonne débit)', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 8c4c210e77..77dea623aa 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Akun layar utama', 'pref_home_screen_accounts_help' => 'Akun mana yang harus ditampilkan di beranda?', 'pref_view_range' => 'Rentang tampilan', - 'pref_view_range_help' => 'Beberapa grafik secara otomatis dikelompokkan dalam beberapa periode. Periode apa yang anda inginkan?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Suatu hari', 'pref_1W' => 'Satu minggu', 'pref_1M' => 'Satu bulan', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Akun kas', 'Cash account' => 'Akun kas', 'reconcile_account' => 'Rekonsiliasi akun ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Hapus rekonsiliasi', 'update_reconciliation' => 'Rekonsiliasi pembaruan', 'amount_cannot_be_zero' => 'Jumlah tersebut tidak dapat menjadi nol', diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index 081ce56d1b..f6a6585720 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/id_ID/import.php b/resources/lang/id_ID/import.php index dfb98557cc..bb68fb307c 100644 --- a/resources/lang/id_ID/import.php +++ b/resources/lang/id_ID/import.php @@ -24,119 +24,154 @@ 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' => '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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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.', + 'disabled_for_demo_user' => 'disabled in demo', // 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' => '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.', // 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' => '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_ynab' => 'Import from You Need A Budget', // 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' => 'Global import configuration', + '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_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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Import configuration', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Upload files', - 'import_file_type_csv' => 'CSV (nilai yang dipisahkan koma)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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_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', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (nilai yang dipisahkan koma)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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_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', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Akun aset (IBAN)', 'column_account-id' => 'Asset account ID (matching FF3)', 'column_account-name' => 'Akun aset (nama)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Jumlah', 'column_amount_foreign' => 'Amount (in foreign currency)', 'column_amount_debit' => 'Jumlah (kolom debit)', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 34320af6a0..b402a9674e 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -443,7 +443,7 @@ return [ '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_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Un giorno', 'pref_1W' => 'Una settimana', 'pref_1M' => 'Un mese', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Conti contanti', 'Cash account' => 'Conto contanti', 'reconcile_account' => 'Riconciliazione conto ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Elimina riconciliazione', 'update_reconciliation' => 'Aggiorna riconciliazione', 'amount_cannot_be_zero' => 'L\'importo non può essere zero', @@ -745,10 +746,10 @@ return [ 'reconcilliation_transaction_title' => 'Riconciliazione (:from a :to)', 'reconcile_this_account' => 'Riconcilia questo conto', 'confirm_reconciliation' => 'Conferma riconciliazione', - 'submitted_start_balance' => 'Saldo iniziale presentato', + 'submitted_start_balance' => 'Saldo iniziale inserito', 'selected_transactions' => 'Transazioni selezionate (:count)', 'already_cleared_transactions' => 'Transazioni già conciliate (:count)', - 'submitted_end_balance' => 'Saldo finale inviato', + 'submitted_end_balance' => 'Saldo finale inserito', 'initial_balance_description' => 'Saldo iniziale per ":account"', // categories: diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index 41360638d9..4fffc58257 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Ripetizioni', 'calendar' => 'Calendario', 'weekend' => 'Fine settimana', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index c78c15921d..892ee094d6 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Importa i dati in Firefly III', - 'prerequisites_breadcrumb_fake' => 'Prerequisiti per il fornitore di importazione fittizio', - 'prerequisites_breadcrumb_spectre' => 'Prerequisiti per Spectre', - '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\'operazione per il provider ":provider".', + 'index_breadcrumb' => 'Importa i dati in Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisiti per il fornitore di importazione fittizio', + 'prerequisites_breadcrumb_spectre' => 'Prerequisiti per Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisiti per bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'job_configuration_breadcrumb' => 'Configurazione per ":key"', + 'job_status_breadcrumb' => 'Stato di importazione per ":key"', + 'cannot_create_for_provider' => 'Firefly III non può creare un\'operazione per il provider ":provider".', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - 'general_index_title' => 'Importa un file', - 'general_index_intro' => 'Benvenuti nella routine di importazione di Firefly III. Esistono alcuni modi per importare dati in Firefly III, visualizzati qui come pulsanti.', + 'general_index_title' => 'Importa un file', + 'general_index_intro' => 'Benvenuti nella routine di importazione di Firefly III. Esistono alcuni modi per importare dati in Firefly III, visualizzati qui come pulsanti.', // import provider strings (index): - 'button_fake' => 'Esegui un\'importazione fittizia', - 'button_file' => 'Importa un file', - 'button_bunq' => 'Importa da bunq', - 'button_spectre' => 'Importa usando Spectre', - 'button_plaid' => 'Importa usando Plaid', - 'button_yodlee' => 'Importa usando Yodlee', - 'button_quovo' => 'Importa usando Quovo', + 'button_fake' => 'Esegui un\'importazione fittizia', + 'button_file' => 'Importa un file', + 'button_bunq' => 'Importa da bunq', + 'button_spectre' => 'Importa usando Spectre', + 'button_plaid' => 'Importa usando Plaid', + 'button_yodlee' => 'Importa usando Yodlee', + 'button_quovo' => 'Importa usando Quovo', + 'button_ynab' => 'Import from You Need A Budget', // global config box (index) - 'global_config_title' => 'Configurazione globale di importazione', - 'global_config_text' => 'In futuro, questo riquadro presenterà le preferenze che si applicano a TUTTI i fornitori di importazione di cui sopra.', + 'global_config_title' => 'Configurazione globale di importazione', + 'global_config_text' => 'In futuro, questo riquadro presenterà le preferenze che si applicano a TUTTI i fornitori di importazione di cui sopra.', // prerequisites box (index) - 'need_prereq_title' => 'Prerequisiti di importazione', - 'need_prereq_intro' => 'Alcuni metodi di importazione richiedono la tua attenzione prima che possano essere utilizzati. Ad esempio, potrebbero richiedere speciali chiavi API o segreti dell\'applicazione. Puoi configurarli qui. L\'icona indica se questi prerequisiti sono stati soddisfatti.', - 'do_prereq_fake' => 'Prerequisiti per il fornitore fittizio', - 'do_prereq_file' => 'Prerequisiti per le importazioni da file', - 'do_prereq_bunq' => 'Prerequisiti per le importazioni da bunq', - 'do_prereq_spectre' => 'Prerequisiti per le importazioni usando Spectre', - 'do_prereq_plaid' => 'Prerequisiti per le importazioni usando Plaid', - 'do_prereq_yodlee' => 'Prerequisiti per le importazioni usando Yodlee', - 'do_prereq_quovo' => 'Prerequisiti per le importazioni usando Quovo', + 'need_prereq_title' => 'Prerequisiti di importazione', + 'need_prereq_intro' => 'Alcuni metodi di importazione richiedono la tua attenzione prima che possano essere utilizzati. Ad esempio, potrebbero richiedere speciali chiavi API o segreti dell\'applicazione. Puoi configurarli qui. L\'icona indica se questi prerequisiti sono stati soddisfatti.', + 'do_prereq_fake' => 'Prerequisiti per il fornitore fittizio', + 'do_prereq_file' => 'Prerequisiti per le importazioni da file', + 'do_prereq_bunq' => 'Prerequisiti per le importazioni da bunq', + 'do_prereq_spectre' => 'Prerequisiti per le importazioni usando Spectre', + 'do_prereq_plaid' => 'Prerequisiti per le importazioni usando Plaid', + 'do_prereq_yodlee' => 'Prerequisiti per le importazioni usando Yodlee', + 'do_prereq_quovo' => 'Prerequisiti per le importazioni usando Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Configurazione di importazione', - 'can_config_intro' => 'Alcuni metodi di importazione possono essere configurati a tuo piacimento. Hanno ulteriori impostazioni che puoi modificare.', - 'do_config_fake' => 'Configurazione per il fornitore fittizio', - 'do_config_file' => 'Configurazione per le importazioni da file', - 'do_config_bunq' => 'Configurazione per le importazioni da bunq', - 'do_config_spectre' => 'Configurazione per importazioni da Spectre', - 'do_config_plaid' => 'Configurazione per importazioni da Plaid', - 'do_config_yodlee' => 'Configurazione per importazioni da Yodlee', - 'do_config_quovo' => 'Configurazione per importazioni da Quovo', + 'can_config_title' => 'Configurazione di importazione', + 'can_config_intro' => 'Alcuni metodi di importazione possono essere configurati a tuo piacimento. Hanno ulteriori impostazioni che puoi modificare.', + 'do_config_fake' => 'Configurazione per il fornitore fittizio', + 'do_config_file' => 'Configurazione per le importazioni da file', + 'do_config_bunq' => 'Configurazione per le importazioni da bunq', + 'do_config_spectre' => 'Configurazione per importazioni da Spectre', + 'do_config_plaid' => 'Configurazione per importazioni da Plaid', + 'do_config_yodlee' => 'Configurazione per importazioni da Yodlee', + 'do_config_quovo' => 'Configurazione per importazioni da Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisiti per un\'importazione dal fornitore di importazione fittizio', - 'prereq_fake_text' => 'Questo provider fittizio richiede una chiave API fittizia. Deve contenere 32 caratteri. È possibile utilizzare questa: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisiti per un\'importazione utilizzando le API di Spectre', - 'prereq_spectre_text' => 'Per l\'importazione dei dati attraverso le API Spectre (v4), devi fornire a Firefly III due valori segreti. Questi si possono trovare nella pagina dei segreti.', - 'prereq_spectre_pub' => 'Allo stesso modo, l\'API Spectre deve conoscere la chiave pubblica che vedi qui sotto. Senza di essa, non ti riconoscerà. Per favore inserisci questa chiave pubblica nella tua pagina dei segreti.', - 'prereq_bunq_title' => 'Prerequisiti per un\'importazione da bunq', - 'prereq_bunq_text' => 'Per importare da bunq, è necessario ottenere una chiave API. Puoi farlo attraverso l\'app. Si noti che la funzione di importazione per bunq è in BETA. È stato testato solo contro l\'API sandbox.', - 'prereq_bunq_ip' => 'bunq richiede il tuo indirizzo IP esterno. Firefly III ha provato a riempire questo campo utilizzando il servizio ipify. Assicurati che questo indirizzo IP sia corretto altrimenti l\'importazione fallirà.', + 'prereq_fake_title' => 'Prerequisiti per un\'importazione dal fornitore di importazione fittizio', + 'prereq_fake_text' => 'Questo provider fittizio richiede una chiave API fittizia. Deve contenere 32 caratteri. È possibile utilizzare questa: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisiti per un\'importazione utilizzando le API di Spectre', + 'prereq_spectre_text' => 'Per l\'importazione dei dati attraverso le API Spectre (v4), devi fornire a Firefly III due valori segreti. Questi si possono trovare nella pagina dei segreti.', + 'prereq_spectre_pub' => 'Allo stesso modo, l\'API Spectre deve conoscere la chiave pubblica che vedi qui sotto. Senza di essa, non ti riconoscerà. Per favore inserisci questa chiave pubblica nella tua pagina dei segreti.', + 'prereq_bunq_title' => 'Prerequisiti per un\'importazione da bunq', + 'prereq_bunq_text' => 'Per importare da bunq, è necessario ottenere una chiave API. Puoi farlo attraverso l\'app. Si noti che la funzione di importazione per bunq è in BETA. È stato testato solo contro l\'API sandbox.', + 'prereq_bunq_ip' => 'bunq richiede il tuo indirizzo IP esterno. Firefly III ha provato a riempire questo campo utilizzando il servizio ipify. Assicurati che questo indirizzo IP sia corretto altrimenti l\'importazione fallirà.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Chiave API fittizia memorizzata correttamente!', - 'prerequisites_saved_for_spectre' => 'ID dell\'app e segreto memorizzati!', - 'prerequisites_saved_for_bunq' => 'Chiave API e IP memorizzati!', + 'prerequisites_saved_for_fake' => 'Chiave API fittizia memorizzata correttamente!', + 'prerequisites_saved_for_spectre' => 'ID dell\'app e segreto memorizzati!', + 'prerequisites_saved_for_bunq' => 'Chiave API e IP memorizzati!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - '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_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: - 'job_config_fake_artist_title' => 'Inserisci il nome dell\'album', - 'job_config_fake_artist_text' => 'Molte routine di importazione presentano alcuni passaggi di configurazione da eseguire. Nel caso del fornitore di importazione fittizio, è necessario rispondere ad alcune domande strane. In questo caso, inserire "David Bowie" per continuare.', - 'job_config_fake_song_title' => 'Inserisci il nome del brano', - 'job_config_fake_song_text' => 'Menziona la canzone "Golden years" per continuare con l\'importazione fittizia.', - 'job_config_fake_album_title' => 'Inserisci il nome dell\'album', - 'job_config_fake_album_text' => 'Alcune routine di importazione richiedono dati aggiuntivi a metà dell\'importazione. Nel caso del fornitore di importazione fittizio, è necessario rispondere ad alcune domande strane. Inserire "Station to station" per continuare.', + 'job_config_fake_artist_title' => 'Inserisci il nome dell\'album', + 'job_config_fake_artist_text' => 'Molte routine di importazione presentano alcuni passaggi di configurazione da eseguire. Nel caso del fornitore di importazione fittizio, è necessario rispondere ad alcune domande strane. In questo caso, inserire "David Bowie" per continuare.', + 'job_config_fake_song_title' => 'Inserisci il nome del brano', + 'job_config_fake_song_text' => 'Menziona la canzone "Golden years" per continuare con l\'importazione fittizia.', + 'job_config_fake_album_title' => 'Inserisci il nome dell\'album', + 'job_config_fake_album_text' => 'Alcune routine di importazione richiedono dati aggiuntivi a metà dell\'importazione. Nel caso del fornitore di importazione fittizio, è necessario rispondere ad alcune domande strane. Inserire "Station to station" per continuare.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Configurazione importazione (1/4) - Carica il tuo file', - 'job_config_file_upload_text' => 'Questa routine ti aiuterà a importare i file dalla tua banca in Firefly III. ', - 'job_config_file_upload_help' => 'Seleziona il tuo file. Assicurati che il file sia codificato in UTF-8.', - 'job_config_file_upload_config_help' => 'Se hai precedentemente importato i dati in Firefly III, potresti avere un file di configurazione, che preimposterà i valori di configurazione per te. Per alcune banche, altri utenti hanno gentilmente fornito il loro file di configurazione', - 'job_config_file_upload_type_help' => 'Seleziona il tipo di file che caricherai', - 'job_config_file_upload_submit' => 'Carica i file', - 'import_file_type_csv' => 'CSV (valori separati da virgola)', - 'file_not_utf8' => 'Il file che hai caricato non è codificato come UTF-8 o ASCII. Firefly III non può gestire tali file. Utilizzare Notepad++ o Sublime per convertire il file in UTF-8.', - 'job_config_uc_title' => 'Configurazione di importazione (2/4) - Impostazione di base dei file', - 'job_config_uc_text' => 'Per poter importare correttamente il tuo file, ti preghiamo di convalidare le opzioni di seguito.', - 'job_config_uc_header_help' => 'Seleziona questa casella se la prima riga del tuo file CSV sono i titoli delle colonne.', - 'job_config_uc_date_help' => 'Formato della data e ora nel tuo file. Segui il formato indicato in questa pagina. Il valore predefinito analizzerà le date che assomigliano a questa: :dateExample.', - 'job_config_uc_delimiter_help' => 'Scegli il delimitatore di campo che viene utilizzato nel file di ingresso. Se non si è sicuri, la virgola è l\'opzione più sicura.', - 'job_config_uc_account_help' => 'Se il tuo file NON contiene informazioni sui tuoi conti di attività, utilizza questo menu a discesa per selezionare a quale conto appartengono le transazioni nel file.', - 'job_config_uc_apply_rules_title' => 'Applica regole', - 'job_config_uc_apply_rules_text' => 'Applica le tue regole ad ogni transazione importata. Si noti che questo rallenta l\'importazione in modo significativo.', - 'job_config_uc_specifics_title' => 'Opzioni specifiche della banca', - 'job_config_uc_specifics_txt' => 'Alcune banche forniscono file formattati in modo errato. Firefly III può sistemarli automaticamente. Se la tua banca rende disponibili tali file ma non è elencata qui, ti preghiamo di segnalare il problema su GitHub.', - 'job_config_uc_submit' => 'Continua', - 'invalid_import_account' => 'Hai selezionato un conto non valido su cui effettuare l\'importazione.', + 'job_config_file_upload_title' => 'Configurazione importazione (1/4) - Carica il tuo file', + 'job_config_file_upload_text' => 'Questa routine ti aiuterà a importare i file dalla tua banca in Firefly III. ', + 'job_config_file_upload_help' => 'Seleziona il tuo file. Assicurati che il file sia codificato in UTF-8.', + 'job_config_file_upload_config_help' => 'Se hai precedentemente importato i dati in Firefly III, potresti avere un file di configurazione, che preimposterà i valori di configurazione per te. Per alcune banche, altri utenti hanno gentilmente fornito il loro file di configurazione', + 'job_config_file_upload_type_help' => 'Seleziona il tipo di file che caricherai', + 'job_config_file_upload_submit' => 'Carica i file', + 'import_file_type_csv' => 'CSV (valori separati da virgola)', + 'file_not_utf8' => 'Il file che hai caricato non è codificato come UTF-8 o ASCII. Firefly III non può gestire tali file. Utilizzare Notepad++ o Sublime per convertire il file in UTF-8.', + 'job_config_uc_title' => 'Configurazione di importazione (2/4) - Impostazione di base dei file', + 'job_config_uc_text' => 'Per poter importare correttamente il tuo file, ti preghiamo di convalidare le opzioni di seguito.', + 'job_config_uc_header_help' => 'Seleziona questa casella se la prima riga del tuo file CSV sono i titoli delle colonne.', + 'job_config_uc_date_help' => 'Formato della data e ora nel tuo file. Segui il formato indicato in questa pagina. Il valore predefinito analizzerà le date che assomigliano a questa: :dateExample.', + 'job_config_uc_delimiter_help' => 'Scegli il delimitatore di campo che viene utilizzato nel file di ingresso. Se non si è sicuri, la virgola è l\'opzione più sicura.', + 'job_config_uc_account_help' => 'Se il tuo file NON contiene informazioni sui tuoi conti di attività, utilizza questo menu a discesa per selezionare a quale conto appartengono le transazioni nel file.', + 'job_config_uc_apply_rules_title' => 'Applica regole', + 'job_config_uc_apply_rules_text' => 'Applica le tue regole ad ogni transazione importata. Si noti che questo rallenta l\'importazione in modo significativo.', + 'job_config_uc_specifics_title' => 'Opzioni specifiche della banca', + 'job_config_uc_specifics_txt' => 'Alcune banche forniscono file formattati in modo errato. Firefly III può sistemarli automaticamente. Se la tua banca rende disponibili tali file ma non è elencata qui, ti preghiamo di segnalare il problema su GitHub.', + 'job_config_uc_submit' => 'Continua', + 'invalid_import_account' => 'Hai selezionato un conto non valido su cui effettuare l\'importazione.', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Scegli il tuo login', - 'job_config_spectre_login_text' => 'Firefly III ha rilevato :count login esistenti nel tuo account Spectre. Quale vorresti usare per l\'importazione?', - 'spectre_login_status_active' => 'Attivo', - 'spectre_login_status_inactive' => 'Inattivo', - '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 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' => '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_config_spectre_login_title' => 'Scegli il tuo login', + 'job_config_spectre_login_text' => 'Firefly III ha rilevato :count login esistenti nel tuo account Spectre. Quale vorresti usare per l\'importazione?', + 'spectre_login_status_active' => 'Attivo', + 'spectre_login_status_inactive' => 'Inattivo', + '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 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' => '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' => '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.', + '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' => '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Conto attività (IBAN)', 'column_account-id' => 'ID conto attività (mappa FF3)', 'column_account-name' => 'Conto attività (nome)', - 'column_account-bic' => 'Conto attività (BIC)', + 'column_account-bic' => 'Conto attività (BIC)', 'column_amount' => 'Importo', 'column_amount_foreign' => 'Importo (in altra valuta)', 'column_amount_debit' => 'Importo (colonna debito)', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index a00e4be5e2..500f0f75a9 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Voorpaginarekeningen', 'pref_home_screen_accounts_help' => 'Welke betaalrekeningen wil je op de voorpagina zien?', 'pref_view_range' => 'Bereik', - 'pref_view_range_help' => 'Sommige pagina\'s springen naar een standaard bereik. Welk bereik heeft jouw voorkeur?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Eén dag', 'pref_1W' => 'Eén week', 'pref_1M' => 'Eén maand', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Contant geldrekeningen', 'Cash account' => 'Contant geldrekening', 'reconcile_account' => 'Afstemmen betaalrekening ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Verwijder correctie', 'update_reconciliation' => 'Update correctie', 'amount_cannot_be_zero' => 'Het bedrag mag niet nul zijn', diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index f19ff53daa..48f11b02b0 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Herhalingen', 'calendar' => 'Kalender', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/nl_NL/import.php b/resources/lang/nl_NL/import.php index d68ff96f74..42f2761d49 100644 --- a/resources/lang/nl_NL/import.php +++ b/resources/lang/nl_NL/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Gegevens importeren in Firefly III', - 'prerequisites_breadcrumb_fake' => 'Vereisten voor de nep-importhulp', - 'prerequisites_breadcrumb_spectre' => 'Vereisten voor Spectre', - 'prerequisites_breadcrumb_bunq' => 'Vereisten voor bunq', - 'job_configuration_breadcrumb' => 'Instellingen voor ":key"', - 'job_status_breadcrumb' => 'Importstatus voor ":key"', - 'cannot_create_for_provider' => 'Firefly III kan niet importeren met behulp van ":provider".', + 'index_breadcrumb' => 'Gegevens importeren in Firefly III', + 'prerequisites_breadcrumb_fake' => 'Vereisten voor de nep-importhulp', + 'prerequisites_breadcrumb_spectre' => 'Vereisten voor Spectre', + 'prerequisites_breadcrumb_bunq' => 'Vereisten voor bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'job_configuration_breadcrumb' => 'Instellingen voor ":key"', + 'job_status_breadcrumb' => 'Importstatus voor ":key"', + 'cannot_create_for_provider' => 'Firefly III kan niet importeren met behulp van ":provider".', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - 'general_index_title' => 'Importeer een bestand', - 'general_index_intro' => 'Dit is de import-routine van Firefly III. Er zijn verschillende manieren om gegevens te importeren in Firefly III, hier als knoppen weergegeven.', + 'general_index_title' => 'Importeer een bestand', + 'general_index_intro' => 'Dit is de import-routine van Firefly III. Er zijn verschillende manieren om gegevens te importeren in Firefly III, hier als knoppen weergegeven.', // import provider strings (index): - 'button_fake' => 'Nepdata importeren', - 'button_file' => 'Importeer een bestand', - 'button_bunq' => 'Importeer uit bunq', - 'button_spectre' => 'Importeer via Spectre', - 'button_plaid' => 'Importeer via Plaid', - 'button_yodlee' => 'Importeer via Spectre', - 'button_quovo' => 'Importeer via Quovo', + 'button_fake' => 'Nepdata importeren', + 'button_file' => 'Importeer een bestand', + 'button_bunq' => 'Importeer uit bunq', + 'button_spectre' => 'Importeer via Spectre', + 'button_plaid' => 'Importeer via Plaid', + 'button_yodlee' => 'Importeer via Spectre', + 'button_quovo' => 'Importeer via Quovo', + 'button_ynab' => 'Import from You Need A Budget', // global config box (index) - 'global_config_title' => 'Configuratiebestand', - 'global_config_text' => 'In de toekomst bevat dit vak voorkeuren die van toepassing zijn op ALLE bovenstaande importproviders.', + 'global_config_title' => 'Configuratiebestand', + 'global_config_text' => 'In de toekomst bevat dit vak voorkeuren die van toepassing zijn op ALLE bovenstaande importproviders.', // prerequisites box (index) - 'need_prereq_title' => 'Importvereisten', - 'need_prereq_intro' => 'Sommige importmethoden hebben je aandacht nodig voor ze gebruikt kunnen worden. Ze vereisen bijvoorbeeld speciale API-sleutels of geheime waardes. Je kan ze hier instellen. Het icoontje geeft aan of deze vereisten al ingevuld zijn.', - 'do_prereq_fake' => 'Vereisten voor de nep-importhulp', - 'do_prereq_file' => 'Vereisten voor het importeren van bestanden', - 'do_prereq_bunq' => 'Vereisten voor een import van bunq', - 'do_prereq_spectre' => 'Vereisten voor een import via Spectre', - 'do_prereq_plaid' => 'Vereisten voor een import via Plaid', - 'do_prereq_yodlee' => 'Vereisten voor een import via Yodlee', - 'do_prereq_quovo' => 'Vereisten voor een import via Quovo', + 'need_prereq_title' => 'Importvereisten', + 'need_prereq_intro' => 'Sommige importmethoden hebben je aandacht nodig voor ze gebruikt kunnen worden. Ze vereisen bijvoorbeeld speciale API-sleutels of geheime waardes. Je kan ze hier instellen. Het icoontje geeft aan of deze vereisten al ingevuld zijn.', + 'do_prereq_fake' => 'Vereisten voor de nep-importhulp', + 'do_prereq_file' => 'Vereisten voor het importeren van bestanden', + 'do_prereq_bunq' => 'Vereisten voor een import van bunq', + 'do_prereq_spectre' => 'Vereisten voor een import via Spectre', + 'do_prereq_plaid' => 'Vereisten voor een import via Plaid', + 'do_prereq_yodlee' => 'Vereisten voor een import via Yodlee', + 'do_prereq_quovo' => 'Vereisten voor een import via Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Importinstellingen', - 'can_config_intro' => 'Sommige importmethodes kunnen ingesteld worden zoals jij dat wilt. Er zijn extra instellingen die je aan kan passen.', - 'do_config_fake' => 'Instellingen voor de nep-importhulp', - 'do_config_file' => 'Instellingen voor importeren van bestanden', - 'do_config_bunq' => 'Instellingen voor importeren uit bunq', - 'do_config_spectre' => 'Instellingen voor importeren uit Spectre', - 'do_config_plaid' => 'Instellingen voor importeren uit Plaid', - 'do_config_yodlee' => 'Instellingen voor importeren uit Yodlee', - 'do_config_quovo' => 'Instellingen voor importeren uit Quovo', + 'can_config_title' => 'Importinstellingen', + 'can_config_intro' => 'Sommige importmethodes kunnen ingesteld worden zoals jij dat wilt. Er zijn extra instellingen die je aan kan passen.', + 'do_config_fake' => 'Instellingen voor de nep-importhulp', + 'do_config_file' => 'Instellingen voor importeren van bestanden', + 'do_config_bunq' => 'Instellingen voor importeren uit bunq', + 'do_config_spectre' => 'Instellingen voor importeren uit Spectre', + 'do_config_plaid' => 'Instellingen voor importeren uit Plaid', + 'do_config_yodlee' => 'Instellingen voor importeren uit Yodlee', + 'do_config_quovo' => 'Instellingen voor importeren uit Quovo', // prerequisites: - 'prereq_fake_title' => 'Instellingen voor importeren uit de nep-importhulp', - 'prereq_fake_text' => 'Deze nep-provider heeft een neppe API key nodig. Deze moet 32 tekens lang zijn. Je mag deze gebruiken: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Vereisten voor een import via Spectre', - 'prereq_spectre_text' => 'Als je gegevens wilt importeren via de Spectre API (v4), moet je een aantal geheime codes bezitten. Ze zijn te vinden op de secrets pagina.', - 'prereq_spectre_pub' => 'De Spectre API moet de publieke sleutel kennen die je hieronder ziet. Zonder deze sleutel herkent Spectre je niet. Voer deze publieke sleutel in op je secrets-pagina.', - 'prereq_bunq_title' => 'Vereisten aan een import van bunq', - 'prereq_bunq_text' => 'Om te importeren vanaf bunq moet je een API key hebben. Deze kan je aanvragen in de app. Denk er aan dat deze functie in BETA is. De code is alleen getest op de sandbox API.', - 'prereq_bunq_ip' => 'bunq wilt graag je externe IP-adres weten. Firefly III heeft geprobeerd dit in te vullen met behulp van de ipify-dienst. Zorg dat je zeker weet dat dit IP-adres klopt, want anders zal de import niet werken.', + 'prereq_fake_title' => 'Instellingen voor importeren uit de nep-importhulp', + 'prereq_fake_text' => 'Deze nep-provider heeft een neppe API key nodig. Deze moet 32 tekens lang zijn. Je mag deze gebruiken: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Vereisten voor een import via Spectre', + 'prereq_spectre_text' => 'Als je gegevens wilt importeren via de Spectre API (v4), moet je een aantal geheime codes bezitten. Ze zijn te vinden op de secrets pagina.', + 'prereq_spectre_pub' => 'De Spectre API moet de publieke sleutel kennen die je hieronder ziet. Zonder deze sleutel herkent Spectre je niet. Voer deze publieke sleutel in op je secrets-pagina.', + 'prereq_bunq_title' => 'Vereisten aan een import van bunq', + 'prereq_bunq_text' => 'Om te importeren vanaf bunq moet je een API key hebben. Deze kan je aanvragen in de app. Denk er aan dat deze functie in BETA is. De code is alleen getest op de sandbox API.', + 'prereq_bunq_ip' => 'bunq wilt graag je externe IP-adres weten. Firefly III heeft geprobeerd dit in te vullen met behulp van de ipify-dienst. Zorg dat je zeker weet dat dit IP-adres klopt, want anders zal de import niet werken.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Nep API-sleutel is opgeslagen!', - 'prerequisites_saved_for_spectre' => 'APP ID en secret opgeslagen!', - 'prerequisites_saved_for_bunq' => 'API-sleutel en IP opgeslagen!', + 'prerequisites_saved_for_fake' => 'Nep API-sleutel is opgeslagen!', + 'prerequisites_saved_for_spectre' => 'APP ID en secret opgeslagen!', + 'prerequisites_saved_for_bunq' => 'API-sleutel en IP opgeslagen!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Importinstellingen - regels toepassen?', - 'job_config_apply_rules_text' => 'Als de nep-importhulp gedraaid heeft kunnen je regels worden toegepast op de transacties. Dit kost wel tijd.', - 'job_config_input' => 'Je invoer', + 'job_config_apply_rules_title' => 'Importinstellingen - regels toepassen?', + 'job_config_apply_rules_text' => 'Als de nep-importhulp gedraaid heeft kunnen je regels worden toegepast op de transacties. Dit kost wel tijd.', + 'job_config_input' => 'Je invoer', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Voer albumnaam in', - 'job_config_fake_artist_text' => 'Veel importroutines hebben een paar configuratiestappen die je moet doorlopen. In het geval van de nep-importhulp moet je een aantal rare vragen beantwoorden. Voer \'David Bowie\' in om door te gaan.', - 'job_config_fake_song_title' => 'Naam van nummer', - 'job_config_fake_song_text' => 'Noem het nummer "Golden years" om door te gaan met de nep import.', - 'job_config_fake_album_title' => 'Albumnaam invoeren', - 'job_config_fake_album_text' => 'Sommige importroutines vereisen extra gegevens halverwege de import. In het geval van de nep-importhulp moet je een aantal rare vragen beantwoorden. Voer "Station naar station" in om door te gaan.', + 'job_config_fake_artist_title' => 'Voer albumnaam in', + 'job_config_fake_artist_text' => 'Veel importroutines hebben een paar configuratiestappen die je moet doorlopen. In het geval van de nep-importhulp moet je een aantal rare vragen beantwoorden. Voer \'David Bowie\' in om door te gaan.', + 'job_config_fake_song_title' => 'Naam van nummer', + 'job_config_fake_song_text' => 'Noem het nummer "Golden years" om door te gaan met de nep import.', + 'job_config_fake_album_title' => 'Albumnaam invoeren', + 'job_config_fake_album_text' => 'Sommige importroutines vereisen extra gegevens halverwege de import. In het geval van de nep-importhulp moet je een aantal rare vragen beantwoorden. Voer "Station naar station" in om door te gaan.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Importinstellingen (1/4) - Upload je bestand', - 'job_config_file_upload_text' => 'Met deze routine kan je bestanden van je bank importeren in Firefly III. ', - 'job_config_file_upload_help' => 'Selecteer je bestand. Zorg ervoor dat het bestand UTF-8 gecodeerd is.', - 'job_config_file_upload_config_help' => 'Als je eerder gegevens hebt geïmporteerd in Firefly III, heb je wellicht een configuratiebestand, dat een aantal zaken alvast voor je kan instellen. Voor bepaalde banken hebben andere gebruikers uit de liefde van hun hart het benodigde configuratiebestand gedeeld', - 'job_config_file_upload_type_help' => 'Selecteer het type bestand dat je zal uploaden', - 'job_config_file_upload_submit' => 'Bestanden uploaden', - 'import_file_type_csv' => 'CSV (kommagescheiden waardes)', - 'file_not_utf8' => 'Het bestand dat je hebt geüpload, is niet gecodeerd als UTF-8 of ASCII. Firefly III kan dergelijke bestanden niet verwerken. Gebruik Notepad ++ of Sublime om je bestand naar UTF-8 te converteren.', - 'job_config_uc_title' => 'Importinstellingen (2/4) - Algemene importinstellingen', - 'job_config_uc_text' => 'Om je bestand goed te kunnen importeren moet je deze opties verifiëren.', - 'job_config_uc_header_help' => 'Vink hier als de eerste rij kolomtitels bevat.', - 'job_config_uc_date_help' => 'Datum/tijd formaat in jouw bestand. Volg het formaat zoals ze het op deze pagina uitleggen. Het standaardformaat ziet er zo uit: :dateExample.', - 'job_config_uc_delimiter_help' => 'Kies het veldscheidingsteken dat in jouw bestand wordt gebruikt. Als je het niet zeker weet, is de komma de beste optie.', - 'job_config_uc_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw betaalrekening(en), geef dan hier aan om welke rekening het gaat.', - 'job_config_uc_apply_rules_title' => 'Regels toepassen', - 'job_config_uc_apply_rules_text' => 'Past je regels toe op elke geïmporteerde transactie. Merk op dat dit de import aanzienlijk vertraagt.', - 'job_config_uc_specifics_title' => 'Bank-specifieke opties', - 'job_config_uc_specifics_txt' => 'Sommige banken leveren slecht geformatteerde bestanden aan. Firefly III kan deze automatisch corrigeren. Als jouw bank dergelijke bestanden levert, maar deze hier niet wordt vermeld, open dan een issue op GitHub.', - 'job_config_uc_submit' => 'Volgende', - 'invalid_import_account' => 'Je hebt een ongeldige betaalrekening geselecteerd om in te importeren.', + 'job_config_file_upload_title' => 'Importinstellingen (1/4) - Upload je bestand', + 'job_config_file_upload_text' => 'Met deze routine kan je bestanden van je bank importeren in Firefly III. ', + 'job_config_file_upload_help' => 'Selecteer je bestand. Zorg ervoor dat het bestand UTF-8 gecodeerd is.', + 'job_config_file_upload_config_help' => 'Als je eerder gegevens hebt geïmporteerd in Firefly III, heb je wellicht een configuratiebestand, dat een aantal zaken alvast voor je kan instellen. Voor bepaalde banken hebben andere gebruikers uit de liefde van hun hart het benodigde configuratiebestand gedeeld', + 'job_config_file_upload_type_help' => 'Selecteer het type bestand dat je zal uploaden', + 'job_config_file_upload_submit' => 'Bestanden uploaden', + 'import_file_type_csv' => 'CSV (kommagescheiden waardes)', + 'file_not_utf8' => 'Het bestand dat je hebt geüpload, is niet gecodeerd als UTF-8 of ASCII. Firefly III kan dergelijke bestanden niet verwerken. Gebruik Notepad ++ of Sublime om je bestand naar UTF-8 te converteren.', + 'job_config_uc_title' => 'Importinstellingen (2/4) - Algemene importinstellingen', + 'job_config_uc_text' => 'Om je bestand goed te kunnen importeren moet je deze opties verifiëren.', + 'job_config_uc_header_help' => 'Vink hier als de eerste rij kolomtitels bevat.', + 'job_config_uc_date_help' => 'Datum/tijd formaat in jouw bestand. Volg het formaat zoals ze het op deze pagina uitleggen. Het standaardformaat ziet er zo uit: :dateExample.', + 'job_config_uc_delimiter_help' => 'Kies het veldscheidingsteken dat in jouw bestand wordt gebruikt. Als je het niet zeker weet, is de komma de beste optie.', + 'job_config_uc_account_help' => 'Als jouw CSV bestand geen referenties bevat naar jouw betaalrekening(en), geef dan hier aan om welke rekening het gaat.', + 'job_config_uc_apply_rules_title' => 'Regels toepassen', + 'job_config_uc_apply_rules_text' => 'Past je regels toe op elke geïmporteerde transactie. Merk op dat dit de import aanzienlijk vertraagt.', + 'job_config_uc_specifics_title' => 'Bank-specifieke opties', + 'job_config_uc_specifics_txt' => 'Sommige banken leveren slecht geformatteerde bestanden aan. Firefly III kan deze automatisch corrigeren. Als jouw bank dergelijke bestanden levert, maar deze hier niet wordt vermeld, open dan een issue op GitHub.', + 'job_config_uc_submit' => 'Volgende', + 'invalid_import_account' => 'Je hebt een ongeldige betaalrekening geselecteerd om in te importeren.', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Kies je login', - 'job_config_spectre_login_text' => 'Firefly III heeft :count bestaande login(s) gevonden in je Spectre-account. Welke wil je gebruiken om van te importeren?', - 'spectre_login_status_active' => 'Actief', - 'spectre_login_status_inactive' => 'Inactief', - 'spectre_login_status_disabled' => 'Uitgeschakeld', - 'spectre_login_new_login' => 'Log in via een andere bank, of via een van deze banken met andere inloggegevens.', - 'job_config_spectre_accounts_title' => 'Selecteer de rekeningen waaruit je wilt importeren', - 'job_config_spectre_accounts_text' => 'Je hebt ":name" (:country) geselecteerd. Je hebt :count rekening(en) bij deze provider. Kies de Firefly III betaalrekening(en) waar je de transacties in wilt opslaan. Denk er aan dat zowel de ":name"-rekeningen als de Firefly III rekeningen dezelfde valuta moeten hebben.', - 'spectre_no_supported_accounts' => 'Je kan niet importeren van deze rekening omdat de valuta niet overeen komt.', - 'spectre_do_not_import' => '(niet importeren)', - '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_config_spectre_login_title' => 'Kies je login', + 'job_config_spectre_login_text' => 'Firefly III heeft :count bestaande login(s) gevonden in je Spectre-account. Welke wil je gebruiken om van te importeren?', + 'spectre_login_status_active' => 'Actief', + 'spectre_login_status_inactive' => 'Inactief', + 'spectre_login_status_disabled' => 'Uitgeschakeld', + 'spectre_login_new_login' => 'Log in via een andere bank, of via een van deze banken met andere inloggegevens.', + 'job_config_spectre_accounts_title' => 'Selecteer de rekeningen waaruit je wilt importeren', + 'job_config_spectre_accounts_text' => 'Je hebt ":name" (:country) geselecteerd. Je hebt :count rekening(en) bij deze provider. Kies de Firefly III betaalrekening(en) waar je de transacties in wilt opslaan. Denk er aan dat zowel de ":name"-rekeningen als de Firefly III rekeningen dezelfde valuta moeten hebben.', + 'spectre_no_supported_accounts' => 'Je kan niet importeren van deze rekening omdat de valuta niet overeen komt.', + 'spectre_do_not_import' => '(niet importeren)', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Betaalrekening (IBAN)', 'column_account-id' => 'Betaalrekening (ID gelijk aan FF3)', 'column_account-name' => 'Betaalrekeningnaam', - 'column_account-bic' => 'Betaalrekening (BIC)', + 'column_account-bic' => 'Betaalrekening (BIC)', 'column_amount' => 'Bedrag', 'column_amount_foreign' => 'Bedrag (in vreemde valuta)', 'column_amount_debit' => 'Bedrag (debetkolom)', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 428c7b4372..7c5687f66e 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Konta na stronie domowej', 'pref_home_screen_accounts_help' => 'Które konta powinny być wyświetlane na stronie głównej?', 'pref_view_range' => 'Zakres widzenia', - 'pref_view_range_help' => 'Niektóre wykresy są automatycznie grupowane w okresach. Jaki okres wolisz?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Dzień', 'pref_1W' => 'Tydzień', 'pref_1M' => 'Miesiąc', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Konta gotówkowe', 'Cash account' => 'Konto gotówkowe', 'reconcile_account' => 'Uzgodnij konto ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Usuń uzgodnienie', 'update_reconciliation' => 'Zaktualizuj uzgodnienie', 'amount_cannot_be_zero' => 'Kwota nie może wynosić zero', diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index 8e14752274..43aca5551b 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Powtórzenia', 'calendar' => 'Kalendarz', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/pl_PL/import.php b/resources/lang/pl_PL/import.php index 978e9f65c0..f79f6959ef 100644 --- a/resources/lang/pl_PL/import.php +++ b/resources/lang/pl_PL/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - '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', - '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' => '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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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.', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - 'general_index_title' => 'Importuj plik', - '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' => 'Importuj plik', + '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' => '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', + 'button_fake' => 'Fake an import', + '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', + 'button_ynab' => 'Import from You Need A Budget', // global config box (index) - 'global_config_title' => 'Globalna konfiguracja importu', - 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + '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' => '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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Wybierz typ pliku, który będziesz przesyłać', - 'job_config_file_upload_submit' => 'Prześlij pliki', - 'import_file_type_csv' => 'CSV (wartości oddzielone przecinkami)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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' => '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' => 'Kontynuuj', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Wybierz typ pliku, który będziesz przesyłać', + 'job_config_file_upload_submit' => 'Prześlij pliki', + 'import_file_type_csv' => 'CSV (wartości oddzielone przecinkami)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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' => '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' => 'Kontynuuj', + 'invalid_import_account' => 'You have selected an invalid account to import into.', // job configuration for Spectre: - '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' => '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' => '(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' => 'Zastosuj reguły', - '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_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' => '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' => '(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' => 'Zastosuj reguły', + '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' => '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' => 'Zastosuj reguły', - '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_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' => 'Zastosuj reguły', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Konto aktywów (IBAN)', 'column_account-id' => 'ID konta aktywów (z bazy FF3)', 'column_account-name' => 'Konto aktywów (nazwa)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Kwota', 'column_amount_foreign' => 'Kwota (w obcej walucie)', 'column_amount_debit' => 'Kwota (kolumna debetowa)', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 60e987e3fa..96b0815825 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Conta da tela inicial', 'pref_home_screen_accounts_help' => 'Que conta deve ser exibida na tela inicial?', 'pref_view_range' => 'Ver intervalo', - 'pref_view_range_help' => 'Alguns gráficos são agrupados automaticamente em períodos. Qual período você prefere?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Um dia', 'pref_1W' => 'Uma semana', 'pref_1M' => 'Um mês', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Contas Correntes', 'Cash account' => 'Conta Corrente', 'reconcile_account' => 'Reconciliar conta ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Eliminar reconciliação', 'update_reconciliation' => 'Atualizar a reconciliação', 'amount_cannot_be_zero' => 'O valor não pode ser zero', diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index 1061a841a0..b3ed2eb0f0 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/pt_BR/import.php b/resources/lang/pt_BR/import.php index 67c3bf11e9..963475b00b 100644 --- a/resources/lang/pt_BR/import.php +++ b/resources/lang/pt_BR/import.php @@ -24,119 +24,154 @@ 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' => '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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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.', + 'disabled_for_demo_user' => 'disabled in demo', // 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' => '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.', // 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' => '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_ynab' => 'Import from You Need A Budget', // 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' => 'Global import configuration', + '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_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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Import configuration', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Upload files', - 'import_file_type_csv' => 'CSV (valores separados por vírgula)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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_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', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (valores separados por vírgula)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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_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', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Conta de Ativo (IBAN)', 'column_account-id' => 'ID da Conta de Ativo (correspondente FF3)', 'column_account-name' => 'Conta de Ativo (nome)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Montante', 'column_amount_foreign' => 'Montante (em moeda estrangeira)', 'column_amount_debit' => 'Montante (coluna de débito)', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index de71fae926..f5292f8f7d 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -443,7 +443,7 @@ return [ 'pref_home_screen_accounts' => 'Счета, отображаемые в сводке', 'pref_home_screen_accounts_help' => 'Какие счета нужно отображать в сводке на главной странице?', 'pref_view_range' => 'Диапазон просмотра', - 'pref_view_range_help' => 'Некоторые диаграммы автоматически группируются по периодам. Какой период вы предпочитаете?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Один день', 'pref_1W' => 'Одна неделя', 'pref_1M' => 'Один месяц', @@ -701,6 +701,7 @@ return [ 'cash_accounts' => 'Наличные деньги', 'Cash account' => 'Наличные деньги', 'reconcile_account' => 'Сверка счёта ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Удалить сверку', 'update_reconciliation' => 'Обновить сверку', 'amount_cannot_be_zero' => 'Сумма не может быть равна нулю', diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index 625439e845..ce357ba026 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Повторения', 'calendar' => 'Календарь', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/ru_RU/import.php b/resources/lang/ru_RU/import.php index 5ac24b037f..0b20a2fad6 100644 --- a/resources/lang/ru_RU/import.php +++ b/resources/lang/ru_RU/import.php @@ -24,119 +24,154 @@ declare(strict_types=1); return [ // ALL breadcrumbs and subtitles: - 'index_breadcrumb' => 'Импорт данных в 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' => 'Конфигурация для ":key"', - 'job_status_breadcrumb' => 'Статус импорта для ":key"', - 'cannot_create_for_provider' => 'Firefly III не может создать задачу для ":provider"-провайдера.', + 'index_breadcrumb' => 'Импорт данных в Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'job_configuration_breadcrumb' => 'Конфигурация для ":key"', + 'job_status_breadcrumb' => 'Статус импорта для ":key"', + 'cannot_create_for_provider' => 'Firefly III не может создать задачу для ":provider"-провайдера.', + 'disabled_for_demo_user' => 'disabled in demo', // index page: - 'general_index_title' => 'Импортировать файл', - 'general_index_intro' => 'Добро пожаловать в инструмент импорта Firefly III. Существует несколько способов импорта данных в Firefly III, отображаемых здесь в виде кнопок.', + 'general_index_title' => 'Импортировать файл', + 'general_index_intro' => 'Добро пожаловать в инструмент импорта Firefly III. Существует несколько способов импорта данных в Firefly III, отображаемых здесь в виде кнопок.', // import provider strings (index): - 'button_fake' => 'Поддельный (демо) импорт', - 'button_file' => 'Импортировать файл', - 'button_bunq' => 'Импорт из bunq', - 'button_spectre' => 'Импорт с использованием Spectre', - 'button_plaid' => 'Импорт с использованием Plaid', - 'button_yodlee' => 'Импорт с использованием Yodlee', - 'button_quovo' => 'Импорт с использованием Quovo', + 'button_fake' => 'Поддельный (демо) импорт', + 'button_file' => 'Импортировать файл', + 'button_bunq' => 'Импорт из bunq', + 'button_spectre' => 'Импорт с использованием Spectre', + 'button_plaid' => 'Импорт с использованием Plaid', + 'button_yodlee' => 'Импорт с использованием Yodlee', + 'button_quovo' => 'Импорт с использованием Quovo', + 'button_ynab' => 'Import from You Need A Budget', // global config box (index) - 'global_config_title' => 'Глобальные настройки импорта', - 'global_config_text' => 'In the future, this box will feature preferences that apply to ALL import providers above.', + 'global_config_title' => 'Глобальные настройки импорта', + '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_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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Импорт конфигурации', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Конфигурация для импорта из Spectre', - 'do_config_plaid' => 'Конфигурация для импорта из Plaid', - 'do_config_yodlee' => 'Конфигурация для импорта из Yodlee', - 'do_config_quovo' => 'Конфигурация для импорта из Quovo', + 'can_config_title' => 'Импорт конфигурации', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Конфигурация для импорта из Spectre', + 'do_config_plaid' => 'Конфигурация для импорта из Plaid', + 'do_config_yodlee' => 'Конфигурация для импорта из Yodlee', + 'do_config_quovo' => 'Конфигурация для импорта из Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Ключ Fake API успешно сохранен!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_fake' => 'Ключ Fake API успешно сохранен!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Загрузить файлы', - 'import_file_type_csv' => 'CSV (значения, разделенные запятыми)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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_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' => 'Продолжить', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Загрузить файлы', + 'import_file_type_csv' => 'CSV (значения, разделенные запятыми)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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_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' => 'Продолжить', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Счет актива (IBAN)', 'column_account-id' => 'ID основного счёта (соответствующий FF3)', 'column_account-name' => 'Основной счёт (название)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Сумма', 'column_amount_foreign' => 'Сумма (в иностранной валюте)', 'column_amount_debit' => 'Сумма (столбец с дебетом)', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 95757768cf..6e26744d38 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -446,7 +446,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'pref_home_screen_accounts' => 'Ana ekran hesapları', 'pref_home_screen_accounts_help' => 'Giriş sayfasında hangi hesaplar görüntülensin?', 'pref_view_range' => 'Görüş Mesafesi', - 'pref_view_range_help' => 'Bazı dönemlerde grafikler otomatik olarak gruplandırılır. Hangi dönemi tercih etmek istersiniz?', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', 'pref_1D' => 'Bir gün', 'pref_1W' => 'Bir hafta', 'pref_1M' => 'Bir ay', @@ -704,6 +704,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'cash_accounts' => 'Nakit Hesabı', 'Cash account' => 'Nakit Hesabı', 'reconcile_account' => 'Hesabı ":account" dengeleyin', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Mutabakatı sil', 'update_reconciliation' => 'Mutabakatı güncelle', 'amount_cannot_be_zero' => 'Miktar sıfır olamaz', diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index afb1492e96..8cf94ad9c0 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -237,5 +237,6 @@ return [ 'repetitions' => 'Repetitions', 'calendar' => 'Calendar', 'weekend' => 'Weekend', + 'client_secret' => 'Client secret', ]; diff --git a/resources/lang/tr_TR/import.php b/resources/lang/tr_TR/import.php index e590a661ba..7ab8cc4a96 100644 --- a/resources/lang/tr_TR/import.php +++ b/resources/lang/tr_TR/import.php @@ -24,119 +24,154 @@ 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' => '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', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + '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.', + 'disabled_for_demo_user' => 'disabled in demo', // 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' => '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.', // 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' => '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_ynab' => 'Import from You Need A Budget', // 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' => 'Global import configuration', + '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_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', - 'do_prereq_bunq' => 'Prerequisites for imports from bunq', - 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', - 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', - 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', - 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + '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', + 'do_prereq_bunq' => 'Prerequisites for imports from bunq', + 'do_prereq_spectre' => 'Prerequisites for imports using Spectre', + 'do_prereq_plaid' => 'Prerequisites for imports using Plaid', + 'do_prereq_yodlee' => 'Prerequisites for imports using Yodlee', + 'do_prereq_quovo' => 'Prerequisites for imports using Quovo', + 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + // provider config box (index) - 'can_config_title' => 'Import configuration', - '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', - 'do_config_bunq' => 'Configuration for bunq imports', - 'do_config_spectre' => 'Configuration for imports from Spectre', - 'do_config_plaid' => 'Configuration for imports from Plaid', - 'do_config_yodlee' => 'Configuration for imports from Yodlee', - 'do_config_quovo' => 'Configuration for imports from Quovo', + 'can_config_title' => 'Import configuration', + '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', + 'do_config_bunq' => 'Configuration for bunq imports', + 'do_config_spectre' => 'Configuration for imports from Spectre', + 'do_config_plaid' => 'Configuration for imports from Plaid', + 'do_config_yodlee' => 'Configuration for imports from Yodlee', + 'do_config_quovo' => 'Configuration for imports from Quovo', // prerequisites: - 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', - 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', - 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', - 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', - 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', - 'prereq_bunq_title' => 'Prerequisites for an import from bunq', - 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', - 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Prerequisites for an import from bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', + 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', + 'prerequisites_saved_for_bunq' => 'API key and IP stored!', + 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'Your input', + 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', + 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', + 'job_config_input' => 'Your input', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Enter album name', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Enter song name', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Enter album name', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Enter album name', + 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', + 'job_config_fake_song_title' => 'Enter song name', + 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', + 'job_config_fake_album_title' => 'Enter album name', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Upload files', - 'import_file_type_csv' => 'CSV (virgülle ayrılmış değerler)', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - '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_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', - 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', + 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', + 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Select the type of file you will upload', + 'job_config_file_upload_submit' => 'Upload files', + 'import_file_type_csv' => 'CSV (virgülle ayrılmış değerler)', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', + 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + '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_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', + '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_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_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', - '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_login_title' => 'Choose your 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_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', + '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.', + '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.', + + 'ynab_account_closed' => 'Account is closed!', + 'ynab_account_deleted' => 'Account is deleted!', + 'ynab_account_type_savings' => 'savings account', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'cash account', + 'ynab_account_type_creditCard' => 'credit card', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'other liabilities', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investment account', + 'ynab_account_type_mortgage' => 'mortgage', + 'ynab_do_not_import' => '(do not import)', + 'job_config_ynab_apply_rules' => 'Apply rules', + 'job_config_ynab_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 YNAB: + 'job_config_ynab_select_budgets' => 'Select your budget', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', @@ -219,7 +254,7 @@ return [ 'column_account-iban' => 'Öğe hesabı (IBAN)', 'column_account-id' => 'Asset account ID (matching FF3)', 'column_account-name' => 'Varlık hesabı (isim)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Asset account (BIC)', 'column_amount' => 'Tutar', 'column_amount_foreign' => 'Amount (in foreign currency)', 'column_amount_debit' => 'Miktar (borç sütunu)', From c1ac2bb156dd83e4517b340006221275aa2f80a9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 05:49:03 +0200 Subject: [PATCH 010/166] Expand text and routine for YNAB --- .../Ynab/SelectBudgetHandler.php | 69 +++++++++++++++++-- resources/lang/en_US/import.php | 3 + resources/lang/en_US/list.php | 1 + .../views/import/ynab/select-budgets.twig | 25 ++++++- 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php index 244eba2b5b..a65d7be089 100644 --- a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php +++ b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php @@ -23,8 +23,13 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\JobConfiguration\Ynab; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use Log; @@ -33,6 +38,12 @@ use Log; */ class SelectBudgetHandler implements YnabJobConfigurationInterface { + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var Collection */ + private $accounts; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; /** @var ImportJob */ private $importJob; /** @var ImportJobRepositoryInterface */ @@ -51,6 +62,7 @@ class SelectBudgetHandler implements YnabJobConfigurationInterface if ($selectedBudget !== '') { Log::debug(sprintf('Selected budget is %s, config is complete. Return true.', $selectedBudget)); $this->repository->setStage($this->importJob, 'get_accounts'); + return true; } Log::debug('User has not selected a budget yet, config is not yet complete.'); @@ -90,13 +102,24 @@ class SelectBudgetHandler implements YnabJobConfigurationInterface Log::debug('Now in SelectBudgetHandler::getNextData'); $configuration = $this->repository->getConfiguration($this->importJob); $budgets = $configuration['budgets'] ?? []; - $return = []; + $available = []; + $notAvailable = []; + $total = \count($budgets); foreach ($budgets as $budget) { - $return[$budget['id']] = $budget['name'] . ' (' . $budget['currency_code'] . ')'; + if ($this->haveAssetWithCurrency($budget['currency_code'])) { + Log::debug('Add budget to available list.'); + $available[$budget['id']] = $budget['name'] . ' (' . $budget['currency_code'] . ')'; + continue; + } + Log::debug('Add budget to notAvailable list.'); + $notAvailable[$budget['id']] = $budget['name'] . ' (' . $budget['currency_code'] . ')'; + } return [ - 'budgets' => $return, + 'available' => $available, + 'not_available' => $notAvailable, + 'total' => $total, ]; } @@ -119,8 +142,44 @@ class SelectBudgetHandler implements YnabJobConfigurationInterface */ public function setImportJob(ImportJob $importJob): void { - $this->importJob = $importJob; - $this->repository = app(ImportJobRepositoryInterface::class); + $this->importJob = $importJob; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->repository->setUser($importJob->user); + $this->currencyRepository->setUser($importJob->user); + $this->accountRepository->setUser($importJob->user); + $this->accountRepository->setUser($importJob->user); + + $this->accounts = $this->accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); + } + + /** + * @param string $code + * + * @return bool + */ + private function haveAssetWithCurrency(string $code): bool + { + $currency = $this->currencyRepository->findByCodeNull($code); + if (null === $currency) { + Log::debug(sprintf('No currency found with code "%s"', $code)); + + return false; + } + /** @var Account $account */ + foreach ($this->accounts as $account) { + $currencyId = (int)$this->accountRepository->getMetaValue($account, 'currency_id'); + Log::debug(sprintf('Currency of %s is %d (looking for %d).', $account->name, $currencyId, $currency->id)); + if ($currencyId === $currency->id) { + Log::debug('Return true!'); + + return true; + } + } + Log::debug('Found nothing, return false.'); + + return false; } } \ No newline at end of file diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 1788206605..524135901c 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -171,6 +171,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index 8932fdf83a..bc36c21a41 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Sum of transfers', 'reconcile' => 'Reconcile', 'account_on_spectre' => 'Account (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Import from this account', 'sepa-ct-id' => 'SEPA End to End Identifier', 'sepa-ct-op' => 'SEPA Opposing Account Identifier', diff --git a/resources/views/import/ynab/select-budgets.twig b/resources/views/import/ynab/select-budgets.twig index 1ab9b8efbc..1c6c37b8fd 100644 --- a/resources/views/import/ynab/select-budgets.twig +++ b/resources/views/import/ynab/select-budgets.twig @@ -11,21 +11,40 @@
-

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

+

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

- {{ trans('import.job_config_spectre_select_budgets_text', {count: data.budgets|length}) }} + {{ trans('import.job_config_ynab_select_budgets_text', {count: data.total}) }}

- {{ ExpandedForm.select('budget_id', data.budgets) }} + {% if data.available|length == 0 %} +

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

+ {% else %} + {{ ExpandedForm.select('budget_id', data.available) }} + {% endif %} + + {% if data.not_available|length > 0 %} +

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

+
    + {% for budget in data.not_available %} +
  • {{ budget }}
  • + {% endfor %} +
+ {% endif %}
From 56518ea0282622afc64fd9679cbc08c1529f7645 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 18:19:48 +0200 Subject: [PATCH 011/166] First working version of YNAB import #145 --- .../Prerequisites/YnabPrerequisites.php | 3 +- app/Import/Routine/YnabRoutine.php | 5 +- app/Import/Storage/ImportArrayStorage.php | 51 ++++++++++++++++ .../Ynab/Request/GetAccountsRequest.php | 1 + .../Ynab/Request/GetBudgetsRequest.php | 1 + .../Ynab/Request/GetTransactionsRequest.php | 1 + .../Import/Routine/Ynab/ImportDataHandler.php | 58 ++++++++++++++----- resources/lang/en_US/firefly.php | 1 + resources/lang/en_US/import.php | 5 +- .../views/import/ynab/prerequisites.twig | 16 +++++ 10 files changed, 121 insertions(+), 21 deletions(-) diff --git a/app/Import/Prerequisites/YnabPrerequisites.php b/app/Import/Prerequisites/YnabPrerequisites.php index 2079cdd21d..187d188199 100644 --- a/app/Import/Prerequisites/YnabPrerequisites.php +++ b/app/Import/Prerequisites/YnabPrerequisites.php @@ -63,8 +63,9 @@ class YnabPrerequisites implements PrerequisitesInterface } $callBackUri = route('import.callback.ynab'); + $isHttps = 0 === strpos($callBackUri, 'https://'); - return ['client_id' => $clientId, 'client_secret' => $clientSecret, 'callback_uri' => $callBackUri]; + return ['client_id' => $clientId, 'client_secret' => $clientSecret, 'callback_uri' => $callBackUri, 'is_https' => $isHttps]; } /** diff --git a/app/Import/Routine/YnabRoutine.php b/app/Import/Routine/YnabRoutine.php index 38c0d791c4..7a7fe731b6 100644 --- a/app/Import/Routine/YnabRoutine.php +++ b/app/Import/Routine/YnabRoutine.php @@ -83,14 +83,15 @@ class YnabRoutine implements RoutineInterface $budgets = $configuration['budgets'] ?? []; // if more than 1 budget, select budget first. - if (\count($budgets) > 0) { // TODO should be 1 + if (\count($budgets) > 1) { $this->repository->setStage($this->importJob, 'select_budgets'); $this->repository->setStatus($this->importJob, 'need_job_config'); return; } if (\count($budgets) === 1) { - $this->repository->setStage($this->importJob, 'match_accounts'); + $this->repository->setStatus($this->importJob, 'ready_to_run'); + $this->repository->setStage($this->importJob, 'get_accounts'); } return; diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 19868ec17e..8d6e4742de 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -29,6 +29,8 @@ use DB; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Filter\NegativeAmountFilter; +use FireflyIII\Helpers\Filter\PositiveAmountFilter; use FireflyIII\Models\ImportJob; use FireflyIII\Models\Rule; use FireflyIII\Models\Transaction; @@ -212,6 +214,35 @@ class ImportArrayStorage return $set; } + /** + * @param $journal + * + * @return Transaction + */ + private function getTransactionFromJournal($journal): Transaction + { + // collect transactions using the journal collector + $collector = app(JournalCollectorInterface::class); + $collector->setUser(auth()->user()); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + // filter on specific journals. + $collector->setJournals(new Collection([$journal])); + + // add filter to remove transactions: + $transactionType = $journal->transactionType->type; + if ($transactionType === TransactionType::WITHDRAWAL) { + $collector->addFilter(PositiveAmountFilter::class); + } + if (!($transactionType === TransactionType::WITHDRAWAL)) { + $collector->addFilter(NegativeAmountFilter::class); + } + /** @var Transaction $result */ + $result = $collector->getJournals()->first(); + Log::debug(sprintf('Return transaction #%d with journal id #%d based on ID #%d', $result->id, $result->journal_id, $journal->id)); + + return $result; + } + /** * Get the users transfers, so they can be compared to whatever the user is trying to import. */ @@ -414,6 +445,18 @@ class ImportArrayStorage continue; } + // do transfer detection again! + if ($this->checkForTransfers && $this->transferExists($store)) { + $this->logDuplicateTransfer($store); + $this->repository->addErrorMessage( + $this->importJob, sprintf( + 'Row #%d ("%s") could not be imported. Such a transfer already exists.', + $index, + $store['description'] + ) + ); + continue; + } Log::debug(sprintf('Going to store entry %d of %d', $index + 1, $count)); // convert the date to an object: @@ -430,6 +473,14 @@ class ImportArrayStorage } Log::debug(sprintf('Stored as journal #%d', $journal->id)); $collection->push($journal); + + // add to collection of transfers, if necessary: + if ('transfer' === $store['type']) { + $transaction = $this->getTransactionFromJournal($journal); + Log::debug('We just stored a transfer, so add the journal to the list of transfers.'); + $this->transfers->push($transaction); + Log::debug(sprintf('List length is now %d', $this->transfers->count())); + } } Log::debug('DONE storing!'); diff --git a/app/Services/Ynab/Request/GetAccountsRequest.php b/app/Services/Ynab/Request/GetAccountsRequest.php index 25e15bdf43..c6530c7c18 100644 --- a/app/Services/Ynab/Request/GetAccountsRequest.php +++ b/app/Services/Ynab/Request/GetAccountsRequest.php @@ -46,6 +46,7 @@ class GetAccountsRequest extends YnabRequest Log::debug(sprintf('URI is %s', $uri)); $result = $this->authenticatedGetRequest($uri, []); + //Log::debug('Raw GetAccountsRequest result', $result); // expect data in [data][accounts] $this->accounts = $result['data']['accounts'] ?? []; diff --git a/app/Services/Ynab/Request/GetBudgetsRequest.php b/app/Services/Ynab/Request/GetBudgetsRequest.php index cf7948f319..7ea211c042 100644 --- a/app/Services/Ynab/Request/GetBudgetsRequest.php +++ b/app/Services/Ynab/Request/GetBudgetsRequest.php @@ -50,6 +50,7 @@ class GetBudgetsRequest extends YnabRequest Log::debug(sprintf('URI is %s', $uri)); $result = $this->authenticatedGetRequest($uri, []); + //Log::debug('Raw GetBudgetsRequest result', $result); // expect data in [data][budgets] $rawBudgets = $result['data']['budgets'] ?? []; diff --git a/app/Services/Ynab/Request/GetTransactionsRequest.php b/app/Services/Ynab/Request/GetTransactionsRequest.php index 72d75929ef..8e8c9c5b55 100644 --- a/app/Services/Ynab/Request/GetTransactionsRequest.php +++ b/app/Services/Ynab/Request/GetTransactionsRequest.php @@ -50,6 +50,7 @@ class GetTransactionsRequest extends YnabRequest Log::debug(sprintf('URI is %s', $uri)); $result = $this->authenticatedGetRequest($uri, []); + //Log::debug('Raw GetTransactionsRequest result', $result); // expect data in [data][transactions] $this->transactions = $result['data']['transactions'] ?? []; diff --git a/app/Support/Import/Routine/Ynab/ImportDataHandler.php b/app/Support/Import/Routine/Ynab/ImportDataHandler.php index 9b90d510a3..f8edcac2e2 100644 --- a/app/Support/Import/Routine/Ynab/ImportDataHandler.php +++ b/app/Support/Import/Routine/Ynab/ImportDataHandler.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Routine\Ynab; +use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; @@ -68,13 +69,20 @@ class ImportDataHandler foreach ($mapping as $ynabId => $localId) { $localAccount = $this->getLocalAccount((int)$localId); $transactions = $this->getTransactions($token, $ynabId); - $converted = $this->convertToArray($transactions, $localAccount); - $total[] = $converted; + $converted = $this->convertToArray($transactions, $localAccount); + $total[] = $converted; } $totalSet = array_merge(...$total); Log::debug(sprintf('Found %d transactions in total.', \count($totalSet))); $this->repository->setTransactions($this->importJob, $totalSet); + + // assuming this works, store today's date as a preference + // (combined with the budget from which FF3 imported) + $budgetId = $this->getSelectedBudget()['id'] ?? ''; + if ('' !== $budgetId) { + app('preferences')->set('ynab_' . $budgetId, Carbon::now()->format('Y-m-d')); + } } /** @@ -100,24 +108,38 @@ class ImportDataHandler */ private function convertToArray(array $transactions, Account $localAccount): array { - $array = []; - $total = \count($transactions); - $budget = $this->getSelectedBudget(); + $config = $this->repository->getConfiguration($this->importJob); + $array = []; + $total = \count($transactions); + $budget = $this->getSelectedBudget(); Log::debug(sprintf('Now in StageImportDataHandler::convertToArray() with count %d', \count($transactions))); /** @var array $transaction */ foreach ($transactions as $index => $transaction) { - Log::debug(sprintf('Now creating array for transaction %d of %d', $index + 1, $total)); + $description = $transaction['memo'] ?? '(empty)'; + Log::debug(sprintf('Now creating array for transaction %d of %d ("%s")', $index + 1, $total, $description)); $amount = (string)($transaction['amount'] ?? 0); if ('0' === $amount) { + Log::debug(sprintf('Amount is zero (%s), skip this transaction.', $amount)); continue; } - $source = $localAccount; - $type = 'withdrawal'; - $tags = [ + Log::debug(sprintf('Amount detected is %s', $amount)); + $source = $localAccount; + $type = 'withdrawal'; + $tags = [ $transaction['cleared'] ?? '', $transaction['approved'] ? 'approved' : 'not-approved', $transaction['flag_color'] ?? '', ]; + $possibleDestinationId = null; + if (null !== $transaction['transfer_account_id']) { + // indication that it is a transfer. + $possibleDestinationId = $config['mapping'][$transaction['transfer_account_id']] ?? null; + Log::debug(sprintf('transfer_account_id has value %s', $transaction['transfer_account_id'])); + Log::debug(sprintf('Can map this to the following FF3 asset account: %d', $possibleDestinationId)); + $type = 'transfer'; + + } + $destinationData = [ 'name' => $transaction['payee_name'], 'iban' => null, @@ -125,25 +147,28 @@ class ImportDataHandler 'bic' => null, ]; - $destination = $this->mapper->map(null, $amount, $destinationData); - + $destination = $this->mapper->map($possibleDestinationId, $amount, $destinationData); if (1 === bccomp($amount, '0')) { [$source, $destination] = [$destination, $source]; - $type = 'deposit'; + $type = $type === 'transfer' ? 'transfer' : 'deposit'; + Log::debug(sprintf('Amount is %s, so switch source/dest and make this a %s', $amount, $type)); } - $entry = [ + Log::debug(sprintf('Final source account: #%d ("%s")', $source->id, $source->name)); + Log::debug(sprintf('Final destination account: #%d ("%s")', $destination->id, $destination->name)); + + $entry = [ 'type' => $type, 'date' => $transaction['date'] ?? date('Y-m-d'), - 'tags' => $tags, // TODO + 'tags' => $tags, 'user' => $this->importJob->user_id, - 'notes' => null, // TODO + 'notes' => null, // all custom fields: 'external_id' => $transaction['id'] ?? '', // journal data: - 'description' => $transaction['memo'] ?? '(empty)', + 'description' => $description, 'piggy_bank_id' => null, 'piggy_bank_name' => null, 'bill_id' => null, @@ -172,6 +197,7 @@ class ImportDataHandler ], ], ]; + Log::debug(sprintf('Done with entry #%d', $index)); $array[] = $entry; } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 3e1165760e..a006f1c0c8 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -814,6 +814,7 @@ return [ // new user: 'welcome' => 'Welcome to Firefly III!', 'submit' => 'Submit', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Getting started', 'to_get_started' => 'It is good to see you have successfully installed Firefly III. To get started with this tool please enter your bank\'s name and the balance of your main checking account. Do not worry yet if you have multiple accounts. You can add those later. It\'s just that Firefly III needs something to start with.', 'savings_balance_text' => 'Firefly III will automatically create a savings account for you. By default, there will be no money in your savings account, but if you tell Firefly III the balance it will be stored as such.', diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 524135901c..baa07203d0 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', @@ -171,8 +172,8 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', - 'job_config_ynab_accounts_title' => 'Select accounts', - 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', // keys from "extra" array: diff --git a/resources/views/import/ynab/prerequisites.twig b/resources/views/import/ynab/prerequisites.twig index a2c15ebf00..c7faab048a 100644 --- a/resources/views/import/ynab/prerequisites.twig +++ b/resources/views/import/ynab/prerequisites.twig @@ -18,11 +18,20 @@

{{ trans('import.prereq_ynab_text')|raw }}

+ {% if not is_https %} +

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

+ {{ callback_uri }} +

+ {% endif %} + {% if is_https %}

{{ trans('import.prereq_ynab_redirect')|raw }}

{{ callback_uri }}

+ {% endif %}
@@ -38,9 +47,16 @@ From 1af45aff736c101e8f40c1908d64306369885095 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 19:28:56 +0200 Subject: [PATCH 012/166] Fix missing link to admin. --- app/Support/Twig/General.php | 23 +++++++++++++++++++++- resources/views/partials/menu-sidebar.twig | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 633de8f1c3..a92de4be5c 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -24,6 +24,7 @@ namespace FireflyIII\Support\Twig; use Carbon\Carbon; use FireflyIII\Models\Account; +use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Support\Twig\Extension\Account as AccountExtension; use League\CommonMark\CommonMarkConverter; use Route; @@ -45,7 +46,7 @@ class General extends Twig_Extension $this->balance(), $this->formatFilesize(), $this->mimeIcon(), - $this->markdown(), + $this->markdown() ]; } @@ -64,6 +65,7 @@ class General extends Twig_Extension $this->activeRoutePartialWhat(), $this->formatDate(), new Twig_SimpleFunction('accountGetMetaField', [AccountExtension::class, 'getMetaField']), + $this->hasRole(), ]; } @@ -235,6 +237,25 @@ class General extends Twig_Extension ); } + /** + * Will return true if the user is of role X. + * + * @return Twig_SimpleFunction + */ + protected function hasRole(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'hasRole', + function (string $role): bool { + $repository = app(UserRepositoryInterface::class); + if ($repository->hasRole(auth()->user(), $role)) { + return true; + } + return false; + } + ); + } + /** * @return Twig_SimpleFilter */ diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index 7dde1e8bcc..8d959b7807 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -149,7 +149,7 @@ {{ 'currencies'|_ }} - {% if Auth.user.hasRole('owner') %} + {% if hasRole('owner') %}
  • {{ 'administration'|_ }} From 194073e49a1bfba793ca846a3402c77ba6f1b51c Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 31 Jul 2018 20:39:36 +0200 Subject: [PATCH 013/166] Fix tests. --- app/Import/Storage/ImportArrayStorage.php | 4 +- .../Controllers/Admin/UserControllerTest.php | 8 +-- .../Controllers/CurrencyControllerTest.php | 14 ++-- .../Import/IndexControllerTest.php | 66 ++++++++++++++++--- .../Import/Storage/ImportArrayStorageTest.php | 24 +++++-- 5 files changed, 90 insertions(+), 26 deletions(-) diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 8d6e4742de..1a736c5acc 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -223,8 +223,8 @@ class ImportArrayStorage { // collect transactions using the journal collector $collector = app(JournalCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setUser($this->importJob->user); + $collector->withOpposingAccount(); // filter on specific journals. $collector->setJournals(new Collection([$journal])); diff --git a/tests/Feature/Controllers/Admin/UserControllerTest.php b/tests/Feature/Controllers/Admin/UserControllerTest.php index e5a7c24048..70d559ff56 100644 --- a/tests/Feature/Controllers/Admin/UserControllerTest.php +++ b/tests/Feature/Controllers/Admin/UserControllerTest.php @@ -49,7 +49,7 @@ class UserControllerTest extends TestCase { $repository = $this->mock(UserRepositoryInterface::class); $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->once()->andReturn(false); - $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->once()->andReturn(true); + $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $this->be($this->user()); $response = $this->get(route('admin.users.delete', [1])); $response->assertStatus(200); @@ -80,7 +80,7 @@ class UserControllerTest extends TestCase { $repository = $this->mock(UserRepositoryInterface::class); $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->once()->andReturn(false); - $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->once()->andReturn(true); + $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $this->be($this->user()); $response = $this->get(route('admin.users.edit', [1])); $response->assertStatus(200); @@ -95,7 +95,7 @@ class UserControllerTest extends TestCase { $repository = $this->mock(UserRepositoryInterface::class); //$repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->once()->andReturn(false); - $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); + $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(3)->andReturn(true); $user = $this->user(); $repository->shouldReceive('all')->andReturn(new Collection([$user])); @@ -112,7 +112,7 @@ class UserControllerTest extends TestCase public function testShow(): void { $repository = $this->mock(UserRepositoryInterface::class); - $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->once()->andReturn(true); + $repository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $repository->shouldReceive('getUserData')->andReturn( [ 'export_jobs_success' => 0, diff --git a/tests/Feature/Controllers/CurrencyControllerTest.php b/tests/Feature/Controllers/CurrencyControllerTest.php index 04c11c88bf..e39225964d 100644 --- a/tests/Feature/Controllers/CurrencyControllerTest.php +++ b/tests/Feature/Controllers/CurrencyControllerTest.php @@ -30,7 +30,7 @@ use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Support\Collection; use Log; use Tests\TestCase; - +use Mockery; /** * Class CurrencyControllerTest * @@ -121,7 +121,7 @@ class CurrencyControllerTest extends TestCase $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); - $userRepos->shouldReceive('hasRole')->once()->andReturn(true); + $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $this->be($this->user()); $response = $this->get(route('currencies.create')); @@ -160,7 +160,7 @@ class CurrencyControllerTest extends TestCase $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('canDeleteCurrency')->andReturn(true); - $userRepos->shouldReceive('hasRole')->once()->andReturn(true); + $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $this->be($this->user()); $response = $this->get(route('currencies.delete', [2])); @@ -182,7 +182,7 @@ class CurrencyControllerTest extends TestCase $repository->shouldReceive('canDeleteCurrency')->andReturn(true); $repository->shouldReceive('destroy')->andReturn(true)->once(); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); - $userRepos->shouldReceive('hasRole')->once()->andReturn(true); + $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(1)->andReturn(true); $this->session(['currencies.delete.uri' => 'http://localhost']); $this->be($this->user()); @@ -202,7 +202,7 @@ class CurrencyControllerTest extends TestCase $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); - $userRepos->shouldReceive('hasRole')->once()->andReturn(true); + $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $this->be($this->user()); $response = $this->get(route('currencies.edit', [1])); @@ -227,7 +227,7 @@ class CurrencyControllerTest extends TestCase $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('getCurrencyByPreference')->andReturn($currencies->first()); $repository->shouldReceive('get')->andReturn($currencies); - $userRepos->shouldReceive('hasRole')->once()->andReturn(true); + $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(true); $this->be($this->user()); $response = $this->get(route('currencies.index')); @@ -250,7 +250,7 @@ class CurrencyControllerTest extends TestCase $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('getCurrencyByPreference')->andReturn(new TransactionCurrency); $repository->shouldReceive('get')->andReturn(new Collection); - $userRepos->shouldReceive('hasRole')->once()->andReturn(false); + $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->times(2)->andReturn(false); $this->be($this->user()); $response = $this->get(route('currencies.index')); diff --git a/tests/Feature/Controllers/Import/IndexControllerTest.php b/tests/Feature/Controllers/Import/IndexControllerTest.php index 647fb9a528..5a9e2931a0 100644 --- a/tests/Feature/Controllers/Import/IndexControllerTest.php +++ b/tests/Feature/Controllers/Import/IndexControllerTest.php @@ -26,6 +26,7 @@ use FireflyIII\Import\Prerequisites\BunqPrerequisites; use FireflyIII\Import\Prerequisites\FakePrerequisites; use FireflyIII\Import\Prerequisites\FilePrerequisites; use FireflyIII\Import\Prerequisites\SpectrePrerequisites; +use FireflyIII\Import\Prerequisites\YnabPrerequisites; use FireflyIII\Models\ImportJob; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; @@ -62,6 +63,7 @@ class IndexControllerTest extends TestCase $fakePrerequisites = $this->mock(FakePrerequisites::class); $bunqPrerequisites = $this->mock(BunqPrerequisites::class); $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); // fake job: $importJob = new ImportJob; @@ -69,12 +71,14 @@ class IndexControllerTest extends TestCase $importJob->key = 'fake_job_1'; // mock calls: + $ynabPrerequisites->shouldReceive('setUser')->once(); $fakePrerequisites->shouldReceive('setUser')->once(); $bunqPrerequisites->shouldReceive('setUser')->once(); $spectrePrerequisites->shouldReceive('setUser')->once(); $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $bunqPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $spectrePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $ynabPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false)->once(); @@ -95,6 +99,7 @@ class IndexControllerTest extends TestCase $bunqPrerequisites = $this->mock(BunqPrerequisites::class); $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); $filePrerequisites = $this->mock(FilePrerequisites::class); + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); // fake job: $importJob = new ImportJob; @@ -103,8 +108,19 @@ class IndexControllerTest extends TestCase $importJob->user_id = 1; // mock calls - $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true)->times(2); + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true)->times(3); + + $bunqPrerequisites->shouldReceive('setUser')->times(2); + $bunqPrerequisites->shouldReceive('isComplete')->times(2)->andReturn(false); + + $spectrePrerequisites->shouldReceive('setUser')->times(2); + $spectrePrerequisites->shouldReceive('isComplete')->times(2)->andReturn(false); + + $ynabPrerequisites->shouldReceive('setUser')->times(2); + $ynabPrerequisites->shouldReceive('isComplete')->times(2)->andReturn(false); + $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); + $fakePrerequisites->shouldReceive('isComplete')->times(3)->andReturn(false); $fakePrerequisites->shouldReceive('setUser')->times(3); @@ -128,6 +144,7 @@ class IndexControllerTest extends TestCase $bunqPrerequisites = $this->mock(BunqPrerequisites::class); $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); $filePrerequisites = $this->mock(FilePrerequisites::class); + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); // fake job: $importJob = new ImportJob; @@ -136,12 +153,25 @@ class IndexControllerTest extends TestCase $importJob->user_id = 1; // mock call: - $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true)->times(2); - $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); + $fakePrerequisites->shouldReceive('isComplete')->times(3)->andReturn(true); $fakePrerequisites->shouldReceive('setUser')->times(3); + + $bunqPrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); + $bunqPrerequisites->shouldReceive('setUser')->times(2); + + $spectrePrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); + $spectrePrerequisites->shouldReceive('setUser')->times(2); + + $ynabPrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); + $ynabPrerequisites->shouldReceive('setUser')->times(2); + + + + $repository->shouldReceive('create')->withArgs(['fake'])->andReturn($importJob); $repository->shouldReceive('setStatus')->withArgs([Mockery::any(), 'has_prereq'])->andReturn($importJob)->once(); + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true)->times(3); $this->be($this->user()); $response = $this->get(route('import.create', ['fake'])); @@ -162,23 +192,26 @@ class IndexControllerTest extends TestCase $bunqPrerequisites = $this->mock(BunqPrerequisites::class); $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); $filePrerequisites = $this->mock(FilePrerequisites::class); + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); // fake job: $importJob = new ImportJob; $importJob->provider = 'file'; $importJob->key = 'file_job_1'; - $importJob->user_id =1; + $importJob->user_id = 1; // mock calls $fakePrerequisites->shouldReceive('setUser')->times(2); $bunqPrerequisites->shouldReceive('setUser')->times(2); $spectrePrerequisites->shouldReceive('setUser')->times(2); + $ynabPrerequisites->shouldReceive('setUser')->times(2); $fakePrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); $bunqPrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); $spectrePrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); + $ynabPrerequisites->shouldReceive('isComplete')->times(2)->andReturn(true); - $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false)->times(2); + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false)->times(3); $repository->shouldReceive('create')->withArgs(['file'])->andReturn($importJob); $repository->shouldReceive('setStatus')->withArgs([Mockery::any(), 'has_prereq'])->andReturn($importJob)->once(); @@ -202,6 +235,7 @@ class IndexControllerTest extends TestCase $bunqPrerequisites = $this->mock(BunqPrerequisites::class); $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); $filePrerequisites = $this->mock(FilePrerequisites::class); + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); $job = new ImportJob; $job->user_id = $this->user()->id; @@ -225,11 +259,13 @@ class IndexControllerTest extends TestCase $fakePrerequisites->shouldReceive('setUser')->times(1); $bunqPrerequisites->shouldReceive('setUser')->times(1); $spectrePrerequisites->shouldReceive('setUser')->times(1); + $ynabPrerequisites->shouldReceive('setUser')->times(1); //$filePrerequisites->shouldReceive('setUser')->times(1); $fakePrerequisites->shouldReceive('isComplete')->times(1)->andReturn(true); $bunqPrerequisites->shouldReceive('isComplete')->times(1)->andReturn(true); $spectrePrerequisites->shouldReceive('isComplete')->times(1)->andReturn(true); + $ynabPrerequisites->shouldReceive('isComplete')->times(1)->andReturn(true); //$filePrerequisites->shouldReceive('isComplete')->times(1)->andReturn(true); $this->be($this->user()); @@ -251,16 +287,21 @@ class IndexControllerTest extends TestCase $bunqPrerequisites = $this->mock(BunqPrerequisites::class); $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); $filePrerequisites = $this->mock(FilePrerequisites::class); + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); // call methods: $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(false); + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->andReturn(false); $fakePrerequisites->shouldReceive('setUser')->once(); $bunqPrerequisites->shouldReceive('setUser')->once(); $spectrePrerequisites->shouldReceive('setUser')->once(); + $ynabPrerequisites->shouldReceive('setUser')->once(); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $bunqPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $spectrePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $ynabPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); $response = $this->get(route('import.index')); $response->assertStatus(200); @@ -280,13 +321,22 @@ class IndexControllerTest extends TestCase $spectrePrerequisites = $this->mock(SpectrePrerequisites::class); $filePrerequisites = $this->mock(FilePrerequisites::class); $userRepository = $this->mock(UserRepositoryInterface::class); - + $ynabPrerequisites = $this->mock(YnabPrerequisites::class); // call methods: $fakePrerequisites->shouldReceive('setUser')->once(); - $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); - $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true); + $bunqPrerequisites->shouldReceive('setUser')->once(); + $spectrePrerequisites->shouldReceive('setUser')->once(); + $ynabPrerequisites->shouldReceive('setUser')->once(); + $fakePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $bunqPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $spectrePrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + $ynabPrerequisites->shouldReceive('isComplete')->once()->andReturn(true); + + + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'demo'])->andReturn(true); + $userRepository->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->andReturn(false); $response = $this->get(route('import.index')); $response->assertStatus(200); diff --git a/tests/Unit/Import/Storage/ImportArrayStorageTest.php b/tests/Unit/Import/Storage/ImportArrayStorageTest.php index 0b8a96b017..396fa42acb 100644 --- a/tests/Unit/Import/Storage/ImportArrayStorageTest.php +++ b/tests/Unit/Import/Storage/ImportArrayStorageTest.php @@ -125,7 +125,8 @@ class ImportArrayStorageTest extends TestCase $journalRepos = $this->mock(JournalRepositoryInterface::class); // mock calls: - $collector->shouldReceive('setUser')->once(); + $collector->shouldReceive('setUser')->times(2); + $repository->shouldReceive('setUser')->once(); $repository->shouldReceive('setStatus')->withAnyArgs(); $ruleRepos->shouldReceive('setUser')->once(); @@ -142,13 +143,20 @@ class ImportArrayStorageTest extends TestCase // mock collector so it will return some transfers: - $collector->shouldReceive('setAllAssetAccounts')->once()->andReturnSelf(); + $collector->shouldReceive('setAllAssetAccounts')->times(1)->andReturnSelf(); $collector->shouldReceive('setTypes')->withArgs([[TransactionType::TRANSFER]])->once()->andReturnSelf(); - $collector->shouldReceive('withOpposingAccount')->once()->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->times(2)->andReturnSelf(); $collector->shouldReceive('ignoreCache')->once()->andReturnSelf(); $collector->shouldReceive('removeFilter')->withArgs([InternalTransferFilter::class])->once()->andReturnSelf(); $collector->shouldReceive('getJournals')->andReturn($transferCollection); + // set journals for the return method. + $collector->shouldReceive('setJournals')->andReturnSelf(); + $collector->shouldReceive('addFilter')->andReturnSelf(); + + + + $storage = new ImportArrayStorage; $storage->setImportJob($job); $result = new Collection; @@ -400,6 +408,8 @@ class ImportArrayStorageTest extends TestCase $journalRepos->shouldReceive('store')->once()->andReturn($journal); $journalRepos->shouldReceive('findByHash')->andReturn(null)->times(2); + + $storage = new ImportArrayStorage; $storage->setImportJob($job); $result = new Collection; @@ -455,7 +465,7 @@ class ImportArrayStorageTest extends TestCase $journalRepos = $this->mock(JournalRepositoryInterface::class); // mock calls: - $collector->shouldReceive('setUser')->once(); + $collector->shouldReceive('setUser')->times(2); // twice for transfer $repository->shouldReceive('setUser')->once(); $repository->shouldReceive('setStatus')->withAnyArgs(); $ruleRepos->shouldReceive('setUser')->once(); @@ -471,10 +481,14 @@ class ImportArrayStorageTest extends TestCase $collector->shouldReceive('setAllAssetAccounts')->once()->andReturnSelf(); $collector->shouldReceive('setTypes')->withArgs([[TransactionType::TRANSFER]])->once()->andReturnSelf(); $collector->shouldReceive('ignoreCache')->once()->andReturnSelf(); - $collector->shouldReceive('withOpposingAccount')->once()->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->times(2)->andReturnSelf(); $collector->shouldReceive('removeFilter')->withArgs([InternalTransferFilter::class])->once()->andReturnSelf(); $collector->shouldReceive('getJournals')->andReturn($transferCollection); + // set journals for the return method. + $collector->shouldReceive('setJournals')->andReturnSelf(); + $collector->shouldReceive('addFilter')->andReturnSelf(); + $storage = new ImportArrayStorage; $storage->setImportJob($job); $result = new Collection; From e3e8336602b861c3c3aa88e695e7a98beac438cd Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 1 Aug 2018 07:24:19 +0200 Subject: [PATCH 014/166] Update some code for Heroku. --- .env.heroku | 2 +- app/Providers/AppServiceProvider.php | 5 ++++- nginx_app.conf | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.env.heroku b/.env.heroku index 55237bd98b..13de7aa0b4 100644 --- a/.env.heroku +++ b/.env.heroku @@ -36,7 +36,7 @@ DB_CONNECTION=pgsql # 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. # Several other options exist. You can use 'single' for one big fat error log (not recommended). # Also available are 'syslog' and 'errorlog' which will log to the system itself. -APP_LOG=errorlog +APP_LOG=syslog # Log level. You can set this from least severe to most severe: # debug, info, notice, warning, error, critical, alert, emergency diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 30247d8ee0..d91edc80e3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,7 +25,7 @@ namespace FireflyIII\Providers; use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use Laravel\Passport\Passport; - +use URL; /** * @codeCoverageIgnore * Class AppServiceProvider. @@ -38,6 +38,9 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Schema::defaultStringLength(191); + if('heroku' === env('APP_ENV')) { + URL::forceScheme('https'); + } } /** diff --git a/nginx_app.conf b/nginx_app.conf index 97bdb62378..39dedf0155 100644 --- a/nginx_app.conf +++ b/nginx_app.conf @@ -2,6 +2,9 @@ fastcgi_param HTTP_PROXY ""; location / { + if ($http_x_forwarded_proto != "https") { + return 301 https://$host$request_uri; + } # try to serve file directly, fallback to rewrite try_files $uri @rewriteapp; } From 427e9c56378873cb6ceb02a3e32702998ae8aa36 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 1 Aug 2018 07:32:53 +0200 Subject: [PATCH 015/166] Fix #1216 --- resources/lang/en_US/firefly.php | 7 +++++++ resources/views/transactions/show.twig | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index a006f1c0c8..e93a20952e 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1131,6 +1131,13 @@ return [ 'destination_transaction' => 'Destination transaction', 'delete_journal_link' => 'Delete the link between :source and :destination', 'deleted_link' => 'Deleted link', + 'link_is (partially) paid for by' => 'is (partially) paid for by', + 'link_(partially) pays for' => '(partially) pays for', + 'link_is (partially) refunded by' => 'is (partially) refunded by', + 'link_(partially) refunds' => '(partially) refunds', + 'link_is (partially) reimbursed by' => 'is (partially) reimbursed by', + 'link_(partially) reimburses' => '(partially) reimburses', + 'link_relates to' => 'relates to', // link translations: 'relates to_inward' => 'relates to', diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 099f163687..f63a467f28 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -426,8 +426,8 @@
    From 138a5bc3fea20509679d19622f6508b93afdda7a Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 1 Aug 2018 07:35:31 +0200 Subject: [PATCH 016/166] Add autocomplete field as suggested by Chrome. --- resources/views/auth/register.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/auth/register.twig b/resources/views/auth/register.twig index f6c76ce3ec..8f09651b58 100644 --- a/resources/views/auth/register.twig +++ b/resources/views/auth/register.twig @@ -25,10 +25,10 @@
    - +
    - +
    From 610af45dee9a6fd082d5d2fe8ec4f325d74e0f3b Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 2 Aug 2018 07:12:06 +0200 Subject: [PATCH 017/166] Experimental sort routine for list of accounts [skip ci] --- app/Http/Controllers/Account/IndexController.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index 363a818cdc..f9d854c1aa 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -75,9 +75,18 @@ class IndexController extends Controller $types = config('firefly.accountTypesByIdentifier.' . $what); $collection = $this->repository->getAccountsByType($types); $total = $collection->count(); - $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); + + // sort collection: + $collection = $collection->sortBy( + function (Account $account) { + return ($account->active ? '1' : '0') . $account->name; + } + ); + + + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); unset($collection); /** @var Carbon $start */ $start = clone session('start', Carbon::now()->startOfMonth()); From f07d8e958f42a0c93a69e70062ec58ceb0b12d6d Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 2 Aug 2018 07:14:00 +0200 Subject: [PATCH 018/166] Experimental sort routine for list of accounts [skip ci] --- app/Http/Controllers/Account/IndexController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index f9d854c1aa..533c7e2e9b 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -79,7 +79,7 @@ class IndexController extends Controller // sort collection: $collection = $collection->sortBy( function (Account $account) { - return ($account->active ? '1' : '0') . $account->name; + return ($account->active ? '0' : '1') . $account->name; } ); From ae85876965e8278be9c979e221359febb67b9e7c Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 2 Aug 2018 07:17:18 +0200 Subject: [PATCH 019/166] Make sure null value is turned into an empty string [skip ci] --- app/Support/ExpandedForm.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 3f7e706f9e..0370df5f54 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -756,6 +756,7 @@ class ExpandedForm */ public function textarea(string $name, $value = null, array $options = null): string { + $value = $value ?? ''; $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); $classes = $this->getHolderClasses($name); @@ -791,7 +792,7 @@ class ExpandedForm $options['step'] = 'any'; $defaultCurrency = $options['currency'] ?? Amt::getDefaultCurrency(); /** @var Collection $currencies */ - $currencies = app('amount')->getAllCurrencies(); + $currencies = app('amount')->getAllCurrencies(); unset($options['currency'], $options['placeholder']); // perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount) From 2290fcde22ba372f53f9dc4de824c71ac3182f19 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 3 Aug 2018 16:35:55 +0200 Subject: [PATCH 020/166] First code for liabilities and some tests. --- app/Models/AccountType.php | 56 +++-------- config/firefly.php | 11 ++- database/seeds/AccountTypeSeeder.php | 5 +- resources/lang/en_US/firefly.php | 5 + resources/views/partials/menu-sidebar.twig | 5 + routes/web.php | 4 +- .../AvailableBudgetControllerTest.php | 32 +++++++ .../ConfigurationControllerTest.php | 54 ++++++++++- .../CurrencyExchangeRateControllerTest.php | 68 +++++++++++++ .../Controllers/JournalLinkControllerTest.php | 96 +++++++++++++++++++ 10 files changed, 282 insertions(+), 54 deletions(-) diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index 2a0c2fa7ca..1446d0d6e3 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -35,51 +35,19 @@ use Illuminate\Database\Eloquent\Relations\HasMany; */ class AccountType extends Model { - /** - * - */ - public const DEFAULT = 'Default account'; - /** - * - */ - public const CASH = 'Cash account'; - /** - * - */ - public const ASSET = 'Asset account'; - /** - * - */ - public const EXPENSE = 'Expense account'; - /** - * - */ - public const REVENUE = 'Revenue account'; - /** - * - */ + public const DEFAULT = 'Default account'; + public const CASH = 'Cash account'; + public const ASSET = 'Asset account'; + public const EXPENSE = 'Expense account'; + public const REVENUE = 'Revenue account'; public const INITIAL_BALANCE = 'Initial balance account'; - /** - * - */ - public const BENEFICIARY = 'Beneficiary account'; - /** - * - */ - public const IMPORT = 'Import account'; - /** - * - */ - public const RECONCILIATION = 'Reconciliation account'; - /** - * - */ - public const LOAN = 'Loan'; - /** - * The attributes that should be casted to native types. - * - * @var array - */ + public const BENEFICIARY = 'Beneficiary account'; + public const IMPORT = 'Import account'; + public const RECONCILIATION = 'Reconciliation account'; + public const LOAN = 'Loan'; + public const DEBT = 'Debt'; + public const MORTGAGE = 'Mortgage'; + public const CREDITCARD = 'Credit card'; protected $casts = [ 'created_at' => 'datetime', diff --git a/config/firefly.php b/config/firefly.php index 1af5d5b4a7..c996c32c76 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -197,10 +197,11 @@ return [ ], 'accountTypesByIdentifier' => [ - 'asset' => ['Default account', 'Asset account'], - 'expense' => ['Expense account', 'Beneficiary account'], - 'revenue' => ['Revenue account'], - 'import' => ['Import account'], + 'asset' => ['Default account', 'Asset account'], + 'expense' => ['Expense account', 'Beneficiary account'], + 'revenue' => ['Revenue account'], + 'import' => ['Import account'], + 'liabilities' => ['Loan', 'Debt', 'Credit card', 'Mortgage'], ], 'accountTypeByIdentifier' => [ @@ -267,7 +268,7 @@ return [ 'journalLink' => \FireflyIII\Models\TransactionJournalLink::class, 'currency' => \FireflyIII\Models\TransactionCurrency::class, 'piggyBank' => \FireflyIII\Models\PiggyBank::class, - 'preference' => \FireflyIII\Models\Preference::class, + 'preference' => \FireflyIII\Models\Preference::class, 'tj' => \FireflyIII\Models\TransactionJournal::class, 'tag' => \FireflyIII\Models\Tag::class, 'recurrence' => \FireflyIII\Models\Recurrence::class, diff --git a/database/seeds/AccountTypeSeeder.php b/database/seeds/AccountTypeSeeder.php index 0ebde97fb3..f695c310d8 100644 --- a/database/seeds/AccountTypeSeeder.php +++ b/database/seeds/AccountTypeSeeder.php @@ -41,12 +41,15 @@ class AccountTypeSeeder extends Seeder AccountType::IMPORT, AccountType::LOAN, AccountType::RECONCILIATION, + AccountType::DEBT, + AccountType::MORTGAGE, + AccountType::CREDITCARD, ]; foreach ($types as $type) { try { AccountType::create(['type' => $type]); } catch (PDOException $e) { - Log::warning(sprintf('Could not create account type "%s". It might exist already.', $type)); + Log::warning(sprintf('Could not create account type "%s". It might exist already: %s', $type , $e->getMessage())); } } } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index e93a20952e..dd9f127293 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -700,6 +700,7 @@ return [ 'revenue_accounts' => 'Revenue accounts', 'cash_accounts' => 'Cash accounts', 'Cash account' => 'Cash account', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Reconcile account ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Delete reconciliation', @@ -1187,6 +1188,10 @@ return [ 'no_accounts_intro_revenue' => 'You have no revenue accounts yet. Revenue accounts are the places where you receive money from, such as your employer.', 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', 'no_accounts_create_revenue' => 'Create a revenue account', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Let\'s create a budget', 'no_budgets_intro_default' => 'You have no budgets yet. Budgets are used to organise your expenses into logical groups, which you can give a soft-cap to limit your expenses.', 'no_budgets_imperative_default' => 'Budgets are the basic tools of financial management. Let\'s create one now:', diff --git a/resources/views/partials/menu-sidebar.twig b/resources/views/partials/menu-sidebar.twig index 8d959b7807..048c4d77d5 100644 --- a/resources/views/partials/menu-sidebar.twig +++ b/resources/views/partials/menu-sidebar.twig @@ -27,6 +27,11 @@ {{ 'revenue_accounts'|_ }}
  • +
  • + + {{ 'liabilities_accounts'|_ }} + +
  • diff --git a/routes/web.php b/routes/web.php index 46fc37283b..89081aed4d 100755 --- a/routes/web.php +++ b/routes/web.php @@ -110,10 +110,10 @@ Route::group( ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'accounts', 'as' => 'accounts.'], function () { // show: - Route::get('{what}', ['uses' => 'Account\IndexController@index', 'as' => 'index'])->where('what', 'revenue|asset|expense'); + Route::get('{what}', ['uses' => 'Account\IndexController@index', 'as' => 'index'])->where('what', 'revenue|asset|expense|liabilities'); // create - Route::get('create/{what}', ['uses' => 'Account\CreateController@create', 'as' => 'create'])->where('what', 'revenue|asset|expense'); + Route::get('create/{what}', ['uses' => 'Account\CreateController@create', 'as' => 'create'])->where('what', 'revenue|asset|expense|liabilities'); Route::post('store', ['uses' => 'Account\CreateController@store', 'as' => 'store']); diff --git a/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php b/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php index ccd8c56aa6..2e2f3dd750 100644 --- a/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php +++ b/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php @@ -148,6 +148,38 @@ class AvailableBudgetControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * Store new available budget but the budget currency is invalid. + * + * @covers \FireflyIII\Api\V1\Controllers\AvailableBudgetController + * @covers \FireflyIII\Api\V1\Requests\AvailableBudgetRequest + */ + public function testStoreInvalidCurrency(): void + { + // mock stuff: + $repository = $this->mock(BudgetRepositoryInterface::class); + $currencyRepository = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $currencyRepository->shouldReceive('findNull')->andReturn(null); + + // data to submit + $data = [ + 'transaction_currency_id' => '1', + 'amount' => '100', + 'start_date' => '2018-01-01', + 'end_date' => '2018-01-31', + ]; + + + // test API + $response = $this->post('/api/v1/available_budgets', $data,['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('Could not find the indicated currency.'); + $response->assertHeader('Content-Type', 'application/json'); + } + /** * Update available budget. * diff --git a/tests/Api/V1/Controllers/ConfigurationControllerTest.php b/tests/Api/V1/Controllers/ConfigurationControllerTest.php index f003a993ac..ba3c4add09 100644 --- a/tests/Api/V1/Controllers/ConfigurationControllerTest.php +++ b/tests/Api/V1/Controllers/ConfigurationControllerTest.php @@ -109,7 +109,7 @@ class ConfigurationControllerTest extends TestCase */ public function testUpdate(): void { - $data = [ + $data = [ 'name' => 'permission_update_check', 'value' => 1, @@ -135,7 +135,57 @@ class ConfigurationControllerTest extends TestCase FireflyConfig::shouldReceive('get')->withArgs(['permission_update_check'])->andReturn($permConfig)->once(); FireflyConfig::shouldReceive('get')->withArgs(['last_update_check'])->andReturn($lastConfig)->once(); FireflyConfig::shouldReceive('get')->withArgs(['single_user_mode'])->andReturn($singleConfig)->once(); - FireflyConfig::shouldReceive('set')->once()->withArgs(['permission_update_check',1]); + FireflyConfig::shouldReceive('set')->once()->withArgs(['permission_update_check', 1]); + + + $expected = [ + 'data' => [ + 'is_demo_site' => false, + 'permission_update_check' => -1, + 'last_update_check' => 123456789, + 'single_user_mode' => true, + ], + ]; + + $response = $this->post('/api/v1/configuration', $data); + $response->assertStatus(200); + $response->assertExactJson($expected); + } + + /** + * Set configuration variables. + * + * @covers \FireflyIII\Api\V1\Controllers\ConfigurationController + */ + public function testUpdateBoolean(): void + { + $data = [ + 'name' => 'single_user_mode', + 'value' => 'true', + + ]; + + $demoConfig = new Configuration; + $demoConfig->name = 'is_demo_site'; + $demoConfig->data = false; + + $permConfig = new Configuration; + $permConfig->name = 'permission_update_check'; + $permConfig->data = -1; + + $lastConfig = new Configuration; + $lastConfig->name = 'last_update_check'; + $lastConfig->data = 123456789; + + $singleConfig = new Configuration; + $singleConfig->name = 'single_user_mode'; + $singleConfig->data = true; + + FireflyConfig::shouldReceive('get')->withArgs(['is_demo_site'])->andReturn($demoConfig)->once(); + FireflyConfig::shouldReceive('get')->withArgs(['permission_update_check'])->andReturn($permConfig)->once(); + FireflyConfig::shouldReceive('get')->withArgs(['last_update_check'])->andReturn($lastConfig)->once(); + FireflyConfig::shouldReceive('get')->withArgs(['single_user_mode'])->andReturn($singleConfig)->once(); + FireflyConfig::shouldReceive('set')->once()->withArgs(['single_user_mode', true]); $expected = [ diff --git a/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php b/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php index 1ba9df9e72..d246883ef1 100644 --- a/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php +++ b/tests/Api/V1/Controllers/CurrencyExchangeRateControllerTest.php @@ -102,4 +102,72 @@ class CurrencyExchangeRateControllerTest extends TestCase ); $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + + /** + * @covers \FireflyIII\Api\V1\Controllers\CurrencyExchangeRateController + */ + public function testIndexBadSource(): 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(null)->once(); + $repository->shouldReceive('findByCodeNull')->withArgs(['USD'])->andReturn(TransactionCurrency::whereCode('USD')->first())->once(); + + // test API + $params = [ + 'from' => 'EUR', + 'to' => 'USD', + 'date' => '2018-01-01', + ]; + $response = $this->get('/api/v1/cer?' . http_build_query($params), ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('Unknown source currency.'); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\CurrencyExchangeRateController + */ + public function testIndexBadDestination(): 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('USD')->first())->once(); + $repository->shouldReceive('findByCodeNull')->withArgs(['USD'])->andReturn(null)->once(); + + // test API + $params = [ + 'from' => 'EUR', + 'to' => 'USD', + 'date' => '2018-01-01', + ]; + $response = $this->get('/api/v1/cer?' . http_build_query($params), ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('Unknown destination currency.'); + $response->assertHeader('Content-Type', 'application/json'); + } } diff --git a/tests/Api/V1/Controllers/JournalLinkControllerTest.php b/tests/Api/V1/Controllers/JournalLinkControllerTest.php index 5819cfadaa..858f252c2f 100644 --- a/tests/Api/V1/Controllers/JournalLinkControllerTest.php +++ b/tests/Api/V1/Controllers/JournalLinkControllerTest.php @@ -188,6 +188,53 @@ class JournalLinkControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testStoreWithNull(): 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(null); + + + // 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, ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('Source or destination is NULL.'); // error message + $response->assertHeader('Content-Type', 'application/json'); + } + /** * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest @@ -236,4 +283,53 @@ class JournalLinkControllerTest extends TestCase $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 testUpdateWithNull(): 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(null); + $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(500); + $response->assertSee('Source or destination is NULL.'); // the creation moment. + $response->assertHeader('Content-Type', 'application/json'); + } } From 7a9ab190ebaab9868e3ce36ca3635cce54a34a31 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 3 Aug 2018 16:55:10 +0200 Subject: [PATCH 021/166] Fixes #1586 --- .../Internal/Support/JournalServiceTrait.php | 10 ++-------- .../Import/Placeholder/ImportTransaction.php | 2 ++ .../Import/Routine/File/ImportableConverter.php | 13 +++++-------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index a5de54011c..61f30ef5c1 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -95,19 +95,13 @@ trait JournalServiceTrait */ protected function storeMeta(TransactionJournal $journal, array $data, string $field): void { - - if (!isset($data[$field])) { - Log::debug(sprintf('Want to store meta-field "%s", but no value.', $field)); - - return; - } $set = [ 'journal' => $journal, 'name' => $field, - 'data' => (string)$data[$field], + 'data' => (string)($data[$field] ?? ''), ]; - Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $field, (string)$data[$field])); + Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); /** @var TransactionJournalMetaFactory $factory */ $factory = app(TransactionJournalMetaFactory::class); diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php index 3847c7553c..0d397561f7 100644 --- a/app/Support/Import/Placeholder/ImportTransaction.php +++ b/app/Support/Import/Placeholder/ImportTransaction.php @@ -183,7 +183,9 @@ class ImportTransaction $meta = ['sepa-ct-id', 'sepa-ct-op', 'sepa-db', 'sepa-cc', 'sepa-country', 'sepa-batch-id', 'sepa-ep', 'sepa-ci', 'internal-reference', 'date-interest', 'date-invoice', 'date-book', 'date-payment', 'date-process', 'date-due',]; + Log::debug(sprintf('Now going to check role "%s".', $role)); if (\in_array($role, $meta, true)) { + Log::debug(sprintf('Role "%s" is in allowed meta roles, so store its value %s.', $role, $columnValue->getValue())); $this->meta[$role] = $columnValue->getValue(); return; diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 17fcfe2b46..43445e96e6 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -133,9 +133,9 @@ class ImportableConverter /** * @param string|null $date * - * @return string + * @return string|null */ - private function convertDateValue(string $date = null): string + private function convertDateValue(string $date = null): ?string { $result = null; if (null !== $date) { @@ -147,11 +147,6 @@ class ImportableConverter Log::error($e->getTraceAsString()); } } - if (null === $result) { - $object = new Carbon; - $result = $object->format('Y-m-d'); - } - return $result; } @@ -167,6 +162,8 @@ class ImportableConverter $foreignAmount = $importable->calculateForeignAmount(); $amount = $importable->calculateAmount(); + Log::debug('All meta data: ', $importable->meta); + if ('' === $amount) { $amount = $foreignAmount; } @@ -215,7 +212,7 @@ class ImportableConverter return [ 'type' => $transactionType, - 'date' => $this->convertDateValue($importable->date), + 'date' => $this->convertDateValue($importable->date) ?? Carbon::create()->format('Y-m-d'), 'tags' => $importable->tags, 'user' => $this->importJob->user_id, 'notes' => $importable->note, From dd49926cc249eb2610531d23f0d6b1010d5671aa Mon Sep 17 00:00:00 2001 From: Clemens Wijnekus Date: Sat, 4 Aug 2018 00:08:21 +0200 Subject: [PATCH 022/166] Added .idea folder to Gitignore for IntelliJ based projects. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e8992ef3ee..4f414122e4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ yarn-error.log public/google*.html report.html composer.phar +/.idea \ No newline at end of file From de12db5f05508228b8db638c175df9ee6a7eb64e Mon Sep 17 00:00:00 2001 From: Clemens Wijnekus Date: Sat, 4 Aug 2018 00:27:28 +0200 Subject: [PATCH 023/166] Fix for setting initial user as Owner --- app/Repositories/User/UserRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Repositories/User/UserRepository.php b/app/Repositories/User/UserRepository.php index c9d3ef52ff..1a61259dab 100644 --- a/app/Repositories/User/UserRepository.php +++ b/app/Repositories/User/UserRepository.php @@ -58,7 +58,7 @@ class UserRepository implements UserRepositoryInterface } try { - $user->roles()->attach($role); + $user->roles()->attach($roleObject); } catch (QueryException $e) { // don't care Log::info(sprintf('Query exception when giving user a role: %s', $e->getMessage())); From 771d448a7bf4ce7385a4a4ecfb311c3008ca2448 Mon Sep 17 00:00:00 2001 From: Clemens Wijnekus Date: Sat, 4 Aug 2018 10:17:18 +0200 Subject: [PATCH 024/166] Revert "Added .idea folder to Gitignore for IntelliJ based projects." This reverts commit dd49926 --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4f414122e4..e8992ef3ee 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,3 @@ yarn-error.log public/google*.html report.html composer.phar -/.idea \ No newline at end of file From cb9c87102f713ffe429d4a2dd0ff18cabdfeb192 Mon Sep 17 00:00:00 2001 From: Luca Vallerini Date: Sat, 4 Aug 2018 13:03:54 +0200 Subject: [PATCH 025/166] Transactions links are now translatable on admin views --- resources/lang/de_DE/firefly.php | 4 ++++ resources/lang/en_US/firefly.php | 4 ++++ resources/lang/es_ES/firefly.php | 4 ++++ resources/lang/fr_FR/firefly.php | 4 ++++ resources/lang/id_ID/firefly.php | 4 ++++ resources/lang/it_IT/firefly.php | 4 ++++ resources/lang/nl_NL/firefly.php | 4 ++++ resources/lang/pl_PL/firefly.php | 4 ++++ resources/lang/pt_BR/firefly.php | 4 ++++ resources/lang/ru_RU/firefly.php | 4 ++++ resources/lang/tr_TR/firefly.php | 4 ++++ resources/views/admin/link/index.twig | 6 +++--- resources/views/admin/link/show.twig | 4 ++-- 13 files changed, 49 insertions(+), 5 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index b947fd5730..5ac8855ae3 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -1133,6 +1133,10 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'deleted_link' => 'Verknüpfung löschen', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'bezieht sich auf', 'is (partially) refunded by_inward' => 'wird (teilweise) erstattet durch', 'is (partially) paid for by_inward' => 'wird (teilweise) bezahlt von', diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index dd9f127293..43632689a8 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1141,6 +1141,10 @@ return [ 'link_relates to' => 'relates to', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'relates to', 'is (partially) refunded by_inward' => 'is (partially) refunded by', 'is (partially) paid for by_inward' => 'is (partially) paid for by', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index eeecbbfb2a..bc327f15ea 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -1133,6 +1133,10 @@ return [ 'deleted_link' => 'Enlace borrado', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'relacionado con', 'is (partially) refunded by_inward' => 'es (parcialmente) es devuelto por', 'is (partially) paid for by_inward' => 'es(parcialmente) pagado por', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 9fbab65594..e84db72fb6 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Lien supprimé', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'se rapporte à', 'is (partially) refunded by_inward' => 'est (partiellement) remboursé par', 'is (partially) paid for by_inward' => 'est (partiellement) payé par', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 77dea623aa..17ecbc86e5 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Tautan dihapus', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'berhubungan dengan', 'is (partially) refunded by_inward' => '(sebagian) dikembalikan oleh', 'is (partially) paid for by_inward' => 'adalah (sebagian) dibayar oleh', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index b402a9674e..80f5ed6e7f 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Elimina collegamento', // link translations: + 'Paid_name' => 'Pagata', + 'Refund_name' => 'Rimborso', + 'Reimbursement_name' => 'Rimborso', + 'Related_name' => 'Inerente', 'relates to_inward' => 'inerente a', 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsata da', 'is (partially) paid for by_inward' => 'è (parzialmente) pagata da', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 500f0f75a9..1958b6f73a 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Koppeling verwijderd', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'gerelateerd aan', 'is (partially) refunded by_inward' => 'wordt (deels) terugbetaald door', 'is (partially) paid for by_inward' => 'wordt (deels) betaald door', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 7c5687f66e..76c5c7fb80 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Usunięto powiązanie', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'odnosi się do', 'is (partially) refunded by_inward' => 'jest (częściowo) zwracane przez', 'is (partially) paid for by_inward' => 'jest (częściowo) opłacane przez', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 96b0815825..cc4c4dc6c4 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Excluir ligação', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'relacionado a', 'is (partially) refunded by_inward' => 'é (parcialmente) devolvido por', 'is (partially) paid for by_inward' => 'é (parcialmente) pago por', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index f5292f8f7d..d437830797 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -1132,6 +1132,10 @@ return [ 'deleted_link' => 'Связь удалена', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'связано с', 'is (partially) refunded by_inward' => '(частично) возвращён', 'is (partially) paid for by_inward' => '(частично) оплачен', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 6e26744d38..ddd1dff69e 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -1135,6 +1135,10 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'deleted_link' => 'Bağlantı silindi', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'ile ilişkili', 'is (partially) refunded by_inward' => 'tarafından (kısmen) geri ödendi', 'is (partially) paid for by_inward' => 'tarafından (kısmen) ödendi', diff --git a/resources/views/admin/link/index.twig b/resources/views/admin/link/index.twig index f49df83759..33185b4401 100644 --- a/resources/views/admin/link/index.twig +++ b/resources/views/admin/link/index.twig @@ -35,13 +35,13 @@ {% endif %}
  • + {# input buttons #} + + + {# icon #} + + + {# description #} + + + + + + + + + + {% if not hideBudgets %} + + {% endif %} + + {% if not hideCategories %} + + {% endif %} + {% if not hideBills %} + + {% endif %} + diff --git a/resources/views/partials/transaction-row.twig b/resources/views/partials/transaction-row.twig index 9f392268ee..dd31b6c44c 100644 --- a/resources/views/partials/transaction-row.twig +++ b/resources/views/partials/transaction-row.twig @@ -1,17 +1,11 @@ - + + {# input buttons #} {# icon #} @@ -36,31 +30,39 @@ + {# amount #} + + {# date #} + {# source #} - + {# dest #} - {% if not hideBudgets %} + {# budget, if opted to show. #} + {% if showBudgets %} {% endif %} - {% if not hideCategories %} + {# category, if opted to show. #} + {% if showCategories %} {% endif %} - {% if not hideBills %} + + {# bill, if opted to show#} + {% if showBill %} @@ -127,12 +127,12 @@ @@ -151,12 +151,12 @@ @@ -165,7 +165,7 @@ From cb2c52cddb025002910e65f19e7fe95b12d2b588 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 12 Aug 2018 14:26:11 +0200 Subject: [PATCH 082/166] New cronjob code. --- app/Console/Commands/Cron.php | 65 ++++++++++++++ app/Console/Kernel.php | 14 +++- .../Controllers/System/CronController.php | 65 ++++++++++++++ app/Http/Kernel.php | 10 ++- app/Support/Binder/CLIToken.php | 62 ++++++++++++++ app/Support/Cronjobs/AbstractCronjob.php | 39 +++++++++ app/Support/Cronjobs/RecurringCronjob.php | 84 +++++++++++++++++++ config/firefly.php | 1 + routes/web.php | 6 ++ 9 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/Cron.php create mode 100644 app/Http/Controllers/System/CronController.php create mode 100644 app/Support/Binder/CLIToken.php create mode 100644 app/Support/Cronjobs/AbstractCronjob.php create mode 100644 app/Support/Cronjobs/RecurringCronjob.php diff --git a/app/Console/Commands/Cron.php b/app/Console/Commands/Cron.php new file mode 100644 index 0000000000..4678382102 --- /dev/null +++ b/app/Console/Commands/Cron.php @@ -0,0 +1,65 @@ +fire(); + } catch (FireflyException $e) { + $this->error($e->getMessage()); + + return 0; + } + if (false === $result) { + $this->line('The recurring transaction cron job did not fire.'); + } + if (true === $result) { + $this->line('The recurring transaction cron job fired successfully.'); + } + + $this->info('More feedback on the cron jobs can be found in the log files.'); + + return 0; + } + + +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 3c1fe481de..1e4f9eb172 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -52,6 +52,18 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { - $schedule->job(new CreateRecurringTransactions(new Carbon))->daily(); + $schedule->call(function() { + echo "\n"; + echo '------------'; + echo "\n"; + echo wordwrap('Firefly III no longer users the Laravel scheduler to do cron jobs! Please read the instructions here:'); + echo "\n"; + echo 'https://firefly-iii.readthedocs.io/en/latest/'; + echo "\n\n"; + echo 'Disable this cron job!'; + echo "\n"; + echo '------------'; + echo "\n"; + })->everyMinute(); } } diff --git a/app/Http/Controllers/System/CronController.php b/app/Http/Controllers/System/CronController.php new file mode 100644 index 0000000000..46a50c8384 --- /dev/null +++ b/app/Http/Controllers/System/CronController.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\System; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Support\Cronjobs\RecurringCronjob; + +/** + * Class CronController + */ +class CronController +{ + /** + * @param string $token + * + * @return string + */ + public function cron(string $token): string + { + $results = []; + $results[] = $this->runRecurring(); + + return implode("
    \n", $results); + } + + /** + * @return string + */ + private function runRecurring(): string + { + $recurring = new RecurringCronjob; + try { + $result = $recurring->fire(); + } catch (FireflyException $e) { + return $e->getMessage(); + } + if (false === $result) { + return 'The recurring transaction cron job did not fire.'; + } + + return 'The recurring transaction cron job fired successfully.'; + } + +} \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b21c6bf743..514b660f1d 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -80,7 +80,7 @@ class Kernel extends HttpKernel // does not check login // does not check 2fa // does not check activation - 'web' => [ + 'web' => [ Sandstorm::class, EncryptCookies::class, AddQueuedCookiesToResponse::class, @@ -90,6 +90,14 @@ class Kernel extends HttpKernel CreateFreshApiToken::class, ], + // only the basic variable binders. + 'binders-only' => [ + Installer::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + Binder::class, + ], + // MUST NOT be logged in. Does not care about 2FA or confirmation. 'user-not-logged-in' => [ Installer::class, diff --git a/app/Support/Binder/CLIToken.php b/app/Support/Binder/CLIToken.php new file mode 100644 index 0000000000..faeb5aef34 --- /dev/null +++ b/app/Support/Binder/CLIToken.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Binder; + +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Routing\Route; +use Illuminate\Support\Collection; +use Log; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Class CLIToken + */ +class CLIToken implements BinderInterface +{ + + /** + * @param string $value + * @param Route $route + * + * @return mixed + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public static function routeBinder(string $value, Route $route) + { + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + /** @var Collection $users */ + $users = $repository->all(); + + foreach ($users as $user) { + $accessToken = app('preferences')->getForUser($user, 'access_token', null); + if ($accessToken->data === $value) { + Log::info(sprintf('Recognized user #%d (%s) from his acccess token.', $user->id, $user->email)); + + return $value; + } + } + throw new NotFoundHttpException; + } +} \ No newline at end of file diff --git a/app/Support/Cronjobs/AbstractCronjob.php b/app/Support/Cronjobs/AbstractCronjob.php new file mode 100644 index 0000000000..3eebb176be --- /dev/null +++ b/app/Support/Cronjobs/AbstractCronjob.php @@ -0,0 +1,39 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Cronjobs; + +/** + * Class AbstractCronjob + */ +abstract class AbstractCronjob +{ + /** @var int */ + public $timeBetweenRuns = 43200; + + /** + * @return bool + */ + abstract public function fire(): bool; + +} \ No newline at end of file diff --git a/app/Support/Cronjobs/RecurringCronjob.php b/app/Support/Cronjobs/RecurringCronjob.php new file mode 100644 index 0000000000..c75e11ad29 --- /dev/null +++ b/app/Support/Cronjobs/RecurringCronjob.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Cronjobs; + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Jobs\CreateRecurringTransactions; +use FireflyIII\Models\Configuration; +use Log; + +/** + * Class RecurringCronjob + */ +class RecurringCronjob extends AbstractCronjob +{ + + /** + * @return bool + * @throws FireflyException + */ + public function fire(): bool + { + /** @var Configuration $config */ + $config = app('fireflyconfig')->get('last_rt_job', 0); + $lastTime = (int)$config->data; + $diff = time() - $lastTime; + $diffForHumans = Carbon::now()->diffForHumans(Carbon::createFromTimestamp($lastTime), true); + if (0 === $lastTime) { + Log::info('Recurring transactions cronjob has never fired before.'); + } + // less than half a day ago: + if ($lastTime > 0 && $diff <= 43200) { + Log::info(sprintf('It has been %s since the recurring transactions cronjob has fired. It will not fire now.', $diffForHumans)); + + return false; + } + + if ($lastTime > 0 && $diff > 43200) { + Log::info(sprintf('It has been %s since the recurring transactions cronjob has fired. It will fire now!', $diffForHumans)); + } + + try { + $this->fireRecurring(); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException(sprintf('Could not run recurring transaction cron job: %s', $e->getMessage())); + } + + return true; + } + + /** + * + * @throws FireflyException + */ + private function fireRecurring(): void + { + $job = new CreateRecurringTransactions(new Carbon); + $job->handle(); + app('fireflyconfig')->set('last_rt_job', time()); + } +} \ No newline at end of file diff --git a/config/firefly.php b/config/firefly.php index 69f9fd000c..99afb8c215 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -307,6 +307,7 @@ return [ 'fromCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class, 'toCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class, 'unfinishedJournal' => \FireflyIII\Support\Binder\UnfinishedJournal::class, + 'cliToken' => \FireflyIII\Support\Binder\CLIToken::class, ], diff --git a/routes/web.php b/routes/web.php index 88246c4aaa..a492dec37a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -33,6 +33,12 @@ Route::group( } ); +Route::group( + ['middleware' => 'binders-only','namespace' => 'FireflyIII\Http\Controllers\System', 'as' => 'cron.', 'prefix' => 'cron'], function () { + Route::get('run/{cliToken}', ['uses' => 'CronController@cron', 'as' => 'cron']); +} +); + /** * These routes only work when the user is NOT logged in. */ From 6941176519d1d3e279d811d89ee9e84ab625a338 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 13 Aug 2018 18:09:47 +0200 Subject: [PATCH 083/166] Make chart red/green --- .../Controllers/Chart/CategoryController.php | 52 +++++++++++-------- public/js/ff/categories/show.js | 4 +- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index 280442c8f2..14955e6413 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -79,12 +79,14 @@ class CategoryController extends Controller $accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $chartData = [ [ - 'label' => (string)trans('firefly.spent'), - 'entries' => [], 'type' => 'bar', + 'label' => (string)trans('firefly.spent'), + 'entries' => [], 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red ], [ - 'label' => (string)trans('firefly.earned'), - 'entries' => [], 'type' => 'bar', + 'label' => (string)trans('firefly.earned'), + 'entries' => [], 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green ], [ 'label' => (string)trans('firefly.sum'), @@ -184,14 +186,16 @@ class CategoryController extends Controller $periods = app('navigation')->listOfPeriods($start, $end); $chartData = [ [ - 'label' => (string)trans('firefly.spent'), - 'entries' => [], - 'type' => 'bar', + 'label' => (string)trans('firefly.spent'), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red ], [ - 'label' => (string)trans('firefly.earned'), - 'entries' => [], - 'type' => 'bar', + 'label' => (string)trans('firefly.earned'), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green ], [ 'label' => (string)trans('firefly.sum'), @@ -245,14 +249,16 @@ class CategoryController extends Controller $periods = app('navigation')->listOfPeriods($start, $end); $chartData = [ [ - 'label' => (string)trans('firefly.spent'), - 'entries' => [], - 'type' => 'bar', + 'label' => (string)trans('firefly.spent'), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red ], [ - 'label' => (string)trans('firefly.earned'), - 'entries' => [], - 'type' => 'bar', + 'label' => (string)trans('firefly.earned'), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green ], [ 'label' => (string)trans('firefly.sum'), @@ -328,14 +334,16 @@ class CategoryController extends Controller // chart data $chartData = [ [ - 'label' => (string)trans('firefly.spent'), - 'entries' => [], - 'type' => 'bar', + 'label' => (string)trans('firefly.spent'), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red ], [ - 'label' => (string)trans('firefly.earned'), - 'entries' => [], - 'type' => 'bar', + 'label' => (string)trans('firefly.earned'), + 'entries' => [], + 'type' => 'bar', + 'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green ], [ 'label' => (string)trans('firefly.sum'), diff --git a/public/js/ff/categories/show.js b/public/js/ff/categories/show.js index 8b83ac44a5..548a1c3022 100644 --- a/public/js/ff/categories/show.js +++ b/public/js/ff/categories/show.js @@ -23,7 +23,7 @@ $(function () { "use strict"; - columnChart(everything, 'category-everything'); - columnChart(specific, 'specific-period'); + columnChartCustomColours(everything, 'category-everything'); + columnChartCustomColours(specific, 'specific-period'); }); \ No newline at end of file From 9c5463e515a62ea6676d7bb9067e1157f1bff1e7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 13 Aug 2018 18:10:02 +0200 Subject: [PATCH 084/166] Update to user and cron job. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8c05b1c8b9..61d1697f47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,11 +17,11 @@ RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nit_IT.UTF-8 U VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload # Make sure we own Firefly III directory -RUN chown -R www-data:www-data /var/www && chmod -R 775 $FIREFLY_PATH/storage +RUN chown -R $APPLICATION_GID:$APPLICATION_UID /var/www && chmod -R 775 $FIREFLY_PATH/storage # Add cron job RUN docker-service enable cron -RUN docker-cronjob '* * * * * application cd /app/ && php artisan schedule:run' +RUN docker-cronjob '0 3 * * * application cd /app/ && php artisan firefly:cron' # Run composer ENV COMPOSER_ALLOW_SUPERUSER 1 From 60e262dece2402cc7a144e560cb8fef56a93affe Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 13 Aug 2018 18:30:47 +0200 Subject: [PATCH 085/166] Fix bug in Spectre import. --- app/Services/Spectre/Object/Account.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/Spectre/Object/Account.php b/app/Services/Spectre/Object/Account.php index d3cfceb689..e8069f99f1 100644 --- a/app/Services/Spectre/Object/Account.php +++ b/app/Services/Spectre/Object/Account.php @@ -67,8 +67,8 @@ class Account extends SpectreObject $this->nature = $data['nature']; $this->createdAt = new Carbon($data['created_at']); $this->updatedAt = new Carbon($data['updated_at']); - - foreach ($data['extra'] as $key => $value) { + $extraArray = \is_array($data['extra']) ? $data['extra'] : []; + foreach ($extraArray as $key => $value) { $this->extra[$key] = $value; } } From 6c9eb1b699aeb9faf6533d44df6fcac4f7a4e2bb Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 13 Aug 2018 19:07:22 +0200 Subject: [PATCH 086/166] Update composer lock file. --- composer.lock | 326 +++++++++++++++++++++++++------------------------- 1 file changed, 160 insertions(+), 166 deletions(-) diff --git a/composer.lock b/composer.lock index 7ecd5c1198..250ad08f15 100644 --- a/composer.lock +++ b/composer.lock @@ -1021,16 +1021,16 @@ }, { "name": "laravel/framework", - "version": "v5.6.29", + "version": "v5.6.33", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "acc6b5c54ab196d3358f60acc5f55d9ebaaccc02" + "reference": "8a708b989adc320c451ee639d812af81f884666b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/acc6b5c54ab196d3358f60acc5f55d9ebaaccc02", - "reference": "acc6b5c54ab196d3358f60acc5f55d9ebaaccc02", + "url": "https://api.github.com/repos/laravel/framework/zipball/8a708b989adc320c451ee639d812af81f884666b", + "reference": "8a708b989adc320c451ee639d812af81f884666b", "shasum": "" }, "require": { @@ -1156,7 +1156,7 @@ "framework", "laravel" ], - "time": "2018-07-26T16:01:26+00:00" + "time": "2018-08-09T20:36:39+00:00" }, { "name": "laravel/passport", @@ -1297,16 +1297,16 @@ }, { "name": "lcobucci/jwt", - "version": "3.2.2", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "0b5930be73582369e10c4d4bb7a12bac927a203c" + "reference": "c9704b751315d21735dc98d78d4f37bd73596da7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0b5930be73582369e10c4d4bb7a12bac927a203c", - "reference": "0b5930be73582369e10c4d4bb7a12bac927a203c", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c9704b751315d21735dc98d78d4f37bd73596da7", + "reference": "c9704b751315d21735dc98d78d4f37bd73596da7", "shasum": "" }, "require": { @@ -1351,7 +1351,7 @@ "JWS", "jwt" ], - "time": "2017-09-01T08:23:26+00:00" + "time": "2018-08-03T11:23:50+00:00" }, { "name": "league/commonmark", @@ -2091,21 +2091,21 @@ }, { "name": "pragmarx/google2fa", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "40b3ce025bed0f9cd0c1c8ab7fc8265344c73de0" + "reference": "d9d960370277958c700552c551e136018b915499" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/40b3ce025bed0f9cd0c1c8ab7fc8265344c73de0", - "reference": "40b3ce025bed0f9cd0c1c8ab7fc8265344c73de0", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/d9d960370277958c700552c551e136018b915499", + "reference": "d9d960370277958c700552c551e136018b915499", "shasum": "" }, "require": { "paragonie/constant_time_encoding": "~1.0|~2.0", - "paragonie/random_compat": "~1.4|~2.0", + "paragonie/random_compat": ">=1 <9.99", "php": ">=5.4", "symfony/polyfill-php56": "~1.2" }, @@ -2148,7 +2148,7 @@ "google2fa", "laravel" ], - "time": "2018-03-15T23:14:19+00:00" + "time": "2018-07-31T23:31:19+00:00" }, { "name": "pragmarx/google2fa-laravel", @@ -2630,16 +2630,16 @@ }, { "name": "symfony/console", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5c31f6a97c1c240707f6d786e7e59bfacdbc0219" + "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5c31f6a97c1c240707f6d786e7e59bfacdbc0219", - "reference": "5c31f6a97c1c240707f6d786e7e59bfacdbc0219", + "url": "https://api.github.com/repos/symfony/console/zipball/ca80b8ced97cf07390078b29773dc384c39eee1f", + "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f", "shasum": "" }, "require": { @@ -2694,20 +2694,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-07-16T14:05:40+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/css-selector", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4" + "reference": "2a4df7618f869b456f9096781e78c57b509d76c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/03ac71606ecb0b0ce792faa17d74cc32c2949ef4", - "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a4df7618f869b456f9096781e78c57b509d76c7", + "reference": "2a4df7618f869b456f9096781e78c57b509d76c7", "shasum": "" }, "require": { @@ -2747,20 +2747,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-07-26T09:10:45+00:00" }, { "name": "symfony/debug", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "a1f2118cedb8731c45e945cdd2b808ca82abc4b5" + "reference": "9316545571f079c4dd183e674721d9dc783ce196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/a1f2118cedb8731c45e945cdd2b808ca82abc4b5", - "reference": "a1f2118cedb8731c45e945cdd2b808ca82abc4b5", + "url": "https://api.github.com/repos/symfony/debug/zipball/9316545571f079c4dd183e674721d9dc783ce196", + "reference": "9316545571f079c4dd183e674721d9dc783ce196", "shasum": "" }, "require": { @@ -2803,20 +2803,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-07-06T14:52:28+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f" + "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f", - "reference": "00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e", + "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e", "shasum": "" }, "require": { @@ -2866,20 +2866,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-07-10T11:02:47+00:00" + "time": "2018-07-26T09:10:45+00:00" }, { "name": "symfony/finder", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" + "reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", - "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", + "url": "https://api.github.com/repos/symfony/finder/zipball/e162f1df3102d0b7472805a5a9d5db9fcf0a8068", + "reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068", "shasum": "" }, "require": { @@ -2915,20 +2915,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-06-19T21:38:16+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "8da9ea68ab2d80dfabd41e0d14b9606bb47a10c0" + "reference": "7d93e3547660ec7ee3dad1428ba42e8076a0e5f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8da9ea68ab2d80dfabd41e0d14b9606bb47a10c0", - "reference": "8da9ea68ab2d80dfabd41e0d14b9606bb47a10c0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7d93e3547660ec7ee3dad1428ba42e8076a0e5f1", + "reference": "7d93e3547660ec7ee3dad1428ba42e8076a0e5f1", "shasum": "" }, "require": { @@ -2969,20 +2969,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-07-16T14:05:40+00:00" + "time": "2018-08-01T14:07:44+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ebd28f4f88a2ca0a0488882ad73c4004f3afdbe3" + "reference": "6347be5110efb27fe45ea04bf213078b67a05036" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ebd28f4f88a2ca0a0488882ad73c4004f3afdbe3", - "reference": "ebd28f4f88a2ca0a0488882ad73c4004f3afdbe3", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6347be5110efb27fe45ea04bf213078b67a05036", + "reference": "6347be5110efb27fe45ea04bf213078b67a05036", "shasum": "" }, "require": { @@ -3056,29 +3056,32 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-07-23T17:16:22+00:00" + "time": "2018-08-01T15:30:34+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3111,20 +3114,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", "shasum": "" }, "require": { @@ -3136,7 +3139,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3170,20 +3173,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "af98553c84912459db3f636329567809d639a8f6" + "reference": "7b4fc009172cc0196535b0328bd1226284a28000" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/af98553c84912459db3f636329567809d639a8f6", - "reference": "af98553c84912459db3f636329567809d639a8f6", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/7b4fc009172cc0196535b0328bd1226284a28000", + "reference": "7b4fc009172cc0196535b0328bd1226284a28000", "shasum": "" }, "require": { @@ -3193,7 +3196,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3226,20 +3229,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46" + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/a4576e282d782ad82397f3e4ec1df8e0f0cafb46", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", "shasum": "" }, "require": { @@ -3248,7 +3251,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3281,20 +3284,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "1a5ad95d9436cbff3296034fe9f8d586dce3fb3a" + "reference": "8e15d04ba3440984d23e7964b2ee1d25c8de1581" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/1a5ad95d9436cbff3296034fe9f8d586dce3fb3a", - "reference": "1a5ad95d9436cbff3296034fe9f8d586dce3fb3a", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8e15d04ba3440984d23e7964b2ee1d25c8de1581", + "reference": "8e15d04ba3440984d23e7964b2ee1d25c8de1581", "shasum": "" }, "require": { @@ -3303,7 +3306,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -3333,20 +3336,20 @@ "polyfill", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/process", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" + "reference": "f01fc7a4493572f7f506c49dcb50ad01fb3a2f56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", - "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "url": "https://api.github.com/repos/symfony/process/zipball/f01fc7a4493572f7f506c49dcb50ad01fb3a2f56", + "reference": "f01fc7a4493572f7f506c49dcb50ad01fb3a2f56", "shasum": "" }, "require": { @@ -3382,7 +3385,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-31T10:17:53+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -3446,16 +3449,16 @@ }, { "name": "symfony/routing", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "73770bf3682b4407b017c2bdcb2b11cdcbce5322" + "reference": "6912cfebc0ea4e7a46fdd15c9bd1f427dd39ff1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/73770bf3682b4407b017c2bdcb2b11cdcbce5322", - "reference": "73770bf3682b4407b017c2bdcb2b11cdcbce5322", + "url": "https://api.github.com/repos/symfony/routing/zipball/6912cfebc0ea4e7a46fdd15c9bd1f427dd39ff1b", + "reference": "6912cfebc0ea4e7a46fdd15c9bd1f427dd39ff1b", "shasum": "" }, "require": { @@ -3519,20 +3522,20 @@ "uri", "url" ], - "time": "2018-06-28T06:30:33+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/translation", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2dd74d6b2dcbd46a93971e6ce7d245cf3123e957" + "reference": "6fcd1bd44fd6d7181e6ea57a6f4e08a09b29ef65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2dd74d6b2dcbd46a93971e6ce7d245cf3123e957", - "reference": "2dd74d6b2dcbd46a93971e6ce7d245cf3123e957", + "url": "https://api.github.com/repos/symfony/translation/zipball/6fcd1bd44fd6d7181e6ea57a6f4e08a09b29ef65", + "reference": "6fcd1bd44fd6d7181e6ea57a6f4e08a09b29ef65", "shasum": "" }, "require": { @@ -3588,20 +3591,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2018-07-23T08:20:20+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "9f882aed43f364de1d43038e8fb39703c577afc1" + "reference": "69e174f4c02ec43919380171c6f7550753299316" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9f882aed43f364de1d43038e8fb39703c577afc1", - "reference": "9f882aed43f364de1d43038e8fb39703c577afc1", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/69e174f4c02ec43919380171c6f7550753299316", + "reference": "69e174f4c02ec43919380171c6f7550753299316", "shasum": "" }, "require": { @@ -3663,7 +3666,7 @@ "debug", "dump" ], - "time": "2018-07-05T11:54:23+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -3780,16 +3783,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.5.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a" + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", - "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", "shasum": "" }, "require": { @@ -3826,20 +3829,20 @@ "env", "environment" ], - "time": "2018-07-01T10:25:50+00:00" + "time": "2018-07-29T20:33:41+00:00" }, { "name": "zendframework/zend-diactoros", - "version": "1.8.3", + "version": "1.8.5", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "72c13834fb3db2a962e913758b384ff2e6425d6e" + "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/72c13834fb3db2a962e913758b384ff2e6425d6e", - "reference": "72c13834fb3db2a962e913758b384ff2e6425d6e", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/3e4edb822c942f37ade0d09579cfbab11e2fee87", + "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87", "shasum": "" }, "require": { @@ -3889,7 +3892,7 @@ "psr", "psr-7" ], - "time": "2018-07-24T21:54:38+00:00" + "time": "2018-08-10T14:16:32+00:00" } ], "packages-dev": [ @@ -4729,16 +4732,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -4750,12 +4753,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -4788,7 +4791,7 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5041,16 +5044,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.2.7", + "version": "7.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8e878aff7917ef66e702e03d1359b16eee254e2c" + "reference": "f9b14c17860eccb440a0352a117a81eb754cff5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e878aff7917ef66e702e03d1359b16eee254e2c", - "reference": "8e878aff7917ef66e702e03d1359b16eee254e2c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f9b14c17860eccb440a0352a117a81eb754cff5a", + "reference": "f9b14c17860eccb440a0352a117a81eb754cff5a", "shasum": "" }, "require": { @@ -5095,7 +5098,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.2-dev" + "dev-master": "7.3-dev" } }, "autoload": { @@ -5121,22 +5124,11 @@ "testing", "xunit" ], - "time": "2018-07-15T05:20:50+00:00" + "time": "2018-08-07T06:44:28+00:00" }, { "name": "roave/security-advisories", "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "053766d789f6393e5bc0896635d35abf8d2d362e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/053766d789f6393e5bc0896635d35abf8d2d362e", - "reference": "053766d789f6393e5bc0896635d35abf8d2d362e", - "shasum": "" - }, "conflict": { "3f/pygmentize": "<1.2", "adodb/adodb-php": "<5.20.12", @@ -5177,11 +5169,12 @@ "gregwar/rst": "<1.0.3", "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1", "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", "joomla/session": "<1.3.1", "kreait/firebase-php": ">=3.2,<3.8.1", - "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "magento/magento1ce": "<1.9.3.9", "magento/magento1ee": ">=1.9,<1.14.3.2", @@ -5221,7 +5214,7 @@ "symfony/dependency-injection": ">=2,<2.0.17", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", - "symfony/http-foundation": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/http-foundation": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3", "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/routing": ">=2,<2.0.19", @@ -5232,7 +5225,7 @@ "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", "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/symfony": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3", "symfony/translation": ">=2,<2.0.17", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", @@ -5258,9 +5251,10 @@ "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", - "zendframework/zend-diactoros": ">=1,<1.0.4", + "zendframework/zend-diactoros": ">=1,<1.8.4", + "zendframework/zend-feed": ">=1,<2.10.3", "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.3,<2.3.8|>=2.4,<2.4.1", + "zendframework/zend-http": ">=1,<2.8.1", "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", @@ -5291,7 +5285,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-07-18T13:51:34+00:00" + "time": "2018-08-10T10:05:21+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5858,16 +5852,16 @@ }, { "name": "symfony/class-loader", - "version": "v3.4.13", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e" + "reference": "31db283fc86d3143e7ff87e922177b457d909c30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/e63c12699822bb3b667e7216ba07fbcc3a3e203e", - "reference": "e63c12699822bb3b667e7216ba07fbcc3a3e203e", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/31db283fc86d3143e7ff87e922177b457d909c30", + "reference": "31db283fc86d3143e7ff87e922177b457d909c30", "shasum": "" }, "require": { @@ -5910,20 +5904,20 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:37:34+00:00" + "time": "2018-07-26T11:19:56+00:00" }, { "name": "symfony/config", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5" + "reference": "c868972ac26e4e19860ce11b300bb74145246ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", - "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", + "url": "https://api.github.com/repos/symfony/config/zipball/c868972ac26e4e19860ce11b300bb74145246ff9", + "reference": "c868972ac26e4e19860ce11b300bb74145246ff9", "shasum": "" }, "require": { @@ -5973,20 +5967,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-06-20T11:15:17+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" + "reference": "2e30335e0aafeaa86645555959572fe7cea22b43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2e30335e0aafeaa86645555959572fe7cea22b43", + "reference": "2e30335e0aafeaa86645555959572fe7cea22b43", "shasum": "" }, "require": { @@ -6023,20 +6017,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784" + "reference": "966c982df3cca41324253dc0c7ffe76b6076b705" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/07463bbbbbfe119045a24c4a516f92ebd2752784", - "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/966c982df3cca41324253dc0c7ffe76b6076b705", + "reference": "966c982df3cca41324253dc0c7ffe76b6076b705", "shasum": "" }, "require": { @@ -6072,20 +6066,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-19T16:51:42+00:00" + "time": "2018-07-26T11:00:49+00:00" }, { "name": "symfony/yaml", - "version": "v4.1.2", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" + "reference": "46bc69aa91fc4ab78a96ce67873a6b0c148fd48c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/46bc69aa91fc4ab78a96ce67873a6b0c148fd48c", + "reference": "46bc69aa91fc4ab78a96ce67873a6b0c148fd48c", "shasum": "" }, "require": { @@ -6131,7 +6125,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "theseer/tokenizer", From 7327941c777813931c27bf8a8fc055573c742b4f Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 13 Aug 2018 19:07:46 +0200 Subject: [PATCH 087/166] Alert if cron job isn't running. --- .../Controllers/Recurring/IndexController.php | 4 ++++ .../Http/Controllers/GetConfigurationData.php | 18 ++++++++++++++++++ .../Http/Controllers/PeriodOverview.php | 2 -- resources/lang/en_US/firefly.php | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index 5981e82c1e..e26a5507fe 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Recurrence; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Support\Http\Controllers\GetConfigurationData; use FireflyIII\Transformers\RecurrenceTransformer; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; @@ -41,6 +42,7 @@ use Symfony\Component\HttpFoundation\ParameterBag; */ class IndexController extends Controller { + use GetConfigurationData; /** @var RecurringRepositoryInterface Recurring repository */ private $recurring; @@ -97,6 +99,8 @@ class IndexController extends Controller $paginator = new LengthAwarePaginator($recurring, $total, $pageSize, $page); $paginator->setPath(route('recurring.index')); + $this->verifyRecurringCronJob(); + return view('recurring.index', compact('paginator', 'page', 'pageSize', 'total')); } diff --git a/app/Support/Http/Controllers/GetConfigurationData.php b/app/Support/Http/Controllers/GetConfigurationData.php index 824a5e1f11..2c46da121c 100644 --- a/app/Support/Http/Controllers/GetConfigurationData.php +++ b/app/Support/Http/Controllers/GetConfigurationData.php @@ -33,6 +33,7 @@ use Log; */ trait GetConfigurationData { + /** * All packages that are installed. * @@ -247,4 +248,21 @@ trait GetConfigurationData return false; } + + /** + * + */ + protected function verifyRecurringCronJob(): void + { + $config = app('fireflyconfig')->get('last_rt_job', 0); + $lastTime = (int)$config->data; + $now = time(); + if (0 === $lastTime) { + request()->session()->flash('info', trans('firefly.recurring_never_cron')); + return; + } + if($now - $lastTime > 129600) { + request()->session()->flash('warning', trans('firefly.recurring_cron_long_ago')); + } + } } \ No newline at end of file diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index 62bc74e7c6..c0b13e0b7b 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -59,8 +59,6 @@ use Illuminate\Support\Collection; */ trait PeriodOverview { - - /** * This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range) * and for each period, the amount of money spent and earned. This is a complex operation which is cached for diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 847f9162ca..933466992f 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Created deposits', 'created_Transfers' => 'Created transfers', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Tags', 'recurring_meta_field_notes' => 'Notes', From a2b997ba2093b9164098a50ef5d610c175fc9154 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 13 Aug 2018 19:09:43 +0200 Subject: [PATCH 088/166] Fix #1608 --- app/Console/Commands/CreateImport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php index 74910381e2..2479ac514f 100644 --- a/app/Console/Commands/CreateImport.php +++ b/app/Console/Commands/CreateImport.php @@ -150,7 +150,7 @@ class CreateImport extends Command if (true === $this->option('start')) { - $this->infoLine('The has started. The process is not visible. Please wait.'); + $this->infoLine('The import routine has started. The process is not visible. Please wait.'); Log::debug('Go for import!'); // run it! From e125254687a530eb226d7ecd36e9afef32c8a358 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 14 Aug 2018 06:40:04 +0200 Subject: [PATCH 089/166] Fix bad method name. --- app/Support/Search/Search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index 794b572f06..f456c4be8a 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -122,7 +122,7 @@ class Search implements SearchInterface $collector = $this->applyModifiers($collector); $collector->removeFilter(InternalTransferFilter::class); - $set = $collector->getPaginatedJournals()->getCollection(); + $set = $collector->getPaginatedTransactions()->getCollection(); Log::debug(sprintf('Found %d journals to check. ', $set->count())); From 11be33e942e2611fcefbb733e2a95bec5fd102cb Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 14 Aug 2018 06:40:21 +0200 Subject: [PATCH 090/166] Fix #1605 --- app/Support/Twig/Extension/TransactionJournal.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Support/Twig/Extension/TransactionJournal.php b/app/Support/Twig/Extension/TransactionJournal.php index 4c8f69ff96..59e15cfbe6 100644 --- a/app/Support/Twig/Extension/TransactionJournal.php +++ b/app/Support/Twig/Extension/TransactionJournal.php @@ -157,6 +157,7 @@ class TransactionJournal extends Twig_Extension $totals[$currencyId]['amount'] = bcadd($transaction->amount, $totals[$currencyId]['amount']); if (null !== $transaction->foreign_currency_id) { + $foreignAmount = $transaction->foreign_amount ?? '0'; $foreignId = $transaction->foreign_currency_id; $foreign = $transaction->foreignCurrency; if (!isset($totals[$foreignId])) { @@ -166,7 +167,7 @@ class TransactionJournal extends Twig_Extension ]; } $totals[$foreignId]['amount'] = bcadd( - $transaction->foreign_amount, + $foreignAmount, $totals[$foreignId]['amount'] ); } From 035dc8ceb4d2fa7b1b7df1d20d217247950024b7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 14 Aug 2018 20:08:07 +0200 Subject: [PATCH 091/166] Fix #1609 --- resources/lang/en_US/firefly.php | 2 +- resources/views/recurring/create.twig | 2 +- resources/views/recurring/edit.twig | 2 +- resources/views/transactions/single/create.twig | 2 +- resources/views/transactions/single/edit.twig | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 933466992f..a56b92ba7c 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days days of data may take a while to load.', 'registered' => 'You have registered successfully!', 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Savings account', 'Credit card' => 'Credit card', 'source_accounts' => 'Source account(s)', diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 1c098a5d87..578a796afc 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -118,7 +118,7 @@ {% if budgets|length > 1 %} {{ ExpandedForm.select('budget_id', budgets, null) }} {% else %} - {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }} + {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer', {link: route('budgets.index')})}) }} {% endif %} {# CATEGORY ALWAYS #} diff --git a/resources/views/recurring/edit.twig b/resources/views/recurring/edit.twig index 59fc8e6e16..11197d98db 100644 --- a/resources/views/recurring/edit.twig +++ b/resources/views/recurring/edit.twig @@ -128,7 +128,7 @@ {% if budgets|length > 1 %} {{ ExpandedForm.select('budget_id', budgets, budgetId) }} {##} {% else %} - {{ ExpandedForm.select('budget_id', budgets, budgetId, {helpText: trans('firefly.no_budget_pointer')}) }} + {{ ExpandedForm.select('budget_id', budgets, budgetId, {helpText: trans('firefly.no_budget_pointer', {link: route('budgets.index')})}) }} {#budgets#} {% endif %} diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index 2419f000f2..fdb718ca40 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -78,7 +78,7 @@ {% if budgets|length > 1 %} {{ ExpandedForm.select('budget_id', budgets, null) }} {% else %} - {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }} + {{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer', {link: route('budgets.index')})}) }} {% endif %} {# CATEGORY ALWAYS #} diff --git a/resources/views/transactions/single/edit.twig b/resources/views/transactions/single/edit.twig index 92b27629f6..a43f916046 100644 --- a/resources/views/transactions/single/edit.twig +++ b/resources/views/transactions/single/edit.twig @@ -85,7 +85,7 @@ {% if budgetList|length > 1 %} {{ ExpandedForm.select('budget_id', budgetList, data['budget_id']) }} {% else %} - {{ ExpandedForm.select('budget_id', budgetList, data['budget_id'], {helpText: trans('firefly.no_budget_pointer')}) }} + {{ ExpandedForm.select('budget_id', budgetList, data['budget_id'], {helpText: trans('firefly.no_budget_pointer', {link: route('budgets.index')})}) }} {% endif %} {% endif %} {{ ExpandedForm.text('category',data['category']) }} From efd5ceb40533af47db97d9784716544ff6c7d0ce Mon Sep 17 00:00:00 2001 From: Erik Gelderblom Date: Tue, 14 Aug 2018 21:21:03 +0200 Subject: [PATCH 092/166] fixed installation for cURL and cron --- .deploy/docker/crontab | 1 + .deploy/docker/entrypoint.sh | 3 ++ Dockerfile | 83 +++++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 .deploy/docker/crontab diff --git a/.deploy/docker/crontab b/.deploy/docker/crontab new file mode 100644 index 0000000000..a039a6bc84 --- /dev/null +++ b/.deploy/docker/crontab @@ -0,0 +1 @@ +* * * * * root /artisan schedule:run >> /var/log/cron.log diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 6e234c99c1..2a240720ac 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -26,3 +26,6 @@ cat .env.docker | envsubst > .env composer dump-autoload php artisan package:discover php artisan firefly:instructions install +service rsyslog start +service cron start +exec apache2-foreground diff --git a/Dockerfile b/Dockerfile index 61d1697f47..ac40994ce1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,64 @@ -FROM webdevops/php-nginx:7.2 +FROM php:7.1-apache + +# If building on a RPi, use --build-arg cores=3 to use all cores when compiling +# to speed up the image build +ARG CORES +ENV CORES ${CORES:-1} ENV FIREFLY_PATH /app WORKDIR $FIREFLY_PATH ADD . $FIREFLY_PATH -# gettext is used to update the .env file when the container launches. -RUN apt-get update -y && apt-get install -y --no-install-recommends gettext-base && apt-get clean +# install packages +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends libcurl4-openssl-dev \ + zlib1g-dev \ + libjpeg62-turbo-dev \ + wget \ + libpng-dev \ + libicu-dev \ + libedit-dev \ + libtidy-dev \ + libxml2-dev \ + libsqlite3-dev \ + libpq-dev \ + libbz2-dev \ + gettext-base \ + cron \ + rsyslog \ + locales && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install latest curl +RUN cd /tmp && \ + wget https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz && \ + tar -xvf openssl-${OPENSSL_VERSION}.tar.gz && \ + cd openssl-${OPENSSL_VERSION} && \ + ./config && \ + make -j${CORES} && \ + make install + +RUN cd /tmp && \ + wget https://curl.haxx.se/download/curl-${CURL_VERSION}.tar.gz && \ + tar -xvf curl-${CURL_VERSION}.tar.gz && \ + cd curl-${CURL_VERSION} && \ + ./configure --with-ssl --host=$(gcc -dumpmachine) && \ + make -j${CORES} && \ + make install + +# Make sure that libcurl is using the newer curl libaries +RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/00-curl.conf && ldconfig + +# Create the log file to be able to run tail +RUN touch /var/log/cron.log + +# Setup cron job +COPY .deploy/docker/crontab /etc/cron.d/crontab +RUN chmod 0644 /etc/cron.d/crontab + +# Install PHP exentions. +RUN docker-php-ext-install -j$(nproc) gd intl tidy zip bcmath pdo_mysql bz2 pdo_pgsql # Install composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer @@ -13,22 +66,30 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local # Generate locales supported by Firefly III RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\n\n" > /etc/locale.gen && locale-gen +# copy Apache config to correct spot. +COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf + +# Enable apache mod rewrite.. +RUN a2enmod rewrite + +# Enable apache mod ssl.. +RUN a2enmod ssl + # Create volumes VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload +# Enable default site (Firefly III) +COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf + # Make sure we own Firefly III directory RUN chown -R $APPLICATION_GID:$APPLICATION_UID /var/www && chmod -R 775 $FIREFLY_PATH/storage -# Add cron job -RUN docker-service enable cron -RUN docker-cronjob '0 3 * * * application cd /app/ && php artisan firefly:cron' - # Run composer ENV COMPOSER_ALLOW_SUPERUSER 1 RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest -# Copy nginx config to correct spot. -COPY ./.deploy/docker/vhost.conf /opt/docker/etc/nginx/vhost.conf +# Expose port 80 +EXPOSE 80 -# Copy entrypoint script to correct spot: -COPY ./.deploy/docker/entrypoint.sh /opt/docker/provision/entrypoint.d/default.sh +# Run entrypoint thing +ENTRYPOINT [".deploy/docker/entrypoint.sh"] \ No newline at end of file From a5f89e096780584571f2c8e7952994db5af80326 Mon Sep 17 00:00:00 2001 From: Erik Gelderblom Date: Wed, 15 Aug 2018 14:43:49 +0200 Subject: [PATCH 093/166] fixed missing ENV variables --- Dockerfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index ac40994ce1..c33b6479e9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,9 +5,9 @@ FROM php:7.1-apache ARG CORES ENV CORES ${CORES:-1} -ENV FIREFLY_PATH /app -WORKDIR $FIREFLY_PATH -ADD . $FIREFLY_PATH +ENV FIREFLY_PATH /var/www/firefly-iii) +ENV CURL_VERSION 7.60.0 +ENV OPENSSL_VERSION 1.1.1-pre6 # install packages RUN apt-get update -y && \ @@ -84,6 +84,10 @@ COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-defau # Make sure we own Firefly III directory RUN chown -R $APPLICATION_GID:$APPLICATION_UID /var/www && chmod -R 775 $FIREFLY_PATH/storage +# Copy in Firefly Source +WORKDIR $FIREFLY_PATH +ADD . $FIREFLY_PATH + # Run composer ENV COMPOSER_ALLOW_SUPERUSER 1 RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest From 6bd4fa1c0a8e49863e8e81629604704b6cb403d2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 15 Aug 2018 19:10:00 +0200 Subject: [PATCH 094/166] Remove bad slash from path. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c33b6479e9..b585ebe302 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM php:7.1-apache ARG CORES ENV CORES ${CORES:-1} -ENV FIREFLY_PATH /var/www/firefly-iii) +ENV FIREFLY_PATH /var/www/firefly-iii/ ENV CURL_VERSION 7.60.0 ENV OPENSSL_VERSION 1.1.1-pre6 From 566be8dc63c1330f139e31eafbed5b81e8d5da9a Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 16 Aug 2018 16:42:58 +0200 Subject: [PATCH 095/166] Fix #1564 --- app/Import/Storage/ImportArrayStorage.php | 6 +-- .../Bunq/ChooseAccountsHandler.php | 39 ++++++++++++++++++- .../Routine/Bunq/StageImportDataHandler.php | 30 +++++++++++--- .../Import/Routine/Bunq/StageNewHandler.php | 6 +++ 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 0caabbaa1e..232f552b2f 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -160,7 +160,7 @@ class ImportArrayStorage $array = $this->importJob->transactions; $count = 0; foreach ($array as $index => $transaction) { - if (strtolower(TransactionType::TRANSFER) === $transaction['type']) { + if (strtolower(TransactionType::TRANSFER) === strtolower($transaction['type'])) { $count++; Log::debug(sprintf('Row #%d is a transfer, increase count to %d', $index + 1, $count)); } @@ -479,7 +479,7 @@ class ImportArrayStorage $collection->push($journal); // add to collection of transfers, if necessary: - if ('transfer' === $store['type']) { + if ('transfer' === strtolower($store['type'])) { $transaction = $this->getTransactionFromJournal($journal); Log::debug('We just stored a transfer, so add the journal to the list of transfers.'); $this->transfers->push($transaction); @@ -505,7 +505,7 @@ class ImportArrayStorage private function transferExists(array $transaction): bool { Log::debug('Check if is a double transfer.'); - if (strtolower(TransactionType::TRANSFER) !== $transaction['type']) { + if (strtolower(TransactionType::TRANSFER) !== strtolower($transaction['type'])) { Log::debug(sprintf('Is a %s, not a transfer so no.', $transaction['type'])); return false; diff --git a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php index 6a801deccc..4a357846e0 100644 --- a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php @@ -81,6 +81,17 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface $mapping = $data['account_mapping'] ?? []; $applyRules = 1 === (int)$data['apply_rules']; $final = []; + + /* + * $ibanToAsset is used to map bunq IBAN's to Firefly III asset accounts. The array is structured like this: + * 12BUNQ123456.. => 1, + * 12BUNQ928811.. => 4, + * + * And contains the bunq asset account iban (left) and the FF3 asset ID (right). + * + * This is used to properly map transfers. + */ + $ibanToAsset = []; if (0 === \count($accounts)) { throw new FireflyException('No bunq accounts found. Import cannot continue.'); // @codeCoverageIgnore } @@ -95,11 +106,16 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface $localId = (int)$localId; // validate each - $bunqId = $this->validBunqAccount($bunqId); - $accountId = $this->validLocalAccount($localId); + $bunqId = $this->validBunqAccount($bunqId); + $accountId = $this->validLocalAccount($localId); + $bunqIban = $this->getBunqIban($bunqId); + if (null !== $bunqIban) { + $ibanToAsset[$bunqIban] = $accountId; + } $final[$bunqId] = $accountId; } $config['mapping'] = $final; + $config['bunq-iban'] = $ibanToAsset; $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); @@ -169,6 +185,25 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface $this->accountRepository->setUser($importJob->user); } + /** + * @param int $bunqId + * + * @return null|string + */ + private function getBunqIban(int $bunqId): ?string + { + $config = $this->repository->getConfiguration($this->importJob); + $accounts = $config['accounts'] ?? []; + /** @var array $bunqAccount */ + foreach ($accounts as $bunqAccount) { + if ((int)$bunqAccount['id'] === $bunqId) { + return $bunqAccount['iban'] ?? null; + } + } + + return null; + } + /** * @param int $currencyId * diff --git a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php index cb3f2e2989..8581c9ddde 100644 --- a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php +++ b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php @@ -50,6 +50,8 @@ class StageImportDataHandler private $accountRepository; /** @var ImportJob */ private $importJob; + /** @var array */ + private $jobConfiguration; /** @var ImportJobRepositoryInterface */ private $repository; /** @var array */ @@ -71,10 +73,11 @@ class StageImportDataHandler public function run(): void { $this->getContext(); - $config = $this->repository->getConfiguration($this->importJob); - $accounts = $config['accounts'] ?? []; - $mapping = $config['mapping'] ?? []; - $collection = [[]]; + $config = $this->repository->getConfiguration($this->importJob); + $accounts = $config['accounts'] ?? []; + $mapping = $config['mapping'] ?? []; + $collection = [[]]; + $this->jobConfiguration = $config; /** @var array $bunqAccount */ foreach ($accounts as $bunqAccount) { $bunqAccountId = $bunqAccount['id'] ?? 0; @@ -192,9 +195,24 @@ class StageImportDataHandler */ private function convertToAccount(LabelMonetaryAccount $party, string $expectedType): LocalAccount { - Log::debug('in convertToAccount()'); + + Log::debug(sprintf('in convertToAccount() with LabelMonetaryAccount: %s', '')); if (null !== $party->getIban()) { - // find opposing party by IBAN first. + // find account in 'bunq-iban' array first. + $bunqIbans = $this->jobConfiguration['bunq-iban'] ?? []; + if (isset($bunqIbans[$party->getIban()])) { + $accountId = (int)$bunqIbans[$party->getIban()]; + $result = $this->accountRepository->findNull($accountId); + if (null !== $result) { + Log::debug( + sprintf('Search for #%s (based on IBAN %s) resulted in account %s (#%d)', $accountId, $party->getIban(), $result->name, $result->id) + ); + + return $result; + } + } + + // find opposing party by IBAN second. $result = $this->accountRepository->findByIbanNull($party->getIban(), [$expectedType]); if (null !== $result) { Log::debug(sprintf('Search for %s resulted in account %s (#%d)', $party->getIban(), $result->name, $result->id)); diff --git a/app/Support/Import/Routine/Bunq/StageNewHandler.php b/app/Support/Import/Routine/Bunq/StageNewHandler.php index b4e9a30743..08dc20dd4e 100644 --- a/app/Support/Import/Routine/Bunq/StageNewHandler.php +++ b/app/Support/Import/Routine/Bunq/StageNewHandler.php @@ -150,6 +150,7 @@ class StageNewHandler 'balance' => $mab->getBalance(), 'status' => $mab->getStatus(), 'type' => 'MonetaryAccountBank', + 'iban' => null, 'aliases' => [], ]; @@ -168,6 +169,11 @@ class StageNewHandler 'name' => $alias->getName(), 'value' => $alias->getValue(), ]; + + // store IBAN alias separately: + if ('IBAN' === $alias->getType()) { + $return['iban'] = $alias->getValue(); + } } } From 3ca3ce0726ba05d130153c6f629ae78f7d7ae33b Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 16 Aug 2018 20:43:11 +0200 Subject: [PATCH 096/166] Fix #1616 --- app/Helpers/Chart/MetaPieChart.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Helpers/Chart/MetaPieChart.php b/app/Helpers/Chart/MetaPieChart.php index f60fe020a6..a6c97ef32d 100644 --- a/app/Helpers/Chart/MetaPieChart.php +++ b/app/Helpers/Chart/MetaPieChart.php @@ -360,7 +360,8 @@ class MetaPieChart implements MetaPieChartInterface foreach ($array as $objectId => $amount) { if (!isset($names[$objectId])) { $object = $repository->findNull((int)$objectId); - $names[$objectId] = $object->name ?? $object->tag; + $name = null === $object ? '(no name)' : $object->name; + $names[$objectId] = $name ?? $object->tag; } $amount = app('steam')->positive($amount); $this->total = bcadd($this->total, $amount); From 219a0cd612a17194c631688b84539c334a48e9ca Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Aug 2018 05:54:29 +0200 Subject: [PATCH 097/166] Fix for #1617 --- app/Http/Controllers/ReportController.php | 2 +- app/Http/Requests/ReportFormRequest.php | 6 ++++++ app/Support/Binder/TagList.php | 10 +++++++++- resources/views/reports/options/tag.twig | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index 1af1553e60..82339668ff 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -323,7 +323,7 @@ class ReportController extends Controller $accounts = implode(',', $request->getAccountList()->pluck('id')->toArray()); $categories = implode(',', $request->getCategoryList()->pluck('id')->toArray()); $budgets = implode(',', $request->getBudgetList()->pluck('id')->toArray()); - $tags = implode(',', $request->getTagList()->pluck('tag')->toArray()); + $tags = implode(',', $request->getTagList()->pluck('id')->toArray()); $expense = implode(',', $request->getExpenseList()->pluck('id')->toArray()); $uri = route('reports.index'); diff --git a/app/Http/Requests/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php index b59e8dbec5..9d9707b4f1 100644 --- a/app/Http/Requests/ReportFormRequest.php +++ b/app/Http/Requests/ReportFormRequest.php @@ -213,6 +213,12 @@ class ReportFormRequest extends Request $tag = $repository->findByTag($tagTag); if (null !== $tag) { $collection->push($tag); + continue; + } + $tag = $repository->findNull((int)$tagTag); + if (null !== $tag) { + $collection->push($tag); + continue; } } } diff --git a/app/Support/Binder/TagList.php b/app/Support/Binder/TagList.php index 2201eb09b2..6919034293 100644 --- a/app/Support/Binder/TagList.php +++ b/app/Support/Binder/TagList.php @@ -45,6 +45,7 @@ class TagList implements BinderInterface { if (auth()->check()) { $list = array_unique(array_map('\strtolower', explode(',', $value))); + Log::debug('List of tags is', $list); if (0 === \count($list)) { Log::error('Tag list is empty.'); throw new NotFoundHttpException; // @codeCoverageIgnore @@ -56,7 +57,14 @@ class TagList implements BinderInterface $collection = $allTags->filter( function (Tag $tag) use ($list) { - return \in_array(strtolower($tag->tag), $list, true); + if(\in_array(strtolower($tag->tag), $list, true)) { + return true; + } + if(\in_array((string)$tag->id, $list, true)) { + return true; + } + + return false; } ); diff --git a/resources/views/reports/options/tag.twig b/resources/views/reports/options/tag.twig index 54d4663a0d..bcaec1bf17 100644 --- a/resources/views/reports/options/tag.twig +++ b/resources/views/reports/options/tag.twig @@ -3,7 +3,7 @@
    From df0e2dd2a281c3078dfaf73f0262f2dedc167951 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Aug 2018 06:39:48 +0200 Subject: [PATCH 098/166] Bump version and warn about PHP 7.2 --- config/firefly.php | 4 ++-- config/upgrade.php | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/firefly.php b/config/firefly.php index 99afb8c215..ab4dfec039 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.5.3', - 'api_version' => '0.6', + 'version' => '4.7.6', + 'api_version' => '0.7', 'db_version' => 4, 'maxUploadSize' => 15242880, 'allowedMimes' => [ diff --git a/config/upgrade.php b/config/upgrade.php index 58def7e7f9..d24e17c798 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -30,6 +30,7 @@ return [ '4.6.4' => 'This version of Firefly III requires PHP7.1.', '4.7.3' => 'This version of Firefly III handles bills differently. See http://bit.ly/FF3-new-bills for more information.', '4.7.4' => 'This version of Firefly III has a new import routine. See http://bit.ly/FF3-new-import for more information.', + '4.7.6' => 'This will be the last version to require PHP7.1. Future versions will require PHP7.2 minimum.', ], 'install' => [ @@ -38,6 +39,7 @@ return [ '4.6.4' => 'This version of Firefly III requires PHP7.1.', '4.7.3' => 'This version of Firefly III handles bills differently. See http://bit.ly/FF3-new-bills for more information.', '4.7.4' => 'This version of Firefly III has a new import routine. See http://bit.ly/FF3-new-import for more information.', + '4.7.6' => 'This will be the last version to require PHP7.1. Future versions will require PHP7.2 minimum.', ], ], ]; From f3190053576678da0535771d84e4f13a5656b7fb Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Aug 2018 06:45:57 +0200 Subject: [PATCH 099/166] Fix tests. --- .../Controllers/AvailableBudgetController.php | 4 +- .../V1/Requests/AvailableBudgetRequest.php | 10 ++-- .../Destroy/AccountDestroyService.php | 4 +- .../Import/Routine/Bunq/StageNewHandler.php | 1 - public/js/ff/reports/index.js | 8 ++-- .../AvailableBudgetControllerTest.php | 47 ++++--------------- .../Controllers/JournalLinkControllerTest.php | 22 +++++---- .../Controllers/HelpControllerTest.php | 4 +- .../Controllers/ReportControllerTest.php | 3 +- .../Bunq/ChooseAccountsHandlerTest.php | 10 ++-- .../Routine/Bunq/StageNewHandlerTest.php | 3 +- 11 files changed, 46 insertions(+), 70 deletions(-) diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php index 9ea09b38e0..b91dae8c5f 100644 --- a/app/Api/V1/Controllers/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -152,9 +152,9 @@ class AvailableBudgetController extends Controller public function store(AvailableBudgetRequest $request): JsonResponse { $data = $request->getAll(); - $currency = $this->currencyRepository->findNull($data['transaction_currency_id']); + $currency = $this->currencyRepository->findNull($data['currency_id']); if (null === $currency) { - $this->currencyRepository->findByCodeNull($data['transaction_currency_code']); + $this->currencyRepository->findByCodeNull($data['currency_code']); } if (null === $currency) { throw new FireflyException('Could not find the indicated currency.'); diff --git a/app/Api/V1/Requests/AvailableBudgetRequest.php b/app/Api/V1/Requests/AvailableBudgetRequest.php index 2ad7fe1367..865b2b7fd5 100644 --- a/app/Api/V1/Requests/AvailableBudgetRequest.php +++ b/app/Api/V1/Requests/AvailableBudgetRequest.php @@ -47,11 +47,11 @@ class AvailableBudgetRequest extends Request public function getAll(): array { return [ - 'transaction_currency_id' => $this->integer('currency_id'), - 'transaction_currency_code' => $this->string('currency_code'), - 'amount' => $this->string('amount'), - 'start_date' => $this->date('start_date'), - 'end_date' => $this->date('end_date'), + 'currency_id' => $this->integer('currency_id'), + 'currency_code' => $this->string('currency_code'), + 'amount' => $this->string('amount'), + 'start_date' => $this->date('start_date'), + 'end_date' => $this->date('end_date'), ]; } diff --git a/app/Services/Internal/Destroy/AccountDestroyService.php b/app/Services/Internal/Destroy/AccountDestroyService.php index 674ddc29fc..daf3546323 100644 --- a/app/Services/Internal/Destroy/AccountDestroyService.php +++ b/app/Services/Internal/Destroy/AccountDestroyService.php @@ -51,8 +51,8 @@ class AccountDestroyService DB::table('transactions')->where('account_id', $account->id)->update(['account_id' => $moveTo->id]); // also update recurring transactions: - DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id', $moveTo->id]); - DB::table('recurrences_transactions')->where('destination_id', $account->id)->update(['destination_id', $moveTo->id]); + DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id' => $moveTo->id]); + DB::table('recurrences_transactions')->where('destination_id', $account->id)->update(['destination_id' => $moveTo->id]); } $service = app(JournalDestroyService::class); diff --git a/app/Support/Import/Routine/Bunq/StageNewHandler.php b/app/Support/Import/Routine/Bunq/StageNewHandler.php index 08dc20dd4e..05debeba55 100644 --- a/app/Support/Import/Routine/Bunq/StageNewHandler.php +++ b/app/Support/Import/Routine/Bunq/StageNewHandler.php @@ -68,7 +68,6 @@ class StageNewHandler $config = $this->repository->getConfiguration($this->importJob); $config['accounts'] = $accounts; $this->repository->setConfiguration($this->importJob, $config); - return; } throw new FireflyException('The bunq API context is unexpectedly empty.'); // @codeCoverageIgnore diff --git a/public/js/ff/reports/index.js b/public/js/ff/reports/index.js index 971a31f1f7..6bc886c102 100644 --- a/public/js/ff/reports/index.js +++ b/public/js/ff/reports/index.js @@ -108,7 +108,7 @@ function setOptionalFromCookies() { if ((readCookie('report-categories') !== null)) { arr = readCookie('report-categories').split(','); arr.forEach(function (val) { - $('#inputCategories').find('option[value="' + val + '"]').prop('selected', true); + $('#inputCategories').find('option[value="' + encodeURI(val) + '"]').prop('selected', true); }); } $('#inputCategories').multiselect(defaultMultiSelect); @@ -117,7 +117,7 @@ function setOptionalFromCookies() { if ((readCookie('report-budgets') !== null)) { arr = readCookie('report-budgets').split(','); arr.forEach(function (val) { - $('#inputBudgets').find('option[value="' + val + '"]').prop('selected', true); + $('#inputBudgets').find('option[value="' + encodeURI(val) + '"]').prop('selected', true); }); } $('#inputBudgets').multiselect(defaultMultiSelect); @@ -126,7 +126,7 @@ function setOptionalFromCookies() { if ((readCookie('report-tags') !== null)) { arr = readCookie('report-tags').split(','); arr.forEach(function (val) { - $('#inputTags').find('option[value="' + val + '"]').prop('selected', true); + $('#inputTags').find('option[value="' + encodeURI(val) + '"]').prop('selected', true); }); } $('#inputTags').multiselect(defaultMultiSelect); @@ -135,7 +135,7 @@ function setOptionalFromCookies() { if ((readCookie('report-exp-rev') !== null)) { arr = readCookie('report-exp-rev').split(','); arr.forEach(function (val) { - $('#inputExpRevAccounts').find('option[value="' + val + '"]').prop('selected', true); + $('#inputExpRevAccounts').find('option[value="' + encodeURI(val) + '"]').prop('selected', true); }); } $('#inputExpRevAccounts').multiselect(defaultMultiSelect); diff --git a/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php b/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php index cf1d849e7e..ea52b0aef1 100644 --- a/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php +++ b/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php @@ -133,10 +133,10 @@ class AvailableBudgetControllerTest extends TestCase // data to submit $data = [ - 'transaction_currency_id' => '1', - 'amount' => '100', - 'start_date' => '2018-01-01', - 'end_date' => '2018-01-31', + 'currency_id' => '1', + 'amount' => '100', + 'start_date' => '2018-01-01', + 'end_date' => '2018-01-31', ]; @@ -148,37 +148,6 @@ class AvailableBudgetControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } - /** - * Store new available budget but the budget currency is invalid. - * - * @covers \FireflyIII\Api\V1\Controllers\AvailableBudgetController - * @covers \FireflyIII\Api\V1\Requests\AvailableBudgetRequest - */ - public function testStoreInvalidCurrency(): void - { - // mock stuff: - $repository = $this->mock(BudgetRepositoryInterface::class); - $currencyRepository = $this->mock(CurrencyRepositoryInterface::class); - - // mock calls: - $repository->shouldReceive('setUser')->once(); - $currencyRepository->shouldReceive('findNull')->andReturn(null); - - // data to submit - $data = [ - 'transaction_currency_id' => '1', - 'amount' => '100', - 'start_date' => '2018-01-01', - 'end_date' => '2018-01-31', - ]; - - - // test API - $response = $this->post('/api/v1/available_budgets', $data, ['Accept' => 'application/json']); - $response->assertStatus(500); - $response->assertSee('Could not find the indicated currency.'); - $response->assertHeader('Content-Type', 'application/json'); - } /** * Update available budget. @@ -203,10 +172,10 @@ class AvailableBudgetControllerTest extends TestCase // data to submit $data = [ - 'transaction_currency_id' => '1', - 'amount' => '100', - 'start_date' => '2018-01-01', - 'end_date' => '2018-01-31', + 'currency_id' => '1', + 'amount' => '100', + 'start_date' => '2018-01-01', + 'end_date' => '2018-01-31', ]; // test API diff --git a/tests/Api/V1/Controllers/JournalLinkControllerTest.php b/tests/Api/V1/Controllers/JournalLinkControllerTest.php index ea176dc744..c424ac1144 100644 --- a/tests/Api/V1/Controllers/JournalLinkControllerTest.php +++ b/tests/Api/V1/Controllers/JournalLinkControllerTest.php @@ -158,8 +158,8 @@ class JournalLinkControllerTest extends TestCase $collector = $this->mock(TransactionCollectorInterface::class); // mock calls: - $repository->shouldReceive('setUser')->once(); - $journalRepos->shouldReceive('setUser')->once(); + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser'); $collector->shouldReceive('setUser')->withAnyArgs(); $collector->shouldReceive('setUser')->withAnyArgs(); @@ -171,6 +171,7 @@ class JournalLinkControllerTest extends TestCase $journalRepos->shouldReceive('findNull')->andReturn($journal); $repository->shouldReceive('storeLink')->once()->andReturn($journalLink); + $repository->shouldReceive('findLink')->once()->andReturn(false); // data to submit @@ -206,8 +207,8 @@ class JournalLinkControllerTest extends TestCase $collector = $this->mock(TransactionCollectorInterface::class); // mock calls: - $repository->shouldReceive('setUser')->once(); - $journalRepos->shouldReceive('setUser')->once(); + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser'); $collector->shouldReceive('setUser')->withAnyArgs(); $collector->shouldReceive('setUser')->withAnyArgs(); @@ -230,8 +231,8 @@ class JournalLinkControllerTest extends TestCase // test API $response = $this->post('/api/v1/journal_links', $data, ['Accept' => 'application/json']); - $response->assertStatus(500); - $response->assertSee('Source or destination is NULL.'); // error message + $response->assertStatus(422); + $response->assertSee('Invalid inward ID.'); // error message $response->assertHeader('Content-Type', 'application/json'); } @@ -256,7 +257,7 @@ class JournalLinkControllerTest extends TestCase // mock calls: $repository->shouldReceive('setUser'); - $journalRepos->shouldReceive('setUser')->once(); + $journalRepos->shouldReceive('setUser'); $collector->shouldReceive('setUser')->withAnyArgs(); $collector->shouldReceive('setUser')->withAnyArgs(); @@ -268,6 +269,7 @@ class JournalLinkControllerTest extends TestCase $journalRepos->shouldReceive('findNull')->andReturn($journal); $repository->shouldReceive('updateLink')->once()->andReturn($journalLink); + $repository->shouldReceive('findLink')->once()->andReturn(false); // data to submit $data = [ @@ -305,7 +307,7 @@ class JournalLinkControllerTest extends TestCase // mock calls: $repository->shouldReceive('setUser'); - $journalRepos->shouldReceive('setUser')->once(); + $journalRepos->shouldReceive('setUser'); $collector->shouldReceive('setUser')->withAnyArgs(); $collector->shouldReceive('setUser')->withAnyArgs(); @@ -327,8 +329,8 @@ class JournalLinkControllerTest extends TestCase // test API $response = $this->put('/api/v1/journal_links/' . $journalLink->id, $data, ['Accept' => 'application/json']); - $response->assertStatus(500); - $response->assertSee('Source or destination is NULL.'); // the creation moment. + $response->assertStatus(422); + $response->assertSee('Invalid inward ID.'); // the creation moment. $response->assertHeader('Content-Type', 'application/json'); } } diff --git a/tests/Feature/Controllers/HelpControllerTest.php b/tests/Feature/Controllers/HelpControllerTest.php index e244a6d34c..345984fc98 100644 --- a/tests/Feature/Controllers/HelpControllerTest.php +++ b/tests/Feature/Controllers/HelpControllerTest.php @@ -112,10 +112,12 @@ class HelpControllerTest extends TestCase $help->shouldReceive('inCache')->withArgs(['index', 'en_US'])->andReturn(false)->once(); $help->shouldReceive('getFromGithub')->withArgs(['index', 'en_US'])->andReturn('')->once(); + $help->shouldReceive('putInCache')->once(); + $this->be($this->user()); $response = $this->get(route('help.show', ['index'])); $response->assertStatus(200); - $response->assertSee('Er is geen hulptekst voor deze pagina.'); // Dutch + $response->assertSee('Deze helptekst is nog niet beschikbaar in het Nederlands.'); // Dutch // put English back: Preference::where('user_id', $this->user()->id)->where('name', 'language')->delete(); diff --git a/tests/Feature/Controllers/ReportControllerTest.php b/tests/Feature/Controllers/ReportControllerTest.php index 6a0ded4d20..38f6bf75a4 100644 --- a/tests/Feature/Controllers/ReportControllerTest.php +++ b/tests/Feature/Controllers/ReportControllerTest.php @@ -564,6 +564,7 @@ class ReportControllerTest extends TestCase $journalRepos = $this->mock(JournalRepositoryInterface::class); $categoryRepos = $this->mock(CategoryRepositoryInterface::class); $tagRepos = $this->mock(TagRepositoryInterface::class); + /** @var Tag $tag */ $tag = $this->user()->tags()->find(1); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $accountRepos->shouldReceive('findNull')->andReturn($this->user()->accounts()->find(1))->twice(); @@ -579,7 +580,7 @@ class ReportControllerTest extends TestCase $this->be($this->user()); $response = $this->post(route('reports.index.post'), $data); $response->assertStatus(302); - $response->assertRedirect(route('reports.report.tag', ['1', $tag->tag, '20160101', '20160131'])); + $response->assertRedirect(route('reports.report.tag', ['1', $tag->id, '20160101', '20160131'])); } /** diff --git a/tests/Unit/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandlerTest.php b/tests/Unit/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandlerTest.php index ba705646fa..0014f6a0ff 100644 --- a/tests/Unit/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandlerTest.php +++ b/tests/Unit/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandlerTest.php @@ -138,6 +138,7 @@ class ChooseAccountsHandlerTest extends TestCase ]; $expected = $config; $expected['mapping'][1234] = 456; + $expected['bunq-iban'] = []; // mock stuff $repository = $this->mock(ImportJobRepositoryInterface::class); @@ -148,7 +149,7 @@ class ChooseAccountsHandlerTest extends TestCase $repository->shouldReceive('setUser')->once(); $accountRepos->shouldReceive('setUser')->once(); $currencyRepos->shouldReceive('setUser')->once(); - $repository->shouldReceive('getConfiguration')->andReturn($config)->times(2); + $repository->shouldReceive('getConfiguration')->andReturn($config)->times(3); $repository->shouldReceive('setConfiguration')->withArgs([Mockery::any(), $expected])->once(); $accountRepos->shouldReceive('findNull')->withArgs([456])->andReturn(new Account)->once(); @@ -192,6 +193,7 @@ class ChooseAccountsHandlerTest extends TestCase ]; $expected = $config; $expected['mapping'][0] = 456; + $expected['bunq-iban'] = []; // mock stuff $repository = $this->mock(ImportJobRepositoryInterface::class); @@ -202,7 +204,7 @@ class ChooseAccountsHandlerTest extends TestCase $repository->shouldReceive('setUser')->once(); $accountRepos->shouldReceive('setUser')->once(); $currencyRepos->shouldReceive('setUser')->once(); - $repository->shouldReceive('getConfiguration')->andReturn($config)->times(2); + $repository->shouldReceive('getConfiguration')->andReturn($config)->times(3); $repository->shouldReceive('setConfiguration')->withArgs([Mockery::any(), $expected])->once(); $accountRepos->shouldReceive('findNull')->withArgs([456])->andReturn(new Account)->once(); @@ -246,6 +248,7 @@ class ChooseAccountsHandlerTest extends TestCase ]; $expected = $config; $expected['mapping'][1234] = 0; + $expected['bunq-iban'] = []; // mock stuff $repository = $this->mock(ImportJobRepositoryInterface::class); @@ -256,7 +259,7 @@ class ChooseAccountsHandlerTest extends TestCase $repository->shouldReceive('setUser')->once(); $accountRepos->shouldReceive('setUser')->once(); $currencyRepos->shouldReceive('setUser')->once(); - $repository->shouldReceive('getConfiguration')->andReturn($config)->times(2); + $repository->shouldReceive('getConfiguration')->andReturn($config)->times(3); $repository->shouldReceive('setConfiguration')->withArgs([Mockery::any(), $expected])->once(); $accountRepos->shouldReceive('findNull')->withArgs([456])->andReturnNull()->once(); @@ -291,7 +294,6 @@ class ChooseAccountsHandlerTest extends TestCase 0 => ['id' => 1234, 'name' => 'bunq'], ], 'apply-rules' => true, - ]; // mock stuff diff --git a/tests/Unit/Support/Import/Routine/Bunq/StageNewHandlerTest.php b/tests/Unit/Support/Import/Routine/Bunq/StageNewHandlerTest.php index 6ba32b9bfb..2fe4812a70 100644 --- a/tests/Unit/Support/Import/Routine/Bunq/StageNewHandlerTest.php +++ b/tests/Unit/Support/Import/Routine/Bunq/StageNewHandlerTest.php @@ -97,6 +97,7 @@ class StageNewHandlerTest extends TestCase 'balance' => null, 'status' => null, 'type' => 'MonetaryAccountBank', + 'iban' => 'SM72C9584723533916792029340', 'aliases' => [ [ 'name' => $alias->getName(), @@ -124,8 +125,8 @@ class StageNewHandlerTest extends TestCase $repository->shouldReceive('setUser')->once(); $mAccount->shouldReceive('listing')->andReturn($list)->once(); $repository->shouldReceive('getConfiguration')->once()->andReturn([]); - $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), $expectedConfig]); + $repository->shouldReceive('setConfiguration')->once()->withArgs([Mockery::any(), $expectedConfig]); $handler = new StageNewHandler; $handler->setImportJob($job); From 379c540bd85103c491beffa97f160a5b311c2d2d Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Aug 2018 20:01:46 +0200 Subject: [PATCH 100/166] Update config for logging in Docker. --- .env.docker | 33 +++++++++++++----------- .env.example | 24 ++++++++--------- .env.heroku | 24 ++++++++--------- .env.sandstorm | 24 ++++++++--------- .env.testing | 24 ++++++++++------- app/Http/Controllers/DebugController.php | 4 +-- config/app.php | 2 -- config/logging.php | 7 ++++- resources/views/debug.twig | 2 +- 9 files changed, 75 insertions(+), 69 deletions(-) diff --git a/.env.docker b/.env.docker index 502b81ef49..a3e9fe9573 100644 --- a/.env.docker +++ b/.env.docker @@ -13,32 +13,34 @@ SITE_OWNER=${SITE_OWNER} # Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it APP_KEY=${FF_APP_KEY} +# Change this value to your preferred time zone. +# Example: Europe/Amsterdam +TZ=UTC + # APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. APP_URL=${APP_URL} TRUSTED_PROXIES=${TRUSTED_PROXIES} # The log channel defines where your log entries go to. -LOG_CHANNEL=${LOG_CHANNEL} - -# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III -# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings. -DB_CONNECTION=${FF_DB_CONNECTION} -DB_HOST=${FF_DB_HOST} -DB_PORT=${FF_DB_PORT} -DB_DATABASE=${FF_DB_NAME} -DB_USERNAME=${FF_DB_USER} -DB_PASSWORD=${FF_DB_PASSWORD} - # 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. # Several other options exist. You can use 'single' for one big fat error log (not recommended). -# Also available are 'syslog' and 'errorlog' which will log to the system itself. -APP_LOG=syslog +# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. +LOG_CHANNEL=stdout # Log level. You can set this from least severe to most severe: # debug, info, notice, warning, error, critical, alert, emergency # If you set it to debug your logs will grow large, and fast. If you set it to emergency probably # nothing will get logged, ever. -APP_LOG_LEVEL=info +APP_LOG_LEVEL=notice + +# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III +# For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=homestead +DB_USERNAME=homestead +DB_PASSWORD=secret # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file @@ -103,4 +105,5 @@ IS_DOCKER=true IS_SANDSTORM=false IS_HEROKU=false BUNQ_USE_SANDBOX=false -TZ=${TZ} +MAILGUN_DOMAIN= +MAILGUN_SECRET= diff --git a/.env.example b/.env.example index 73afaf0470..0daa6aee41 100644 --- a/.env.example +++ b/.env.example @@ -15,15 +15,24 @@ APP_KEY=SomeRandomStringOf32CharsExactly # Change this value to your preferred time zone. # Example: Europe/Amsterdam -TZ=UTC +TZ=${TZ} # APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. APP_URL=http://localhost TRUSTED_PROXIES= # The log channel defines where your log entries go to. +# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. +# Several other options exist. You can use 'single' for one big fat error log (not recommended). +# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. LOG_CHANNEL=daily +# Log level. You can set this from least severe to most severe: +# debug, info, notice, warning, error, critical, alert, emergency +# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably +# nothing will get logged, ever. +APP_LOG_LEVEL=notice + # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html DB_CONNECTION=mysql @@ -33,17 +42,6 @@ DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret -# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. -# Several other options exist. You can use 'single' for one big fat error log (not recommended). -# Also available are 'syslog' and 'errorlog' which will log to the system itself. -APP_LOG=daily - -# Log level. You can set this from least severe to most severe: -# debug, info, notice, warning, error, critical, alert, emergency -# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably -# nothing will get logged, ever. -APP_LOG_LEVEL=notice - # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file SESSION_DRIVER=file @@ -105,7 +103,7 @@ DEMO_USERNAME= DEMO_PASSWORD= IS_DOCKER=false IS_SANDSTORM=false -BUNQ_USE_SANDBOX=false IS_HEROKU=false +BUNQ_USE_SANDBOX=false MAILGUN_DOMAIN= MAILGUN_SECRET= diff --git a/.env.heroku b/.env.heroku index 13de7aa0b4..4925830036 100644 --- a/.env.heroku +++ b/.env.heroku @@ -22,7 +22,16 @@ APP_URL=http://localhost TRUSTED_PROXIES= # The log channel defines where your log entries go to. -LOG_CHANNEL=syslog +# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. +# Several other options exist. You can use 'single' for one big fat error log (not recommended). +# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. +LOG_CHANNEL=stdout + +# Log level. You can set this from least severe to most severe: +# debug, info, notice, warning, error, critical, alert, emergency +# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably +# nothing will get logged, ever. +APP_LOG_LEVEL=debug # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # If you use SQLite, set connection to `sqlite` and remove the database, username and password settings. @@ -33,17 +42,6 @@ DB_CONNECTION=pgsql -# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. -# Several other options exist. You can use 'single' for one big fat error log (not recommended). -# Also available are 'syslog' and 'errorlog' which will log to the system itself. -APP_LOG=syslog - -# Log level. You can set this from least severe to most severe: -# debug, info, notice, warning, error, critical, alert, emergency -# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably -# nothing will get logged, ever. -APP_LOG_LEVEL=debug - # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file SESSION_DRIVER=file @@ -105,7 +103,7 @@ DEMO_USERNAME= DEMO_PASSWORD= IS_DOCKER=false IS_SANDSTORM=false -BUNQ_USE_SANDBOX=false IS_HEROKU=true +BUNQ_USE_SANDBOX=false MAILGUN_DOMAIN= MAILGUN_SECRET= diff --git a/.env.sandstorm b/.env.sandstorm index 5895f63467..138001852b 100755 --- a/.env.sandstorm +++ b/.env.sandstorm @@ -22,7 +22,16 @@ APP_URL=http://localhost TRUSTED_PROXIES= # The log channel defines where your log entries go to. -LOG_CHANNEL=syslog +# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. +# Several other options exist. You can use 'single' for one big fat error log (not recommended). +# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. +LOG_CHANNEL=stdout + +# Log level. You can set this from least severe to most severe: +# debug, info, notice, warning, error, critical, alert, emergency +# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably +# nothing will get logged, ever. +APP_LOG_LEVEL=debug # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # If you use SQLite, set connection to `sqlite` and remove the database, username and password settings. @@ -33,17 +42,6 @@ DB_DATABASE=firefly DB_USERNAME=firefly DB_PASSWORD=firefly -# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. -# Several other options exist. You can use 'single' for one big fat error log (not recommended). -# Also available are 'syslog' and 'errorlog' which will log to the system itself. -APP_LOG=syslog - -# Log level. You can set this from least severe to most severe: -# debug, info, notice, warning, error, critical, alert, emergency -# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably -# nothing will get logged, ever. -APP_LOG_LEVEL=info - # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file SESSION_DRIVER=file @@ -105,7 +103,7 @@ DEMO_USERNAME= DEMO_PASSWORD= IS_DOCKER=false IS_SANDSTORM=true -BUNQ_USE_SANDBOX=false IS_HEROKU=false +BUNQ_USE_SANDBOX=false MAILGUN_DOMAIN= MAILGUN_SECRET= diff --git a/.env.testing b/.env.testing index 6eec082239..94958107bb 100644 --- a/.env.testing +++ b/.env.testing @@ -22,16 +22,10 @@ APP_URL=http://localhost TRUSTED_PROXIES= # The log channel defines where your log entries go to. -LOG_CHANNEL=dailytest - -# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III -# For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html -DB_CONNECTION=sqlite - # 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. # Several other options exist. You can use 'single' for one big fat error log (not recommended). -# Also available are 'syslog' and 'errorlog' which will log to the system itself. -APP_LOG=daily +# Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. +LOG_CHANNEL=dailytest # Log level. You can set this from least severe to most severe: # debug, info, notice, warning, error, critical, alert, emergency @@ -39,6 +33,15 @@ APP_LOG=daily # nothing will get logged, ever. APP_LOG_LEVEL=debug +# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III +# For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html +DB_CONNECTION=sqlite + + + + + + # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file SESSION_DRIVER=file @@ -64,6 +67,9 @@ MAILGUN_SECRET= MANDRILL_SECRET= SPARKPOST_SECRET= +# Firefly III can send you the following messages +SEND_REGISTRATION_MAIL=true +SEND_ERROR_MESSAGE=false # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. MAPBOX_API_KEY= @@ -97,7 +103,7 @@ DEMO_USERNAME= DEMO_PASSWORD= IS_DOCKER=false IS_SANDSTORM=false -BUNQ_USE_SANDBOX=true IS_HEROKU=false +BUNQ_USE_SANDBOX=true MAILGUN_DOMAIN= MAILGUN_SECRET= diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 79809ef843..704adde592 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -136,7 +136,7 @@ class DebugController extends Controller $errorReporting = $this->errorReporting((int)ini_get('error_reporting')); $appEnv = env('APP_ENV', ''); $appDebug = var_export(env('APP_DEBUG', false), true); - $appLog = env('APP_LOG', ''); + $logChannel = env('LOG_CHANNEL', ''); $appLogLevel = env('APP_LOG_LEVEL', ''); $packages = $this->collectPackages(); $cacheDriver = env('CACHE_DRIVER', 'unknown'); @@ -175,7 +175,7 @@ class DebugController extends Controller return view( 'debug', compact( - 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'appLog', 'appLogLevel', 'now', 'packages', 'drivers', + 'phpVersion', 'extensions', 'localeAttempts', 'appEnv', 'appDebug', 'logChannel', 'appLogLevel', 'now', 'packages', 'drivers', 'currentDriver', 'userAgent', 'displayErrors', 'errorReporting', 'phpOs', 'interface', 'logContent', 'cacheDriver', 'isDocker', 'isSandstorm', 'trustedProxies', diff --git a/config/app.php b/config/app.php index 5127530c6d..47d80fc8ba 100644 --- a/config/app.php +++ b/config/app.php @@ -32,8 +32,6 @@ return [ 'fallback_locale' => 'en_US', 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', - 'log' => envNonEmpty('APP_LOG', 'errorlog'), - 'log_level' => envNonEmpty('APP_LOG_LEVEL', 'info'), 'providers' => [ /* diff --git a/config/logging.php b/config/logging.php index 3e1dcfd1f3..1ac3fb0284 100644 --- a/config/logging.php +++ b/config/logging.php @@ -59,7 +59,12 @@ return [ 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => envNonEmpty('APP_LOG_LEVEL', 'info'), + ], + 'stdout' => [ + 'driver' => 'single', + 'path' => 'php://stdout', + 'level' => envNonEmpty('APP_LOG_LEVEL', 'info'), ], 'daily' => [ diff --git a/resources/views/debug.twig b/resources/views/debug.twig index 7d57662635..57c1408034 100644 --- a/resources/views/debug.twig +++ b/resources/views/debug.twig @@ -19,7 +19,7 @@ Debug information generated at {{ now }} for Firefly III version **{{ FF_VERSION | App environment | {{ appEnv }} | | App debug mode | {{ appDebug }} | | App cache driver | {{ cacheDriver }} | -| App logging | {{ appLogLevel }}, {{ appLog }} | +| App logging | {{ appLogLevel }}, {{ logChannel }} | | PHP version | {{ phpVersion }} | | Display errors | {{ displayErrors }} | | Session start | {{ session('start') }} | From a8080f55f096571bd4d3a2cc0fc0d42a6999d561 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Aug 2018 21:12:26 +0200 Subject: [PATCH 101/166] Fix docker and entry point. --- .deploy/docker/entrypoint.sh | 2 +- .env.docker | 12 ++++++------ Dockerfile | 2 +- docker-compose.yml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 2a240720ac..3e419fd2eb 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -16,7 +16,7 @@ mkdir -p $FIREFLY_PATH/storage/upload # make sure we own the volumes: -chown -R $APPLICATION_GID:$APPLICATION_UID -R $FIREFLY_PATH/storage +chown -R www-data:www-data -R $FIREFLY_PATH/storage chmod -R 775 $FIREFLY_PATH/storage # remove any lingering files that may break upgrades: diff --git a/.env.docker b/.env.docker index a3e9fe9573..71b52d8f2f 100644 --- a/.env.docker +++ b/.env.docker @@ -35,12 +35,12 @@ APP_LOG_LEVEL=notice # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_DATABASE=homestead -DB_USERNAME=homestead -DB_PASSWORD=secret +DB_CONNECTION=${FF_DB_CONNECTION} +DB_HOST=${FF_DB_HOST} +DB_PORT=${FF_DB_PORT} +DB_DATABASE=${FF_DB_NAME} +DB_USERNAME=${FF_DB_USER} +DB_PASSWORD=${FF_DB_PASSWORD} # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file diff --git a/Dockerfile b/Dockerfile index b585ebe302..f9cf575904 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,7 +82,7 @@ VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf # Make sure we own Firefly III directory -RUN chown -R $APPLICATION_GID:$APPLICATION_UID /var/www && chmod -R 775 $FIREFLY_PATH/storage +RUN chown -R www-data:www-data /var/www && chmod -R 775 $FIREFLY_PATH/storage # Copy in Firefly Source WORKDIR $FIREFLY_PATH diff --git a/docker-compose.yml b/docker-compose.yml index 28210fcf1b..655086a555 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: - FF_APP_KEY=S0m3R@nd0mStr1ngOf32Ch@rsEx@ctly - FF_APP_ENV=local - TZ=Europe/Amsterdam - image: jc5x/firefly-iii + image: jc5x/firefly-iii:develop links: - firefly_iii_db networks: From 81d17409d4e87f29d8c2e770737b7b2a9fca618f Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Aug 2018 21:51:15 +0200 Subject: [PATCH 102/166] Fix some things with the update checker. --- .env.docker | 2 +- app/Handlers/Events/VersionCheckEventHandler.php | 10 +++++++--- app/Helpers/Update/UpdateTrait.php | 13 +++++++++++++ app/Http/Controllers/DebugController.php | 4 ++-- app/Support/FireflyConfig.php | 4 +++- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.env.docker b/.env.docker index 71b52d8f2f..2ac31ed6a7 100644 --- a/.env.docker +++ b/.env.docker @@ -31,7 +31,7 @@ LOG_CHANNEL=stdout # debug, info, notice, warning, error, critical, alert, emergency # If you set it to debug your logs will grow large, and fast. If you set it to emergency probably # nothing will get logged, ever. -APP_LOG_LEVEL=notice +APP_LOG_LEVEL=${APP_LOG_LEVEL} # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html diff --git a/app/Handlers/Events/VersionCheckEventHandler.php b/app/Handlers/Events/VersionCheckEventHandler.php index 6213a10f8c..e7d893cefa 100644 --- a/app/Handlers/Events/VersionCheckEventHandler.php +++ b/app/Handlers/Events/VersionCheckEventHandler.php @@ -28,6 +28,7 @@ namespace FireflyIII\Handlers\Events; use FireflyConfig; use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Helpers\Update\UpdateTrait; +use FireflyIII\Models\Configuration; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Log; @@ -50,9 +51,11 @@ class VersionCheckEventHandler */ public function checkForUpdates(RequestedVersionCheckStatus $event): void { + Log::debug('Now in checkForUpdates()'); // in Sandstorm, cannot check for updates: $sandstorm = 1 === (int)getenv('SANDSTORM'); if (true === $sandstorm) { + Log::debug('This is Sandstorm instance, done.'); return; // @codeCoverageIgnore } @@ -61,18 +64,19 @@ class VersionCheckEventHandler /** @var User $user */ $user = $event->user; if (!$repository->hasRole($user, 'owner')) { + Log::debug('User is not admin, done.'); return; } + /** @var Configuration $lastCheckTime */ $lastCheckTime = FireflyConfig::get('last_update_check', time()); $now = time(); $diff = $now - $lastCheckTime->data; - Log::debug(sprintf('Difference is %d seconds.', $diff)); + Log::debug(sprintf('Last check time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff)); if ($diff < 604800) { Log::debug(sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data))); - //return; - + return; } // last check time was more than a week ago. Log::debug('Have not checked for a new version in a week!'); diff --git a/app/Helpers/Update/UpdateTrait.php b/app/Helpers/Update/UpdateTrait.php index 775c44e186..e01d1b4891 100644 --- a/app/Helpers/Update/UpdateTrait.php +++ b/app/Helpers/Update/UpdateTrait.php @@ -42,6 +42,7 @@ trait UpdateTrait */ public function getLatestRelease(): ?Release { + Log::debug('Now in getLatestRelease()'); $return = null; /** @var UpdateRequest $request */ $request = app(UpdateRequest::class); @@ -53,11 +54,15 @@ trait UpdateTrait // get releases from array. $releases = $request->getReleases(); + + Log::debug(sprintf('Found %d releases', \count($releases))); + if (\count($releases) > 0) { // first entry should be the latest entry: /** @var Release $first */ $first = reset($releases); $return = $first; + Log::debug(sprintf('Number of releases found is larger than zero. Return %s ', $first->getTitle())); } return $return; @@ -73,17 +78,21 @@ trait UpdateTrait */ public function parseResult(int $versionCheck, Release $release = null): string { + Log::debug(sprintf('Now in parseResult(%d)', $versionCheck)); $current = (string)config('firefly.version'); $return = ''; if ($versionCheck === -2) { + Log::debug('-2, so give error.'); $return = (string)trans('firefly.update_check_error'); } if ($versionCheck === -1 && null !== $release) { + Log::debug('New version!'); // there is a new FF version! // has it been released for at least three days? $today = new Carbon; $releaseDate = $release->getUpdated(); if ($today->diffInDays($releaseDate, true) > 3) { + Log::debug('New version is older than 3 days!'); $monthAndDayFormat = (string)trans('config.month_and_day'); $return = (string)trans( 'firefly.update_new_version_alert', @@ -97,10 +106,12 @@ trait UpdateTrait } if (0 === $versionCheck) { + Log::debug('User is running current version.'); // you are running the current version! $return = (string)trans('firefly.update_current_version_alert', ['version' => $current]); } if (1 === $versionCheck && null !== $release) { + Log::debug('User is running NEWER version.'); // you are running a newer version! $return = (string)trans('firefly.update_newer_version_alert', ['your_version' => $current, 'new_version' => $release->getTitle()]); } @@ -117,7 +128,9 @@ trait UpdateTrait */ public function versionCheck(Release $release = null): int { + Log::debug('Now in versionCheck()'); if (null === $release) { + Log::debug('Release is null, return -2.'); return -2; } $current = (string)config('firefly.version'); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 704adde592..1a8c7ba20a 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -136,8 +136,8 @@ class DebugController extends Controller $errorReporting = $this->errorReporting((int)ini_get('error_reporting')); $appEnv = env('APP_ENV', ''); $appDebug = var_export(env('APP_DEBUG', false), true); - $logChannel = env('LOG_CHANNEL', ''); - $appLogLevel = env('APP_LOG_LEVEL', ''); + $logChannel = env('LOG_CHANNEL', ''); + $appLogLevel = env('APP_LOG_LEVEL', 'info'); $packages = $this->collectPackages(); $cacheDriver = env('CACHE_DRIVER', 'unknown'); diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index 1c9a1c9d3c..debd591f77 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -118,9 +118,11 @@ class FireflyConfig public function set(string $name, $value): Configuration { Log::debug('Set new value for ', ['name' => $name]); + /** @var Configuration $config */ $config = Configuration::whereName($name)->first(); if (null === $config) { Log::debug('Does not exist yet ', ['name' => $name]); + /** @var Configuration $item */ $item = new Configuration; $item->name = $name; $item->data = $value; @@ -130,7 +132,7 @@ class FireflyConfig return $item; } - Log::debug('Exists already ', ['name' => $name]); + Log::debug('Exists already, overwrite value.', ['name' => $name]); $config->data = $value; $config->save(); Cache::forget('ff-config-' . $name); From 35bacf2ad05a9e72681189aac67f59af4c4175f2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 05:21:47 +0200 Subject: [PATCH 103/166] Update docker file to match postgres. --- docker-compose.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 655086a555..8175a631d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,12 +6,14 @@ services: firefly_iii_app: environment: - FF_DB_HOST=firefly_iii_db - - FF_DB_NAME=firefly_db - - FF_DB_USER=firefly_db - - FF_DB_PASSWORD=firefly_db_secret + - FF_DB_NAME=firefly + - FF_DB_USER=firefly + - FF_DB_PASSWORD=firefly - FF_APP_KEY=S0m3R@nd0mStr1ngOf32Ch@rsEx@ctly - FF_APP_ENV=local + - FF_DB_CONNECTION=pgsql - TZ=Europe/Amsterdam + - APP_LOG_LEVEL=debug image: jc5x/firefly-iii:develop links: - firefly_iii_db @@ -30,15 +32,13 @@ services: type: volume firefly_iii_db: environment: - - MYSQL_DATABASE=firefly_db - - MYSQL_USER=firefly_db - - MYSQL_PASSWORD=firefly_db_secret - - MYSQL_RANDOM_ROOT_PASSWORD=yes - image: "mariadb:latest" + - POSTGRES_PASSWORD=firefly + - POSTGRES_USER=firefly + image: "postgres:latest" networks: - firefly_iii_net volumes: - - "firefly_iii_db:/var/lib/mysql" + - "firefly_iii_db:/var/lib/postgresql/data" version: "3.2" volumes: firefly_iii_db: ~ From 004807aa32ba67842d40745f53a31d6f45989ebb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 14:08:28 +0200 Subject: [PATCH 104/166] Update cron file, use supervisor. --- .deploy/docker/cronjob.conf | 11 +++++++++++ .deploy/docker/crontab | 1 - .deploy/docker/entrypoint.sh | 4 +--- .deploy/docker/firefly-iii.conf | 6 ++++++ .deploy/docker/supervisord.conf | 30 ++++++++++++++++++++++++++++++ Dockerfile | 19 ++++++++++++++----- app/Console/Kernel.php | 32 +++++++++++++++++--------------- 7 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 .deploy/docker/cronjob.conf delete mode 100644 .deploy/docker/crontab create mode 100644 .deploy/docker/firefly-iii.conf create mode 100644 .deploy/docker/supervisord.conf diff --git a/.deploy/docker/cronjob.conf b/.deploy/docker/cronjob.conf new file mode 100644 index 0000000000..71aacd84eb --- /dev/null +++ b/.deploy/docker/cronjob.conf @@ -0,0 +1,11 @@ +[program:cron] +command=/usr/sbin/cron -f -L 15 +user=root +autostart=true +autorestart=true +stdout_events_enabled=true +stderr_events_enabled=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +startsecs=10 +startretries=3 diff --git a/.deploy/docker/crontab b/.deploy/docker/crontab deleted file mode 100644 index a039a6bc84..0000000000 --- a/.deploy/docker/crontab +++ /dev/null @@ -1 +0,0 @@ -* * * * * root /artisan schedule:run >> /var/log/cron.log diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 3e419fd2eb..ff808c1990 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -26,6 +26,4 @@ cat .env.docker | envsubst > .env composer dump-autoload php artisan package:discover php artisan firefly:instructions install -service rsyslog start -service cron start -exec apache2-foreground +exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf --nodaemon \ No newline at end of file diff --git a/.deploy/docker/firefly-iii.conf b/.deploy/docker/firefly-iii.conf new file mode 100644 index 0000000000..5ee1c2eb28 --- /dev/null +++ b/.deploy/docker/firefly-iii.conf @@ -0,0 +1,6 @@ +[program:apache2] +command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND" +stdout_events_enabled=true +stderr_events_enabled=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 \ No newline at end of file diff --git a/.deploy/docker/supervisord.conf b/.deploy/docker/supervisord.conf new file mode 100644 index 0000000000..479120ed38 --- /dev/null +++ b/.deploy/docker/supervisord.conf @@ -0,0 +1,30 @@ +# supervisor config file +# Adapted from the config file distributed with the supervisor package on Debian +# Jessie + +# Enable supervisord in non-daemon mode. Disable the logfile as we receive +# log messages via stdout/err. Set up the child process log directory in case +# the user doesn't set logging to stdout/err. +[supervisord] +nodaemon = true +logfile = NONE +pidfile = /var/run/supervisord.pid +childlogdir = /var/log/supervisor +loglevel=debug + +# Enable supervisorctl via RPC interface over Unix socket +[unix_http_server] +file = /var/run/supervisor.sock +chmod = 0700 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl = unix:///var/run/supervisor.sock + + +# Include conf files for child processes +# Debian/Ubuntu packages use /etc/supervisor/conf.d +[include] +files = /etc/supervisor/conf.d/*.conf \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f9cf575904..06c3345ca7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,12 +20,15 @@ RUN apt-get update -y && \ libedit-dev \ libtidy-dev \ libxml2-dev \ + unzip \ libsqlite3-dev \ + nano \ libpq-dev \ libbz2-dev \ gettext-base \ cron \ rsyslog \ + supervisor \ locales && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -50,12 +53,18 @@ RUN cd /tmp && \ # Make sure that libcurl is using the newer curl libaries RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/00-curl.conf && ldconfig -# Create the log file to be able to run tail -RUN touch /var/log/cron.log +# Mimic the Debian/Ubuntu config file structure for supervisor +COPY .deploy/docker/supervisord.conf /etc/supervisor/supervisord.conf +RUN mkdir -p /etc/supervisor/conf.d /var/log/supervisor -# Setup cron job -COPY .deploy/docker/crontab /etc/cron.d/crontab -RUN chmod 0644 /etc/cron.d/crontab +# copy Firefly III supervisor conf file. +COPY ./.deploy/docker/firefly-iii.conf /etc/supervisor/conf.d/firefly-iii.conf + +# copy cron job supervisor conf file. +COPY ./.deploy/docker/cronjob.conf /etc/supervisor/conf.d/cronjob.conf + +# test crons added via crontab +RUN echo "0 3 * * * cd /var/www/firefly-iii && php artisan firefly:cron >> /dev/stdout 2>&1" | crontab - # Install PHP exentions. RUN docker-php-ext-install -j$(nproc) gd intl tidy zip bcmath pdo_mysql bz2 pdo_pgsql diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 1e4f9eb172..250b83f5bc 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -24,10 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Console; -use Carbon\Carbon; -use FireflyIII\Jobs\CreateRecurringTransactions; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Log; /** * File to make sure commands work. @@ -52,18 +51,21 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { - $schedule->call(function() { - echo "\n"; - echo '------------'; - echo "\n"; - echo wordwrap('Firefly III no longer users the Laravel scheduler to do cron jobs! Please read the instructions here:'); - echo "\n"; - echo 'https://firefly-iii.readthedocs.io/en/latest/'; - echo "\n\n"; - echo 'Disable this cron job!'; - echo "\n"; - echo '------------'; - echo "\n"; - })->everyMinute(); + $schedule->call( + function () { + Log::error('Firefly III no longer users the Laravel scheduler to do cron jobs! Please read the instructions at https://firefly-iii.readthedocs.io/en/latest/'); + echo "\n"; + echo '------------'; + echo "\n"; + echo wordwrap('Firefly III no longer users the Laravel scheduler to do cron jobs! Please read the instructions here:'); + echo "\n"; + echo 'https://firefly-iii.readthedocs.io/en/latest/'; + echo "\n\n"; + echo 'Disable this cron job!'; + echo "\n"; + echo '------------'; + echo "\n"; + } + )->everyMinute(); } } From 90bf2e58b2b31fe973ea77913f242627f4497a50 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 14:12:39 +0200 Subject: [PATCH 105/166] Fixes #1620 --- app/Factory/BillFactory.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php index a3de276298..c08cea5a8d 100644 --- a/app/Factory/BillFactory.php +++ b/app/Factory/BillFactory.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Factory; use FireflyIII\Models\Bill; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Services\Internal\Support\BillServiceTrait; use FireflyIII\User; use Illuminate\Support\Collection; @@ -46,6 +47,10 @@ class BillFactory */ public function create(array $data): ?Bill { + /** @var TransactionCurrencyFactory $factory */ + $factory = app(TransactionCurrencyFactory::class); + /** @var TransactionCurrency $currency */ + $currency = $factory->find((int)$data['currency_id'], (string)$data['currency_code']); /** @var Bill $bill */ $bill = Bill::create( [ @@ -53,7 +58,7 @@ class BillFactory 'match' => 'MIGRATED_TO_RULES', 'amount_min' => $data['amount_min'], 'user_id' => $this->user->id, - 'transaction_currency_id' => $data['transaction_currency_id'], + 'transaction_currency_id' => $currency->id, 'amount_max' => $data['amount_max'], 'date' => $data['date'], 'repeat_freq' => $data['repeat_freq'], From ba0990122810845d9e948b4e82a904526d6ed8f7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 20:11:12 +0200 Subject: [PATCH 106/166] fix #1615 --- 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 65e861c5d9..7cedd16a18 100644 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ b/app/Http/Requests/SplitJournalFormRequest.php @@ -95,7 +95,7 @@ class SplitJournalFormRequest extends Request 'foreign_currency_code' => null, 'reconciled' => false, 'identifier' => $index, - 'currency_id' => $this->integer('journal_currency_id'), + 'currency_id' => (int)$transaction['transaction_currency_id'], 'currency_code' => null, 'description' => $transaction['transaction_description'] ?? '', 'amount' => $transaction['amount'] ?? '', From 21bff39e3117d94c95b22f61a43539987b24e26c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 20:13:26 +0200 Subject: [PATCH 107/166] Fix #1620 --- app/Http/Requests/BillFormRequest.php | 18 +++++++++--------- .../Internal/Update/BillUpdateService.php | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index 8780a4bc4f..25066ad7a1 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -48,15 +48,15 @@ class BillFormRequest extends Request public function getBillData(): array { return [ - 'name' => $this->string('name'), - 'amount_min' => $this->string('amount_min'), - 'transaction_currency_id' => $this->integer('transaction_currency_id'), - 'amount_max' => $this->string('amount_max'), - 'date' => $this->date('date'), - 'repeat_freq' => $this->string('repeat_freq'), - 'skip' => $this->integer('skip'), - 'notes' => $this->string('notes'), - 'active' => $this->boolean('active'), + 'name' => $this->string('name'), + 'amount_min' => $this->string('amount_min'), + 'currency_id' => $this->integer('transaction_currency_id'), + 'amount_max' => $this->string('amount_max'), + 'date' => $this->date('date'), + 'repeat_freq' => $this->string('repeat_freq'), + 'skip' => $this->integer('skip'), + 'notes' => $this->string('notes'), + 'active' => $this->boolean('active'), ]; } diff --git a/app/Services/Internal/Update/BillUpdateService.php b/app/Services/Internal/Update/BillUpdateService.php index 1c849d4fb4..0450e6e90c 100644 --- a/app/Services/Internal/Update/BillUpdateService.php +++ b/app/Services/Internal/Update/BillUpdateService.php @@ -47,7 +47,7 @@ class BillUpdateService $bill->amount_min = $data['amount_min']; $bill->amount_max = $data['amount_max']; $bill->date = $data['date']; - $bill->transaction_currency_id = $data['transaction_currency_id']; + $bill->transaction_currency_id = $data['currency_id']; $bill->repeat_freq = $data['repeat_freq']; $bill->skip = $data['skip']; $bill->automatch = true; @@ -55,7 +55,7 @@ class BillUpdateService $bill->save(); // update note: - if (isset($data['notes']) && null !== $data['notes']) { + if (isset($data['notes'])) { $this->updateNote($bill, (string)$data['notes']); } From 7689b7b4b0784e4a88812d328885b4dc6e1d9b51 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 20:13:52 +0200 Subject: [PATCH 108/166] Forgot to push this part of the fix for #1615 --- resources/views/transactions/split/edit.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/transactions/split/edit.twig b/resources/views/transactions/split/edit.twig index d8c53af0e8..9eec4de223 100644 --- a/resources/views/transactions/split/edit.twig +++ b/resources/views/transactions/split/edit.twig @@ -264,7 +264,7 @@ value="{{ transaction.foreign_currency_id }}"> {% endif %} + value="{{ transaction.currency_id }}"> {# budget #} {% if preFilled.what == 'withdrawal' %} From 0c3a580b3343549cd487c0fa9edb1756d5233db8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Aug 2018 22:11:31 +0200 Subject: [PATCH 109/166] Fix issues with italian translation, add new translations [skip ci] --- resources/lang/de_DE/firefly.php | 6 ++++-- resources/lang/es_ES/firefly.php | 12 +++++++----- resources/lang/es_ES/intro.php | 4 ++-- resources/lang/es_ES/list.php | 2 +- resources/lang/fr_FR/firefly.php | 4 +++- resources/lang/id_ID/firefly.php | 4 +++- resources/lang/it_IT/demo.php | 4 ++-- resources/lang/it_IT/firefly.php | 8 +++++--- resources/lang/nl_NL/firefly.php | 4 +++- resources/lang/pl_PL/firefly.php | 4 +++- resources/lang/pt_BR/firefly.php | 4 +++- resources/lang/ru_RU/firefly.php | 10 ++++++---- resources/lang/ru_RU/list.php | 8 ++++---- resources/lang/ru_RU/validation.php | 12 ++++++------ resources/lang/tr_TR/firefly.php | 4 +++- 15 files changed, 55 insertions(+), 35 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 60b4606758..8821393ef9 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -37,7 +37,7 @@ return [ 'cancel' => 'Abbrechen', 'from' => 'Von', 'to' => 'Bis', - 'help_translating' => 'Dieser Hilfetext ist noch nicht in Ihrer Sprache verfügbar. Möchten Sie beim Übersetzen helfen?', + 'help_translating' => 'Dieser Hilfetext ist noch nicht in Deutsch verfügbar. Möchten Sie beim Übersetzen helfen?', 'showEverything' => 'Alles anzeigen', 'never' => 'Nie', 'no_results_for_empty_search' => 'Ihre Suche war leer, also wurde nichts gefunden.', @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days Tage Daten können eine Weile zu laden benötigen.', 'registered' => 'Sie haben sich erfolgreich registriert!', 'Default asset account' => 'Standard-Bestandskonto', - 'no_budget_pointer' => 'Sie scheinen noch keine Kostenrahmen festgelegt zu haben. Sie sollten einige davon auf der Seite „Kostenrahmen” anlegen. Kostenrahmen können Ihnen dabei helfen, den Überblick über die Ausgaben zu behalten.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Sparkonto', 'Credit card' => 'Kreditkarte', 'source_accounts' => 'Herkunftskonto', @@ -1267,6 +1267,8 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'created_Deposits' => 'Erstellte Einzahlungen', 'created_Transfers' => 'Erstellte Überweisungen', 'created_from_recurrence' => 'Erstellt aus Dauerauftrag „:title” (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Schlagwörter', 'recurring_meta_field_notes' => 'Anmerkungen', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 349c0ab0ff..b49bce607e 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -37,7 +37,7 @@ return [ 'cancel' => 'Cancelar', 'from' => 'Desde', 'to' => 'Hasta', - 'help_translating' => 'This help text is not yet available in your language. Will you help translate?', + 'help_translating' => 'Este texto de ayuda no está disponible en tu idioma. ¿Nos ayudaría a traducir?', 'showEverything' => 'Mostrar todo', 'never' => 'Nunca', 'no_results_for_empty_search' => 'Su búsqueda estaba vacía, por lo que no se encontró nada.', @@ -61,7 +61,7 @@ return [ 'new_asset_account' => 'Nueva cuenta de activo', 'new_expense_account' => 'Nueva cuenta de gastos', 'new_revenue_account' => 'Nueva cuenta de ingresos', - 'new_liabilities_account' => 'New liability', + 'new_liabilities_account' => 'Nueva Responsabilidad (pasivo)', 'new_budget' => 'Nuevo presupuesto', 'new_bill' => 'Nueva factura', 'block_account_logout' => 'Tu sesión ha sido cerrada. Las cuentas bloqueadas no pueden utilizar este sitio. ¿Registrarte con una dirección válida de correo electrónico?', @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days días de datos pueden tomar tiempo en cargarse.', 'registered' => '¡Te has registrado con éxito!', 'Default asset account' => 'Cuenta de ingresos por defecto', - 'no_budget_pointer' => 'Parece que aún no tienes presupuestos. Debe crear algunos en la página presupuestos. Los presupuestos pueden ayudarle a realizar un seguimiento de los gastos.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Cuenta de ahorros', 'Credit card' => 'Tarjeta de crédito', 'source_accounts' => 'Cuenta(s) origen', @@ -270,8 +270,8 @@ return [ 'move_rule_group_up' => 'Mover la regla hacia arriba', 'move_rule_group_down' => 'Mover el grupo de reglas hacia abajo', 'save_rules_by_moving' => 'Guardar esta(s) regla(s) moviéndola(s) a otro grupo:', - 'make_new_rule' => 'Make a new rule in rule group ":title"', - 'make_new_rule_no_group' => 'Make a new rule', + 'make_new_rule' => 'Crea una nueva regla en el grupo ":title"', + 'make_new_rule_no_group' => 'Crea una nueva regla', 'instructions_rule_from_bill' => 'In order to match transactions to your new bill ":name", Firefly III can create a rule that will automatically be checked against any transactions you store. Please verify the details below and store the rule to have Firefly III automatically match transactions to your new bill.', 'rule_is_strict' => 'regla estricta', 'rule_is_not_strict' => 'regla no estricta', @@ -1267,6 +1267,8 @@ return [ 'created_Deposits' => 'Created deposits', 'created_Transfers' => 'Transferencias creadas', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Etiquetas', 'recurring_meta_field_notes' => 'Notas', diff --git a/resources/lang/es_ES/intro.php b/resources/lang/es_ES/intro.php index 85e0787ae5..619ddbafae 100644 --- a/resources/lang/es_ES/intro.php +++ b/resources/lang/es_ES/intro.php @@ -103,12 +103,12 @@ return [ 'bills_show_billChart' => 'Este gráfico muestra las transacciones vinculadas con esta factura.', // 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' => 'Use cuentas para rastrear la cantidad de dinero correspondiente a cada periodo. Piense en gastos como renta, seguro o pagos de credito de vivienda.', 'bills_create_name' => 'Use un nombre descriptivo como "alquiler" o "seguro de salud".', //'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' => 'Seleccione un monto mínimo y uno máximo para esta factura.', 'bills_create_repeat_freq_holder' => 'Muchas facturas se repiten mensualmente, pero usted puede establecer otra frecuencia aquí.', - '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 una cuenta se repite cada 2 semanas, el campo "saltar" debe estar marcado como "1" para saltar una semana y generar el gasto cada 2.', // rules index 'rules_index_intro' => 'Firefly III le permite administrar reglas, que automáticamente se aplicaran para cualquier transacción que cree o edite.', diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index 9c6af308b0..82a1c6ab7b 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -25,7 +25,7 @@ declare(strict_types=1); return [ 'buttons' => 'Botones', 'icon' => 'Icono', - 'id' => 'Identificación', + 'id' => 'ID', 'create_date' => 'Fecha de creación', 'update_date' => 'Fecha de modificación', 'updated_at' => 'Actualizado en', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 0d6cf4d659..85347ee39a 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days jours de données peuvent prendre un certain temps à charger.', 'registered' => 'Vous avez été enregistré avec succès !', 'Default asset account' => 'Compte d’actif par défaut', - 'no_budget_pointer' => 'Vous semblez n’avoir encore aucun budget. Vous devriez en créer un sur la page des budgets. Les budgets peuvent vous aider à garder une trace des dépenses.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Compte d’épargne', 'Credit card' => 'Carte de Crédit', 'source_accounts' => 'Compte(s) source', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Revenus créés', 'created_Transfers' => 'Transferts créés', 'created_from_recurrence' => 'Créé à partir de l\'opération périodique ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Tags', 'recurring_meta_field_notes' => 'Notes', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index c1a7e165c3..251571fd09 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days hari data mungkin perlu beberapa saat untuk memuat.', 'registered' => 'Anda telah berhasil mendaftar!', 'Default asset account' => 'Akun aset standar', - 'no_budget_pointer' => 'Anda tampaknya belum memiliki anggaran. Anda harus membuat beberapa di halaman budgets. Anggaran dapat membantu Anda melacak pengeluaran.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Rekening tabungan', 'Credit card' => 'Kartu kredit', 'source_accounts' => 'Akun sumber', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Created deposits', 'created_Transfers' => 'Created transfers', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Tags', 'recurring_meta_field_notes' => 'Notes', diff --git a/resources/lang/it_IT/demo.php b/resources/lang/it_IT/demo.php index 3a3f7fda89..f96a6e8e8a 100644 --- a/resources/lang/it_IT/demo.php +++ b/resources/lang/it_IT/demo.php @@ -24,12 +24,12 @@ 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ù.', + '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 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 .', + 'reports-index-examples' => 'Assicurati di dare un occhiata a questi esempi: una panoramica finanziaria mensile , una panoramica finanziaria annuale e una panoramica del budget .', 'currencies-index' => 'Firefly III supporta più valute. Sebbene sia impostato su Euro, può essere impostato sul dollaro USA e su molte altre valute. Come puoi vedere, è stata inclusa una piccola selezione di valute, ma puoi aggiungere la tua se lo desideri. Tuttavia, la modifica della valuta predefinita non cambierà la valuta delle transazioni esistenti: Firefly III supporta un uso di più valute allo stesso tempo.', '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.', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 1ebc96fe95..d3b555c451 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -37,7 +37,7 @@ return [ 'cancel' => 'Annulla', 'from' => 'Da', 'to' => 'A', - 'help_translating' => 'Questo testo di aiuto non è ancora disponibile nella tua lingua. Vuoi aiutare con la traduzione?', + 'help_translating' => 'Questo testo di aiuto non è ancora disponibile in italiano. Vuoi aiutare con la traduzione?', 'showEverything' => 'Mostra tutto', 'never' => 'Mai', 'no_results_for_empty_search' => 'Chiave di ricerca vuota, quindi non è stato trovato nulla.', @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days di caricamento dei dati potrebbero richiedere un pò di tempo.', 'registered' => 'Ti sei registrato con successo!', '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.', + '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' => 'Conti origine', @@ -159,7 +159,7 @@ return [ 'not_available_demo_user' => 'La funzione a cui tenti di accedere non è disponibile per gli utenti demo.', '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', + 'transaction_data' => 'Informazioni transazione', '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', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Depositi creati', 'created_Transfers' => 'Trasferimenti creati', 'created_from_recurrence' => 'Creata dalla transazione ricorrente ":title" (#:id)', + 'recurring_never_cron' => 'Sembra che il job cron necessario per le transazioni ricorrenti non sia mai stato eseguito. Questo è ovviamente normale quando hai appena installato Firefly III, tuttavia dovrebbe essere impostato il prima possibile. Consulta le pagine di aiuto usando l\'icona (?) nell\'angolo in alto a destra della pagina.', + 'recurring_cron_long_ago' => 'Sembra che siano passate più di 36 ore dall\'ultima volta che il job cron per le transazioni ricorrenti sia stato lanciato. Sei sicuro che sia stato impostato correttamente? Consulta le pagine di aiuto usando l\'icona (?) nell\'angolo in alto a destra della pagina.', 'recurring_meta_field_tags' => 'Etichette', 'recurring_meta_field_notes' => 'Note', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 234fd8c45c..c3e3eff26b 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => 'Het kan even duren voor :days dagen aan gegevens geladen zijn.', 'registered' => 'Je bent geregistreerd!', 'Default asset account' => 'Standaard betaalrekening', - 'no_budget_pointer' => 'Je hebt nog geen budgetten. Maak er een aantal op de budgetten-pagina. Met budgetten kan je je uitgaven beter bijhouden.', + 'no_budget_pointer' => 'Je hebt nog geen budgetten. Maak er een aantal op de budgetten-pagina. Met budgetten kan je je uitgaven beter bijhouden.', 'Savings account' => 'Spaarrekening', 'Credit card' => 'Credit card', 'source_accounts' => 'Bronrekening(en)', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Gemaakte inkomsten', 'created_Transfers' => 'Gemaakte overschrijvingen', 'created_from_recurrence' => 'Gemaakt door periodieke transactie ":title" (#:id)', + 'recurring_never_cron' => 'Het lijkt er op dat je cronjob die nodig is voor periodieke transacties nog nooit gedraaid heeft. Niet zo gek natuurlijk als je Firefly III echt net geïnstalleerd hebt, maar denk eraan dat je dit regelt. Check de helppagina\'s via het (?)-icoontje rechtsboven.', + 'recurring_cron_long_ago' => 'Het lijkt er op dat het meer dan 36 uur geleden is sinds de cronjob heeft gedraaid die je nodig hebt voor het maken van periodieke transacties. Weet je zeker dat deze goed is ingesteld? Check de helppagina\'s onder het (?)-icoontje rechtsboven voor meer informatie.', 'recurring_meta_field_tags' => 'Tags', 'recurring_meta_field_notes' => 'Notities', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index dee1056823..c7de3342a4 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => 'Załadowanie danych z :days dni może trochę potrwać.', 'registered' => 'Zarejestrowałeś się pomyślnie!', 'Default asset account' => 'Domyślne konto aktywów', - 'no_budget_pointer' => 'Wygląda na to że nie masz jeszcze budżetów. Powinieneś utworzyć kilka na stronie budżety. Budżety mogą Ci pomóc śledzić wydatki.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Konto oszczędnościowe', 'Credit card' => 'Karta kredytowa', 'source_accounts' => 'Konto(a) źródłowe', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Utworzone wpłaty', 'created_Transfers' => 'Utworzone transfery', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Tagi', 'recurring_meta_field_notes' => 'Notatki', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index e460747b30..31b624469d 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days dias de dados podem demorar um pouco para carregar.', 'registered' => 'Você se registrou com sucesso!', 'Default asset account' => 'Conta padrão', - 'no_budget_pointer' => 'Parece que não há orçamentos ainda. Você deve criar alguns na página orçamentos. Orçamentos podem ajudá-lo a manter o controle de despesas.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Conta poupança', 'Credit card' => 'Cartão de crédito', 'source_accounts' => 'Conta(s) de origem', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Created deposits', 'created_Transfers' => 'Created transfers', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Tags', 'recurring_meta_field_notes' => 'Notes', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index e12bb21602..c02e171393 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -37,7 +37,7 @@ return [ 'cancel' => 'Отмена', 'from' => 'От', 'to' => 'Куда', - 'help_translating' => 'This help text is not yet available in your language. Will you help translate?', + 'help_translating' => 'Этот текст справки пока не доступен на вашем языке. Но вы можете помочь с переводом.', 'showEverything' => 'Показать всё', 'never' => 'Никогда', 'no_results_for_empty_search' => 'Результатов не найдено.', @@ -90,7 +90,7 @@ return [ 'warning_much_data' => 'Загрузка данных за :days дней может занять некоторое время.', 'registered' => 'Вы зарегистрировались успешно!', 'Default asset account' => 'Счёт по умолчанию', - 'no_budget_pointer' => 'У вас пока нет бюджетов. Вам следует создать их на странице бюджеты. Бюджеты помогут вам отслеживать расходы.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Сберегательный счет', 'Credit card' => 'Кредитная карта', 'source_accounts' => 'Исходный счет(а)', @@ -270,8 +270,8 @@ return [ 'move_rule_group_up' => 'Переместить группу правил вверх', 'move_rule_group_down' => 'Переместить группу правил вниз', 'save_rules_by_moving' => 'Сохраните эти правила, переместив их в другую группу правил:', - 'make_new_rule' => 'Make a new rule in rule group ":title"', - 'make_new_rule_no_group' => 'Make a new rule', + 'make_new_rule' => 'Создать новое правило в группе правил ":title"', + 'make_new_rule_no_group' => 'Создать новое правило', 'instructions_rule_from_bill' => 'In order to match transactions to your new bill ":name", Firefly III can create a rule that will automatically be checked against any transactions you store. Please verify the details below and store the rule to have Firefly III automatically match transactions to your new bill.', 'rule_is_strict' => 'строгое правило', 'rule_is_not_strict' => 'нестрогое правило', @@ -1266,6 +1266,8 @@ return [ 'created_Deposits' => 'Доходы созданы', 'created_Transfers' => 'Переводы созданы', 'created_from_recurrence' => 'Создано из повторяющейся транзакции ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Метки', 'recurring_meta_field_notes' => 'Примечания', diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index a4951b419b..5e29667050 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -104,7 +104,7 @@ return [ 'sum_transfers' => 'Сумма переводов', 'reconcile' => 'Сверка', 'account_on_spectre' => 'Счёт (Spectre)', - 'account_on_ynab' => 'Account (YNAB)', + 'account_on_ynab' => 'Счёт (YNAB)', 'do_import' => 'Импортировать с этого счёта', 'sepa-ct-id' => 'Идентификатор SEPA end-to-end', 'sepa-ct-op' => 'Идентификатор учетной записи SEPA', @@ -130,7 +130,7 @@ return [ 'transaction_s' => 'Транзакции', 'field' => 'Поле', 'value' => 'Значение', - 'interest' => 'Interest', - 'interest_period' => 'interest period', - 'liability_type' => 'Type of liability', + 'interest' => 'Процентная ставка', + 'interest_period' => 'период начисления процентов', + 'liability_type' => 'Тип ответственности', ]; diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index edc9787d26..3966818d00 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -37,7 +37,7 @@ return [ 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'Данное значение недопустимо для этого поля.', 'at_least_one_transaction' => 'Необходима как минимум одна транзакция.', - 'at_least_one_repetition' => 'Need at least one repetition.', + 'at_least_one_repetition' => 'Необходима как минимум одна транзакция.', 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Содержимое этого поля недействительно без информации о валюте.', 'equal_description' => 'Описание транзакции не должно совпадать с глобальным описанием.', @@ -50,8 +50,8 @@ return [ '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.', - 'less' => ':attribute must be less than 10,000,000', + 'more' => ':attribute должен быть больше нуля.', + 'less' => ':attribute должен быть меньше 10,000,000', 'active_url' => ':attribute не является допустимым URL-адресом.', 'after' => ':attribute должна быть позже :date.', 'alpha' => ':attribute может содержать только буквы.', @@ -60,8 +60,8 @@ return [ 'array' => ':attribute должен быть массивом.', 'unique_for_user' => 'Уже существует запись с этим :attribute.', 'before' => ':attribute должна быть раньше :date.', - 'unique_object_for_user' => 'This name is already in use.', - 'unique_account_for_user' => 'This account name is already in use.', + 'unique_object_for_user' => 'Это название уже используется.', + 'unique_account_for_user' => 'Такое название счёта уже используется.', 'between.numeric' => ':attribute должен быть больше :min и меньше :max.', 'between.file' => ':attribute должен быть размером :min - :max килобайт.', 'between.string' => ':attribute должен содержать :min - :max символов.', @@ -119,7 +119,7 @@ return [ 'file' => ':attribute должен быть файлом.', 'in_array' => 'Поле :attribute не существует в :other.', 'present' => 'Поле :attribute должно быть заполнено.', - 'amount_zero' => 'The total amount cannot be zero.', + 'amount_zero' => 'Сумма не может быть равна нулю.', 'unique_piggy_bank_for_user' => 'Название копилки должно быть уникальным.', '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.', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index d51b2a99cc..4b9ec716aa 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -91,7 +91,7 @@ return [ 'warning_much_data' => ':days günlük verinin yüklenmesi biraz zaman alabilir.', 'registered' => 'Başarıyla kaydoldunuz!', 'Default asset account' => 'Varsayılan varlık hesabı', - 'no_budget_pointer' => 'Henüz bütçeniz yok gibi görünüyor. Bütçe sayfasında bütçe oluşturun. Bütçeler giderleri takip etmenize yardımcı olabilir.', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', 'Savings account' => 'Birikim Hesabı', 'Credit card' => 'Kredi Kartı', 'source_accounts' => 'Kaynak Hesap(lar)', @@ -1269,6 +1269,8 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'created_Deposits' => 'Created deposits', 'created_Transfers' => 'Created transfers', 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', 'recurring_meta_field_tags' => 'Etiketler', 'recurring_meta_field_notes' => 'Notlar', From 4876053018588f5ede7f9ffaa4e03f42745346b8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 19 Aug 2018 19:31:21 +0200 Subject: [PATCH 110/166] Test a fix for #1620 --- app/Http/Requests/BillFormRequest.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/Http/Requests/BillFormRequest.php b/app/Http/Requests/BillFormRequest.php index 25066ad7a1..464a2e1167 100644 --- a/app/Http/Requests/BillFormRequest.php +++ b/app/Http/Requests/BillFormRequest.php @@ -48,15 +48,16 @@ class BillFormRequest extends Request public function getBillData(): array { return [ - 'name' => $this->string('name'), - 'amount_min' => $this->string('amount_min'), - 'currency_id' => $this->integer('transaction_currency_id'), - 'amount_max' => $this->string('amount_max'), - 'date' => $this->date('date'), - 'repeat_freq' => $this->string('repeat_freq'), - 'skip' => $this->integer('skip'), - 'notes' => $this->string('notes'), - 'active' => $this->boolean('active'), + 'name' => $this->string('name'), + 'amount_min' => $this->string('amount_min'), + 'currency_id' => $this->integer('transaction_currency_id'), + 'currency_code' => '', + 'amount_max' => $this->string('amount_max'), + 'date' => $this->date('date'), + 'repeat_freq' => $this->string('repeat_freq'), + 'skip' => $this->integer('skip'), + 'notes' => $this->string('notes'), + 'active' => $this->boolean('active'), ]; } From 433da921bb4f9331363e96314e05b1f9e62ee59f Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 20 Aug 2018 17:29:39 +0200 Subject: [PATCH 111/166] Simplify cronjob call. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 06c3345ca7..69f8c2f9df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ COPY ./.deploy/docker/firefly-iii.conf /etc/supervisor/conf.d/firefly-iii.conf COPY ./.deploy/docker/cronjob.conf /etc/supervisor/conf.d/cronjob.conf # test crons added via crontab -RUN echo "0 3 * * * cd /var/www/firefly-iii && php artisan firefly:cron >> /dev/stdout 2>&1" | crontab - +RUN echo "0 3 * * * /usr/local/bin/php /var/www/firefly-iii/artisan firefly:cron >> /dev/stdout 2>&1" | crontab - # Install PHP exentions. RUN docker-php-ext-install -j$(nproc) gd intl tidy zip bcmath pdo_mysql bz2 pdo_pgsql From f8e914416dbe8ca94ec8dc2a13e83b674a6f6103 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 20 Aug 2018 17:31:39 +0200 Subject: [PATCH 112/166] Give file a newline [skip ci] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 69f8c2f9df..db4338d1fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,4 +105,4 @@ RUN composer install --prefer-dist --no-dev --no-scripts --no-suggest EXPOSE 80 # Run entrypoint thing -ENTRYPOINT [".deploy/docker/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT [".deploy/docker/entrypoint.sh"] From d30da7bf5d97a55a30090a0ffc563d89a1a9042d Mon Sep 17 00:00:00 2001 From: David Meiseles Date: Mon, 20 Aug 2018 11:36:13 -0400 Subject: [PATCH 113/166] Can have link in success message to the transaction it refers to --- app/Http/Controllers/Transaction/SingleController.php | 2 +- resources/views/partials/flashes.twig | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 6b2f5f934e..53d8214769 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -369,7 +369,7 @@ class SingleController extends Controller event(new StoredTransactionJournal($journal, $data['piggy_bank_id'])); - session()->flash('success', (string)trans('firefly.stored_journal', ['description' => $journal->description])); + session()->flash('success_uri', (string)trans('firefly.stored_journal', ['description' => "{$journal->description}"])); app('preferences')->mark(); // @codeCoverageIgnoreStart diff --git a/resources/views/partials/flashes.twig b/resources/views/partials/flashes.twig index 77c03557fe..332e469850 100644 --- a/resources/views/partials/flashes.twig +++ b/resources/views/partials/flashes.twig @@ -18,6 +18,16 @@ {% endif %} +{# SUCCESS MESSAGE WITH URL (ALWAYS SINGULAR) #} +{% if session_has('success_uri') %} + +{% endif %} + {# INFO MESSAGE (CAN BE MULTIPLE) #} {% if session_has('info') %} -{% endif %} -{# SUCCESS MESSAGE WITH URL (ALWAYS SINGULAR) #} -{% if session_has('success_uri') %} - {% endif %} From cc234b594d5e70496c91ed46a23fd6c1a879dd31 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 21 Aug 2018 18:20:01 +0200 Subject: [PATCH 115/166] Fix call to cron job, clear cache after running. --- Dockerfile | 8 ++++++-- app/Jobs/CreateRecurringTransactions.php | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index db4338d1fe..15dcc12043 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,9 @@ ENV FIREFLY_PATH /var/www/firefly-iii/ ENV CURL_VERSION 7.60.0 ENV OPENSSL_VERSION 1.1.1-pre6 +LABEL version="1.0" maintainer="thegrumpydictator@gmail.com" + + # install packages RUN apt-get update -y && \ apt-get install -y --no-install-recommends libcurl4-openssl-dev \ @@ -64,8 +67,8 @@ COPY ./.deploy/docker/firefly-iii.conf /etc/supervisor/conf.d/firefly-iii.conf COPY ./.deploy/docker/cronjob.conf /etc/supervisor/conf.d/cronjob.conf # test crons added via crontab -RUN echo "0 3 * * * /usr/local/bin/php /var/www/firefly-iii/artisan firefly:cron >> /dev/stdout 2>&1" | crontab - - +RUN echo "0 3 * * * /usr/local/bin/php /var/www/firefly-iii/artisan firefly:cron" | crontab - +#RUN (crontab -l ; echo "*/1 * * * * free >> /var/www/firefly-iii/public/cron.html") 2>&1 | crontab - # Install PHP exentions. RUN docker-php-ext-install -j$(nproc) gd intl tidy zip bcmath pdo_mysql bz2 pdo_pgsql @@ -75,6 +78,7 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local # Generate locales supported by Firefly III RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\n\n" > /etc/locale.gen && locale-gen + # copy Apache config to correct spot. COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 5fd9d001b3..4ee18fddfd 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -119,6 +119,9 @@ class CreateRecurringTransactions implements ShouldQueue } Log::debug('Done with handle()'); + + // clear cache: + app('preferences')->mark(); } /** From 7c34144ccd1c31cc976ab746bbf94e62c412fe1c Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 22 Aug 2018 21:18:15 +0200 Subject: [PATCH 116/166] Some basic code for liability accounts. --- .../Controllers/Account/ShowController.php | 14 +++- .../Account/AccountRepository.php | 75 +++++++++++++++++++ .../Account/AccountRepositoryInterface.php | 33 +++++++- resources/lang/en_US/firefly.php | 2 +- resources/views/accounts/show.twig | 55 +++++++++----- 5 files changed, 158 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php index db7adc3e5f..6032d1fb78 100644 --- a/app/Http/Controllers/Account/ShowController.php +++ b/app/Http/Controllers/Account/ShowController.php @@ -91,6 +91,11 @@ class ShowController extends Controller if (AccountType::INITIAL_BALANCE === $account->accountType->type) { return $this->redirectToOriginalAccount($account); } + // a basic thing to determin if this account is a liability: + if ($this->repository->isLiability($account)) { + return redirect(route('accounts.show.all', [$account->id])); + } + /** @var Carbon $start */ $start = $start ?? session('start'); /** @var Carbon $end */ @@ -122,9 +127,13 @@ class ShowController extends Controller $transactions->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')])); $showAll = false; + return view( 'accounts.show', - compact('account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri') + compact( + 'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', + 'chartUri' + ) ); } @@ -144,6 +153,7 @@ class ShowController extends Controller if (AccountType::INITIAL_BALANCE === $account->accountType->type) { return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore } + $isLiability = $this->repository->isLiability($account); $end = new Carbon; $today = new Carbon; $start = $this->repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); @@ -167,7 +177,7 @@ class ShowController extends Controller return view( 'accounts.show', - compact('account', 'showAll', 'currency', 'today', 'chartUri', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end') + compact('account', 'showAll','isLiability', 'currency', 'today', 'chartUri', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end') ); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 27e1c0a695..12d57fd97b 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -270,6 +270,30 @@ class AccountRepository implements AccountRepositoryInterface return $factory->findOrCreate('Cash account', $type->type); } + /** + * @param $account + * + * @return string + */ + public function getInterestPerDay(Account $account): string + { + $interest = $this->getMetaValue($account, 'interest'); + $interestPeriod = $this->getMetaValue($account, 'interest_period'); + Log::debug(sprintf('Start with interest of %s percent', $interest)); + + // calculate + if ('monthly' === $interestPeriod) { + $interest = bcdiv(bcmul($interest, '12'), '365'); // per year + Log::debug(sprintf('Interest is now (monthly to daily) %s percent', $interest)); + } + if ('yearly' === $interestPeriod) { + $interest = bcdiv($interest, '365'); // per year + Log::debug(sprintf('Interest is now (yearly to daily) %s percent', $interest)); + } + + return $interest; + } + /** * Return meta value for account. Null if not found. * @@ -382,6 +406,57 @@ class AccountRepository implements AccountRepositoryInterface return $account; } + /** + * @param Account $account + * + * @return bool + */ + public function isLiability(Account $account): bool + { + return \in_array($account->accountType->type, [AccountType::CREDITCARD, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true); + } + + /** + * Returns the date of the very last transaction in this account. + * + * @param Account $account + * + * @return TransactionJournal|null + */ + public function latestJournal(Account $account): ?TransactionJournal + { + $first = $account->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->where('transaction_journals.user_id', $this->user->id) + ->orderBy('transaction_journals.id', 'DESC') + ->first(['transaction_journals.id']); + if (null !== $first) { + return TransactionJournal::find((int)$first->id); + } + + return null; + } + + /** + * Returns the date of the very last transaction in this account. + * + * @param Account $account + * + * @return Carbon|null + */ + public function latestJournalDate(Account $account): ?Carbon + { + $result = null; + $journal = $this->latestJournal($account); + if (null !== $journal) { + $result = $journal->date; + } + + return $result; + } + /** * Returns the date of the very first transaction in this account. * diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index d5e02ab19c..77099390d3 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -123,6 +123,13 @@ interface AccountRepositoryInterface */ public function getCashAccount(): Account; + /** + * @param $account + * + * @return string + */ + public function getInterestPerDay(Account $account): string; + /** * Return meta value for account. Null if not found. * @@ -151,7 +158,6 @@ interface AccountRepositoryInterface */ public function getOpeningBalanceAmount(Account $account): ?string; - /** * Return date of opening balance as string or null. * @@ -170,6 +176,31 @@ interface AccountRepositoryInterface */ public function getReconciliation(Account $account): ?Account; + /** + * @param Account $account + * + * @return bool + */ + public function isLiability(Account $account): bool; + + /** + * Returns the date of the very first transaction in this account. + * + * @param Account $account + * + * @return TransactionJournal|null + */ + public function latestJournal(Account $account): ?TransactionJournal; + + /** + * Returns the date of the very last transaction in this account. + * + * @param Account $account + * + * @return Carbon|null + */ + public function latestJournalDate(Account $account): ?Carbon; + /** * Returns the date of the very first transaction in this account. * diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index a56b92ba7c..b3ff588e14 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Errors', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index 8e13be1ad7..7dc2787cd3 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -6,30 +6,51 @@ {% block content %} -
    -
    -
    -
    -

    - {{ trans('firefly.chart_account_in_period', {name: account.name, start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }} -

    -
    - +
    +
    +
    +
    +

    + {{ trans('firefly.chart_account_in_period', {name: account.name, start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }} +

    + -
    +
    +
    +
    +

    + {% if isLiability %} + This chart pre-calculates rent starting from the last transaction you've entered. It's just an estimation. + {% endif %} +

    +
    +
    +
    +
    + {% if not showAll and isLiability %} +
    +
    +
    +
    +

    pay-off by date

    +
    +
    + Content +
    - {% if not showAll %} + {% endif %} + {% if not showAll and not isLiability %}
    From 179f7208065a434c76c04d6a7aac2a293c84f099 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 23 Aug 2018 06:17:26 +0200 Subject: [PATCH 117/166] Some updated translations [skip ci] --- resources/lang/de_DE/firefly.php | 8 ++++---- resources/lang/es_ES/firefly.php | 2 +- resources/lang/fr_FR/firefly.php | 14 +++++++------- resources/lang/id_ID/firefly.php | 2 +- resources/lang/it_IT/firefly.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 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 8821393ef9..420bc6c9ba 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days Tage Daten können eine Weile zu laden benötigen.', 'registered' => 'Sie haben sich erfolgreich registriert!', 'Default asset account' => 'Standard-Bestandskonto', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'no_budget_pointer' => 'Sie scheinen noch keine Kostenrahmen festgelegt zu haben. Sie sollten einige davon auf der Seite Kostenrahmen anlegen. Kostenrahmen können Ihnen dabei helfen, den Überblick über die Ausgaben zu behalten.', 'Savings account' => 'Sparkonto', 'Credit card' => 'Kreditkarte', 'source_accounts' => 'Herkunftskonto', @@ -903,7 +903,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'errors' => 'Fehler', 'debt_start_date' => 'Startdatum der Verschuldung', 'debt_start_amount' => 'Startbetrag der Verschuldung', - 'debt_start_amount_help' => 'Bitte geben Sie einen positiven Betrag ein. Sie können auch den aktuellen Betrag eingeben, wenn der ursprüngliche Betrag schon einmal zurückgezahlt wurde.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Neue Verbindlichkeit speichern', 'edit_liabilities_account' => 'Verbindlichkeit „:name” bearbeiten', @@ -1267,8 +1267,8 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'created_Deposits' => 'Erstellte Einzahlungen', 'created_Transfers' => 'Erstellte Überweisungen', 'created_from_recurrence' => 'Erstellt aus Dauerauftrag „:title” (#:id)', - 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', - 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_never_cron' => 'Es scheint, dass der Cron-Job, der notwendig ist, um wiederkehrende Buchungen zu unterstützen, nie ausgeführt wurde. Das ist natürlich normal, wenn Sie gerade Firefly III installiert haben, aber dies sollte so schnell wie möglich eingerichtet werden. Bitte besuchen Sie die Hilfeseiten über das ❓-Symbol in der oberen rechten Ecke der Seite.', + 'recurring_cron_long_ago' => 'Es sieht so aus, als wäre es mehr als 36 Stunden her, dass der Cron-Job zur Unterstützung wiederkehrender Buchungen zum letzten Mal ausgeführt wurde. Sind Sie sicher, dass es richtig eingestellt ist? Bitte schauen Sie sich die Hilfeseiten über dem ❓-Symbol oben rechts auf der Seite an.', 'recurring_meta_field_tags' => 'Schlagwörter', 'recurring_meta_field_notes' => 'Anmerkungen', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index b49bce607e..bc16a5ee8f 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -903,7 +903,7 @@ return [ 'errors' => 'Errores', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 85347ee39a..6f48ad6ee6 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -37,7 +37,7 @@ return [ 'cancel' => 'Annuler', 'from' => 'Depuis', 'to' => 'A', - 'help_translating' => 'This help text is not yet available in your language. Will you help translate?', + 'help_translating' => 'Ce texte d\'aide n\'est pas encore disponible en français. Voulez-vous aider à le traduire ?', 'showEverything' => 'Tout Afficher', 'never' => 'Jamais', 'no_results_for_empty_search' => 'Votre recherche était vide, rien n’a été trouvé.', @@ -90,7 +90,7 @@ return [ 'warning_much_data' => ':days jours de données peuvent prendre un certain temps à charger.', 'registered' => 'Vous avez été enregistré avec succès !', 'Default asset account' => 'Compte d’actif par défaut', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'no_budget_pointer' => 'Vous semblez n’avoir encore aucun budget. Vous devriez en créer un sur la page des budgets. Les budgets peuvent vous aider à garder une trace des dépenses.', 'Savings account' => 'Compte d’épargne', 'Credit card' => 'Carte de Crédit', 'source_accounts' => 'Compte(s) source', @@ -678,7 +678,7 @@ return [ 'list_inactive_rule' => 'règle inactive', // accounts: - 'account_missing_transaction' => 'Account #:id (":name") cannot be viewed directly, but Firefly is missing redirect information.', + 'account_missing_transaction' => 'Le compte #:id (":name") ne peut pas être consulter directement et Firefly III ne dispose d\'aucune information pour vous rediriger.', 'details_for_asset' => 'Détails pour le compte d’actif ":name"', 'details_for_expense' => 'Détails du compte de dépenses ":name"', 'details_for_revenue' => 'Détails du comptes de recettes ":name"', @@ -761,7 +761,7 @@ return [ 'already_cleared_transactions' => 'Transactions déjà pointées ( :count)', 'submitted_end_balance' => 'Solde final soumis', 'initial_balance_description' => 'Balance initiale pour ":account"', - 'interest_calc_' => 'unknown', + 'interest_calc_' => 'inconnu', 'interest_calc_daily' => 'Par jour', 'interest_calc_monthly' => 'Par mois', 'interest_calc_yearly' => 'Par an', @@ -902,7 +902,7 @@ return [ 'errors' => 'Erreurs', 'debt_start_date' => 'Date de début de la dette', 'debt_start_amount' => 'Montant initial de la dette', - 'debt_start_amount_help' => 'Veuillez entrer un montant positif. Vous pouvez également entrer le montant actuel si le montant initial n\'est plus connu.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Enregistrer un nouveau passif', 'edit_liabilities_account' => 'Modifier le passif ":name"', @@ -1266,8 +1266,8 @@ return [ 'created_Deposits' => 'Revenus créés', 'created_Transfers' => 'Transferts créés', 'created_from_recurrence' => 'Créé à partir de l\'opération périodique ":title" (#:id)', - 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', - 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_never_cron' => 'Il semble que la tâche cron qui supporte les opérations périodiques n\'a jamais été exécutée. Ceci est normal si vous venez juste d\'installer Firefly III, mais pensez à la configurer dès que possible. Veuillez consulter la page d\'aide associée en cliquant sur l\'icône (?) en haut à droite de la page.', + 'recurring_cron_long_ago' => 'Il semble que la dernière exécution de la tâche cron supportant les opérations périodiques date de plus de 36 heures. Êtes-vous surs qu\'elle est configurée correctement ? Veuillez consulter la page d\'aide associée en cliquant sur l\'icône (?) en haut à droite de la page.', 'recurring_meta_field_tags' => 'Tags', 'recurring_meta_field_notes' => 'Notes', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 251571fd09..024456de27 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Kesalahan', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index d3b555c451..7c4e661324 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Errori', 'debt_start_date' => 'Data di inizio del debito', 'debt_start_amount' => 'Importo iniziale del debito', - 'debt_start_amount_help' => 'Inserisci un importo positivo. Puoi anche inserire l\'importo attuale se quello originale è di molto tempo fa.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Memorizza nuova passività', 'edit_liabilities_account' => 'Modica passività ":name"', @@ -1144,7 +1144,7 @@ return [ 'journals_linked' => 'Le transazioni sono collegate.', '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', + 'journal_links' => 'Collegamenti della transazione', 'this_withdrawal' => 'Questo prelievo', 'this_deposit' => 'Questa entrata', 'this_transfer' => 'Questo trasferimento', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index c3e3eff26b..5350040382 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Fouten', 'debt_start_date' => 'Begindatum van schuld', 'debt_start_amount' => 'Beginbedrag van schuld', - 'debt_start_amount_help' => 'Voer een positief bedrag in. Je mag ook het bedrag van vandaag de dag invoeren, als het oorspronkelijke bedrag erg lang geleden was.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Nieuwe passiva opslaan', 'edit_liabilities_account' => 'Passiva ":name" wijzigen', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index c7de3342a4..d7748cc72e 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Błędy', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 31b624469d..1f783d82b4 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Erros', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index c02e171393..3e2fc24183 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -902,7 +902,7 @@ return [ 'errors' => 'Ошибки', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 4b9ec716aa..808147bcf7 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -905,7 +905,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'errors' => 'Hatalar', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter a positive amount. You may also enter the current amount, if the original amount was way-back-when.', + 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', From 05309da76d1d42686711af7a5bbe8fb42811a4d6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 23 Aug 2018 18:32:17 +0200 Subject: [PATCH 118/166] Small fix for test script. --- test.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test.sh b/test.sh index 1530cf6f4b..bcf4487379 100755 --- a/test.sh +++ b/test.sh @@ -22,6 +22,7 @@ apitestclass='' verbalflag='' testsuite='' +suiteflag='' configfile='phpunit.xml'; while getopts 'vcrtf:u:s:a:' flag; do @@ -56,6 +57,7 @@ while getopts 'vcrtf:u:s:a:' flag; do echo "Will only run Api test $OPTARG" ;; s) + suiteflag='true' testsuite="--testsuite $OPTARG" echo "Will only run test suite '$OPTARG'" ;; @@ -63,7 +65,7 @@ while getopts 'vcrtf:u:s:a:' flag; do esac done -if [[ $coverageflag == "true" && ($featureflag == "true" || $unitflag == "true" || $apiflag == "true") ]] +if [[ $coverageflag == "true" && ($suiteflag == "true" || $featureflag == "true" || $unitflag == "true" || $apiflag == "true") ]] then echo "Use config file specific.xml" configfile='phpunit.coverage.specific.xml' From 3f493aceb2a9ec4765d03c207f2a22f56ff071a5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 23 Aug 2018 18:33:07 +0200 Subject: [PATCH 119/166] Mark code as untestable or deprecated (or both). --- app/Console/Commands/CreateExport.php | 2 ++ app/Console/Commands/CreateImport.php | 2 ++ app/Console/Commands/Cron.php | 2 ++ app/Console/Commands/DecryptAttachment.php | 2 ++ app/Console/Commands/EncryptFile.php | 2 ++ app/Console/Commands/Import.php | 2 ++ app/Console/Commands/ScanAttachments.php | 2 ++ app/Console/Commands/UpgradeDatabase.php | 1 + app/Console/Commands/UpgradeFireflyInstructions.php | 2 ++ app/Console/Commands/UseEncryption.php | 1 + app/Console/Commands/VerifiesAccessToken.php | 1 + app/Console/Commands/VerifyDatabase.php | 1 + app/Console/Kernel.php | 1 + app/Events/AdminRequestedTestMessage.php | 1 + app/Events/Event.php | 1 + app/Events/RegisteredUser.php | 1 + app/Events/RequestedNewPassword.php | 1 + app/Events/RequestedReportOnJournals.php | 2 ++ app/Events/RequestedVersionCheckStatus.php | 2 ++ app/Events/UserChangedEmail.php | 2 ++ app/Exceptions/Handler.php | 1 + app/Export/Collector/AttachmentCollector.php | 3 +++ app/Export/Collector/BasicCollector.php | 3 +++ app/Export/Collector/CollectorInterface.php | 3 +++ app/Export/Collector/UploadCollector.php | 3 +++ app/Export/Entry/Entry.php | 3 +++ app/Export/ExpandedProcessor.php | 3 +++ app/Export/Exporter/BasicExporter.php | 3 +++ app/Export/Exporter/CsvExporter.php | 3 +++ app/Export/Exporter/ExporterInterface.php | 3 +++ app/Export/ProcessorInterface.php | 3 +++ 31 files changed, 62 insertions(+) diff --git a/app/Console/Commands/CreateExport.php b/app/Console/Commands/CreateExport.php index 6896b409cf..73b4d3384f 100644 --- a/app/Console/Commands/CreateExport.php +++ b/app/Console/Commands/CreateExport.php @@ -39,6 +39,8 @@ use Storage; * Class CreateExport. * * Generates export from the command line. + * + * @codeCoverageIgnore */ class CreateExport extends Command { diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php index 2479ac514f..15234cb3aa 100644 --- a/app/Console/Commands/CreateImport.php +++ b/app/Console/Commands/CreateImport.php @@ -37,6 +37,8 @@ use Log; /** * Class CreateImport. + * + * @codeCoverageIgnore */ class CreateImport extends Command { diff --git a/app/Console/Commands/Cron.php b/app/Console/Commands/Cron.php index 4678382102..807de0c39f 100644 --- a/app/Console/Commands/Cron.php +++ b/app/Console/Commands/Cron.php @@ -8,6 +8,8 @@ use Illuminate\Console\Command; /** * Class Cron + * + * @codeCoverageIgnore */ class Cron extends Command { diff --git a/app/Console/Commands/DecryptAttachment.php b/app/Console/Commands/DecryptAttachment.php index dfa57386e5..450eedd8bd 100644 --- a/app/Console/Commands/DecryptAttachment.php +++ b/app/Console/Commands/DecryptAttachment.php @@ -31,6 +31,8 @@ use Log; /** * Class DecryptAttachment. + * + * @codeCoverageIgnore */ class DecryptAttachment extends Command { diff --git a/app/Console/Commands/EncryptFile.php b/app/Console/Commands/EncryptFile.php index 64b19820a5..08d9683afa 100644 --- a/app/Console/Commands/EncryptFile.php +++ b/app/Console/Commands/EncryptFile.php @@ -29,6 +29,8 @@ use Illuminate\Console\Command; /** * Class EncryptFile. + * + * @codeCoverageIgnore */ class EncryptFile extends Command { diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php index b7572cf061..7ee59ea03b 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/Import.php @@ -35,6 +35,8 @@ use Log; /** * Class Import. + * + * @codeCoverageIgnore */ class Import extends Command { diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/ScanAttachments.php index cd76aa9377..034a1c2fa3 100644 --- a/app/Console/Commands/ScanAttachments.php +++ b/app/Console/Commands/ScanAttachments.php @@ -34,6 +34,8 @@ use Storage; /** * Class ScanAttachments. + * + * @codeCoverageIgnore */ class ScanAttachments extends Command { diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index b126e38850..b1b393398b 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -63,6 +63,7 @@ use UnexpectedValueException; * Upgrade user database. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @codeCoverageIgnore */ class UpgradeDatabase extends Command { diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index 9aa8f33a1e..37cd28f4ea 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -27,6 +27,8 @@ use Illuminate\Console\Command; /** * Class UpgradeFireflyInstructions. + * + * @codeCoverageIgnore */ class UpgradeFireflyInstructions extends Command { diff --git a/app/Console/Commands/UseEncryption.php b/app/Console/Commands/UseEncryption.php index 7a67d71cf6..55ef87eee9 100644 --- a/app/Console/Commands/UseEncryption.php +++ b/app/Console/Commands/UseEncryption.php @@ -28,6 +28,7 @@ use Illuminate\Support\Str; /** * Class UseEncryption. + * @codeCoverageIgnore */ class UseEncryption extends Command { diff --git a/app/Console/Commands/VerifiesAccessToken.php b/app/Console/Commands/VerifiesAccessToken.php index 857aeaa30b..a0224ae0d6 100644 --- a/app/Console/Commands/VerifiesAccessToken.php +++ b/app/Console/Commands/VerifiesAccessToken.php @@ -30,6 +30,7 @@ use Log; * Trait VerifiesAccessToken. * * Verifies user access token for sensitive commands. + * @codeCoverageIgnore */ trait VerifiesAccessToken { diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 88635664ae..31b8b89658 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -50,6 +50,7 @@ use stdClass; * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @codeCoverageIgnore */ class VerifyDatabase extends Command { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 250b83f5bc..1b3fdb5311 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -30,6 +30,7 @@ use Log; /** * File to make sure commands work. + * @codeCoverageIgnore */ class Kernel extends ConsoleKernel { diff --git a/app/Events/AdminRequestedTestMessage.php b/app/Events/AdminRequestedTestMessage.php index 800ffb909f..0a82c434cc 100644 --- a/app/Events/AdminRequestedTestMessage.php +++ b/app/Events/AdminRequestedTestMessage.php @@ -30,6 +30,7 @@ use Log; /** * Class AdminRequestedTestMessage. + * @codeCoverageIgnore */ class AdminRequestedTestMessage extends Event { diff --git a/app/Events/Event.php b/app/Events/Event.php index 41b3b0437c..84ea6bdb2b 100644 --- a/app/Events/Event.php +++ b/app/Events/Event.php @@ -26,6 +26,7 @@ namespace FireflyIII\Events; /** * Class Event. + * @codeCoverageIgnore */ abstract class Event { diff --git a/app/Events/RegisteredUser.php b/app/Events/RegisteredUser.php index 1bbb4b25dd..c576b16f94 100644 --- a/app/Events/RegisteredUser.php +++ b/app/Events/RegisteredUser.php @@ -29,6 +29,7 @@ use Illuminate\Queue\SerializesModels; /** * Class RegisteredUser. + * @codeCoverageIgnore */ class RegisteredUser extends Event { diff --git a/app/Events/RequestedNewPassword.php b/app/Events/RequestedNewPassword.php index ffd13ee344..7dce83c05d 100644 --- a/app/Events/RequestedNewPassword.php +++ b/app/Events/RequestedNewPassword.php @@ -29,6 +29,7 @@ use Illuminate\Queue\SerializesModels; /** * Class RequestedNewPassword. + * @codeCoverageIgnore */ class RequestedNewPassword extends Event { diff --git a/app/Events/RequestedReportOnJournals.php b/app/Events/RequestedReportOnJournals.php index 9d9c0444a6..0bd2ac4c55 100644 --- a/app/Events/RequestedReportOnJournals.php +++ b/app/Events/RequestedReportOnJournals.php @@ -31,6 +31,8 @@ use Log; /** * Class RequestedReportOnJournals + * + * @codeCoverageIgnore */ class RequestedReportOnJournals { diff --git a/app/Events/RequestedVersionCheckStatus.php b/app/Events/RequestedVersionCheckStatus.php index dabe9ca7b8..a490ea0e38 100644 --- a/app/Events/RequestedVersionCheckStatus.php +++ b/app/Events/RequestedVersionCheckStatus.php @@ -30,6 +30,8 @@ use Illuminate\Queue\SerializesModels; /** * Class RequestedVersionCheckStatus + * + * @codeCoverageIgnore */ class RequestedVersionCheckStatus extends Event { diff --git a/app/Events/UserChangedEmail.php b/app/Events/UserChangedEmail.php index 917eef90ae..12e7aab446 100644 --- a/app/Events/UserChangedEmail.php +++ b/app/Events/UserChangedEmail.php @@ -29,6 +29,8 @@ use Illuminate\Queue\SerializesModels; /** * Class UserChangedEmail. + * + * @codeCoverageIgnore */ class UserChangedEmail extends Event { diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 5d6899c52b..72caac1261 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -38,6 +38,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Handler + * @codeCoverageIgnore */ class Handler extends ExceptionHandler { diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php index 49e8b1e20a..a0a354451b 100644 --- a/app/Export/Collector/AttachmentCollector.php +++ b/app/Export/Collector/AttachmentCollector.php @@ -34,6 +34,9 @@ use Storage; /** * Class AttachmentCollector. + * + * @deprecated + * @codeCoverageIgnore */ class AttachmentCollector extends BasicCollector implements CollectorInterface { diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php index 10215d6c58..c1db2a82a3 100644 --- a/app/Export/Collector/BasicCollector.php +++ b/app/Export/Collector/BasicCollector.php @@ -30,6 +30,9 @@ use Illuminate\Support\Collection; /** * Class BasicCollector. + * + * @codeCoverageIgnore + * @deprecated */ class BasicCollector { diff --git a/app/Export/Collector/CollectorInterface.php b/app/Export/Collector/CollectorInterface.php index b93dc1617b..c58e538ee1 100644 --- a/app/Export/Collector/CollectorInterface.php +++ b/app/Export/Collector/CollectorInterface.php @@ -29,6 +29,9 @@ use Illuminate\Support\Collection; /** * Interface CollectorInterface. + * + * @codeCoverageIgnore + * @deprecated */ interface CollectorInterface { diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php index 5e1e6df02b..e2637b940f 100644 --- a/app/Export/Collector/UploadCollector.php +++ b/app/Export/Collector/UploadCollector.php @@ -31,6 +31,9 @@ use Storage; /** * Class UploadCollector. + * + * @codeCoverageIgnore + * @deprecated */ class UploadCollector extends BasicCollector implements CollectorInterface { diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index 8543c75646..9e0064d58e 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -43,6 +43,9 @@ use FireflyIII\Models\Transaction; * * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.TooManyFields) + * + * @codeCoverageIgnore + * @deprecated */ final class Entry { diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php index 1734713aa0..4adb9499cf 100644 --- a/app/Export/ExpandedProcessor.php +++ b/app/Export/ExpandedProcessor.php @@ -49,6 +49,9 @@ use ZipArchive; * Class ExpandedProcessor. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // its doing a lot. + * + * @codeCoverageIgnore + * @deprecated */ class ExpandedProcessor implements ProcessorInterface { diff --git a/app/Export/Exporter/BasicExporter.php b/app/Export/Exporter/BasicExporter.php index fd1003ddfa..de7eaed474 100644 --- a/app/Export/Exporter/BasicExporter.php +++ b/app/Export/Exporter/BasicExporter.php @@ -29,6 +29,9 @@ use Illuminate\Support\Collection; /** * Class BasicExporter. + * + * @codeCoverageIgnore + * @deprecated */ class BasicExporter { diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php index bbc66b8345..6e4a7b96d1 100644 --- a/app/Export/Exporter/CsvExporter.php +++ b/app/Export/Exporter/CsvExporter.php @@ -30,6 +30,9 @@ use Storage; /** * Class CsvExporter. + * + * @codeCoverageIgnore + * @deprecated */ class CsvExporter extends BasicExporter implements ExporterInterface { diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php index cc44b69c08..998510f65b 100644 --- a/app/Export/Exporter/ExporterInterface.php +++ b/app/Export/Exporter/ExporterInterface.php @@ -29,6 +29,9 @@ use Illuminate\Support\Collection; /** * Interface ExporterInterface. + * + * @codeCoverageIgnore + * @deprecated */ interface ExporterInterface { diff --git a/app/Export/ProcessorInterface.php b/app/Export/ProcessorInterface.php index 641e26eaeb..330e69c2d2 100644 --- a/app/Export/ProcessorInterface.php +++ b/app/Export/ProcessorInterface.php @@ -28,6 +28,9 @@ use Illuminate\Support\Collection; /** * Interface ProcessorInterface. + * + * @codeCoverageIgnore + * @deprecated */ interface ProcessorInterface { From d4096103cb894a3d5565c5aa4c6d6c161ae95518 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 23 Aug 2018 18:33:39 +0200 Subject: [PATCH 120/166] Improve test coverage and fix test code. --- .../Controllers/AvailableBudgetController.php | 4 +- .../V1/Controllers/PiggyBankController.php | 2 +- app/Api/V1/Controllers/RuleController.php | 2 +- .../V1/Controllers/RuleGroupController.php | 2 +- .../PiggyBank/PiggyBankRepository.php | 4 +- .../PiggyBankRepositoryInterface.php | 4 +- .../Controllers/AttachmentControllerTest.php | 1 + .../AvailableBudgetControllerTest.php | 74 ++++- .../V1/Controllers/BudgetControllerTest.php | 2 + .../Controllers/BudgetLimitControllerTest.php | 4 + .../V1/Controllers/CategoryControllerTest.php | 2 + .../Controllers/JournalLinkControllerTest.php | 251 +++++++++++++++++ .../V1/Controllers/LinkTypeControllerTest.php | 141 ++++++++++ .../Controllers/PiggyBankControllerTest.php | 255 ++++++++++++++++++ .../Controllers/PreferencesControllerTest.php | 139 ++++++++++ .../Controllers/RecurrenceControllerTest.php | 79 ++++++ .../Api/V1/Controllers/RuleControllerTest.php | 253 +++++++++++++++++ .../Controllers/RuleGroupControllerTest.php | 151 +++++++++++ .../Account/ShowControllerTest.php | 3 + tests/Unit/Factory/BillFactoryTest.php | 24 +- .../Events/VersionCheckEventHandlerTest.php | 2 +- 21 files changed, 1378 insertions(+), 21 deletions(-) create mode 100644 tests/Api/V1/Controllers/PiggyBankControllerTest.php create mode 100644 tests/Api/V1/Controllers/PreferencesControllerTest.php create mode 100644 tests/Api/V1/Controllers/RuleControllerTest.php create mode 100644 tests/Api/V1/Controllers/RuleGroupControllerTest.php diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php index b91dae8c5f..13c56f1495 100644 --- a/app/Api/V1/Controllers/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -154,7 +154,7 @@ class AvailableBudgetController extends Controller $data = $request->getAll(); $currency = $this->currencyRepository->findNull($data['currency_id']); if (null === $currency) { - $this->currencyRepository->findByCodeNull($data['currency_code']); + $currency = $this->currencyRepository->findByCodeNull($data['currency_code']); } if (null === $currency) { throw new FireflyException('Could not find the indicated currency.'); @@ -169,6 +169,8 @@ class AvailableBudgetController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } + + /** * Update the specified resource in storage. * diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php index 4b1da3528f..8ab73dbcdc 100644 --- a/app/Api/V1/Controllers/PiggyBankController.php +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -161,7 +161,7 @@ class PiggyBankController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } - throw new FireflyException('Could not store new piggy bank.'); // @codeCoverageIgnore + throw new FireflyException('Could not store new piggy bank.'); } diff --git a/app/Api/V1/Controllers/RuleController.php b/app/Api/V1/Controllers/RuleController.php index 42e016332a..07ff7c870c 100644 --- a/app/Api/V1/Controllers/RuleController.php +++ b/app/Api/V1/Controllers/RuleController.php @@ -101,7 +101,7 @@ class RuleController extends Controller // make paginator: $paginator = new LengthAwarePaginator($rules, $count, $pageSize, $this->parameters->get('page')); - $paginator->setPath(route('api.v1.piggy_banks.index') . $this->buildParams()); + $paginator->setPath(route('api.v1.rules.index') . $this->buildParams()); // present to user. $manager->setSerializer(new JsonApiSerializer($baseUrl)); diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php index 83185cec11..d0d9a8d07c 100644 --- a/app/Api/V1/Controllers/RuleGroupController.php +++ b/app/Api/V1/Controllers/RuleGroupController.php @@ -95,7 +95,7 @@ class RuleGroupController extends Controller // types to get, page size: $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; - // get list of budgets. Count it and split it. + // get list of rule groups. Count it and split it. $collection = $this->ruleGroupRepository->get(); $count = $collection->count(); $ruleGroups = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 325146cde4..a06aabe5c6 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -443,9 +443,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param array $data * - * @return PiggyBank + * @return PiggyBank|null */ - public function store(array $data): PiggyBank + public function store(array $data): ?PiggyBank { $data['order'] = $this->getMaxOrder() + 1; /** @var PiggyBank $piggyBank */ diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index dd606c48ba..d6af71404f 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -220,9 +220,9 @@ interface PiggyBankRepositoryInterface * * @param array $data * - * @return PiggyBank + * @return PiggyBank|null */ - public function store(array $data): PiggyBank; + public function store(array $data): ?PiggyBank; /** * Update existing piggy bank. diff --git a/tests/Api/V1/Controllers/AttachmentControllerTest.php b/tests/Api/V1/Controllers/AttachmentControllerTest.php index ed68ea80ca..d6273d3952 100644 --- a/tests/Api/V1/Controllers/AttachmentControllerTest.php +++ b/tests/Api/V1/Controllers/AttachmentControllerTest.php @@ -210,6 +210,7 @@ class AttachmentControllerTest extends TestCase * Store a new attachment. * * @covers \FireflyIII\Api\V1\Controllers\AttachmentController + * @covers \FireflyIII\Api\V1\Requests\AttachmentRequest */ public function testStore(): void { diff --git a/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php b/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php index ea52b0aef1..65bd2f7295 100644 --- a/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php +++ b/tests/Api/V1/Controllers/AvailableBudgetControllerTest.php @@ -129,7 +129,7 @@ class AvailableBudgetControllerTest extends TestCase // mock calls: $repository->shouldReceive('setUser')->once(); $repository->shouldReceive('setAvailableBudget')->once()->andReturn($availableBudget); - $currencyRepository->shouldReceive('findNull')->andReturn(TransactionCurrency::find(1)); + $currencyRepository->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::find(1)); // data to submit $data = [ @@ -148,6 +148,78 @@ class AvailableBudgetControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * Store new available budget without a valid currency. + * + * @covers \FireflyIII\Api\V1\Controllers\AvailableBudgetController + * @covers \FireflyIII\Api\V1\Requests\AvailableBudgetRequest + */ + public function testStoreNoCurrencyId(): void + { + /** @var AvailableBudget $availableBudget */ + $availableBudget = $this->user()->availableBudgets()->first(); + + // mock stuff: + $repository = $this->mock(BudgetRepositoryInterface::class); + $currencyRepository = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('setAvailableBudget')->once()->andReturn($availableBudget); + $currencyRepository->shouldReceive('findNull')->withArgs([1])->andReturn(null)->once(); + $currencyRepository->shouldReceive('findByCodeNull')->withArgs(['EUR'])->andReturn(TransactionCurrency::find(1))->once(); + + // data to submit + $data = [ + 'currency_id' => '1', + 'currency_code' => 'EUR', + 'amount' => '100', + 'start_date' => '2018-01-01', + 'end_date' => '2018-01-31', + ]; + + + // test API + $response = $this->post('/api/v1/available_budgets', $data); + $response->assertStatus(200); + $response->assertJson(['data' => ['type' => 'available_budgets', 'links' => true],]); + $response->assertSee($availableBudget->amount); // the amount + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + /** + * Store new available budget without a valid currency. + * + * @covers \FireflyIII\Api\V1\Controllers\AvailableBudgetController + * @covers \FireflyIII\Api\V1\Requests\AvailableBudgetRequest + */ + public function testStoreNoCurrencyAtAll(): void + { + // mock stuff: + $repository = $this->mock(BudgetRepositoryInterface::class); + $currencyRepository = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $currencyRepository->shouldReceive('findNull')->withArgs([1])->andReturn(null)->once(); + $currencyRepository->shouldReceive('findByCodeNull')->withArgs(['EUR'])->andReturn(null)->once(); + + // data to submit + $data = [ + 'currency_id' => '1', + 'currency_code' => 'EUR', + 'amount' => '100', + 'start_date' => '2018-01-01', + 'end_date' => '2018-01-31', + ]; + + + // test API + $response = $this->post('/api/v1/available_budgets', $data, ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('Could not find the indicated currency.'); // the amount + $response->assertHeader('Content-Type', 'application/json'); + } + /** * Update available budget. diff --git a/tests/Api/V1/Controllers/BudgetControllerTest.php b/tests/Api/V1/Controllers/BudgetControllerTest.php index ad5751b1cc..5c22cda9a1 100644 --- a/tests/Api/V1/Controllers/BudgetControllerTest.php +++ b/tests/Api/V1/Controllers/BudgetControllerTest.php @@ -113,6 +113,7 @@ class BudgetControllerTest extends TestCase * Store a new budget. * * @covers \FireflyIII\Api\V1\Controllers\BudgetController + * @covers \FireflyIII\Api\V1\Requests\BudgetRequest */ public function testStore(): void { @@ -144,6 +145,7 @@ class BudgetControllerTest extends TestCase * Update a budget. * * @covers \FireflyIII\Api\V1\Controllers\BudgetController + * @covers \FireflyIII\Api\V1\Requests\BudgetRequest */ public function testUpdate(): void { diff --git a/tests/Api/V1/Controllers/BudgetLimitControllerTest.php b/tests/Api/V1/Controllers/BudgetLimitControllerTest.php index d20d9296c5..3febef2744 100644 --- a/tests/Api/V1/Controllers/BudgetLimitControllerTest.php +++ b/tests/Api/V1/Controllers/BudgetLimitControllerTest.php @@ -191,6 +191,7 @@ class BudgetLimitControllerTest extends TestCase * Store new budget limit. * * @covers \FireflyIII\Api\V1\Controllers\BudgetLimitController + * @covers \FireflyIII\Api\V1\Requests\BudgetLimitRequest */ public function testStore(): void { @@ -229,6 +230,7 @@ class BudgetLimitControllerTest extends TestCase * Store new budget limit, but give error * * @covers \FireflyIII\Api\V1\Controllers\BudgetLimitController + * @covers \FireflyIII\Api\V1\Requests\BudgetLimitRequest */ public function testStoreBadBudget(): void { @@ -257,6 +259,7 @@ class BudgetLimitControllerTest extends TestCase * Test update of budget limit. * * @covers \FireflyIII\Api\V1\Controllers\BudgetLimitController + * @covers \FireflyIII\Api\V1\Requests\BudgetLimitRequest */ public function testUpdate(): void { @@ -296,6 +299,7 @@ class BudgetLimitControllerTest extends TestCase * Test update of budget limit but submit bad budget. * * @covers \FireflyIII\Api\V1\Controllers\BudgetLimitController + * @covers \FireflyIII\Api\V1\Requests\BudgetLimitRequest */ public function testUpdateBadBudget(): void { diff --git a/tests/Api/V1/Controllers/CategoryControllerTest.php b/tests/Api/V1/Controllers/CategoryControllerTest.php index 0bfb3dfe16..5e4161742b 100644 --- a/tests/Api/V1/Controllers/CategoryControllerTest.php +++ b/tests/Api/V1/Controllers/CategoryControllerTest.php @@ -115,6 +115,7 @@ class CategoryControllerTest extends TestCase * Store a new category. * * @covers \FireflyIII\Api\V1\Controllers\CategoryController + * @covers \FireflyIII\Api\V1\Requests\CategoryRequest */ public function testStore(): void { @@ -146,6 +147,7 @@ class CategoryControllerTest extends TestCase * Update a category. * * @covers \FireflyIII\Api\V1\Controllers\CategoryController + * @covers \FireflyIII\Api\V1\Requests\CategoryRequest */ public function testUpdate(): void { diff --git a/tests/Api/V1/Controllers/JournalLinkControllerTest.php b/tests/Api/V1/Controllers/JournalLinkControllerTest.php index c424ac1144..770f29d091 100644 --- a/tests/Api/V1/Controllers/JournalLinkControllerTest.php +++ b/tests/Api/V1/Controllers/JournalLinkControllerTest.php @@ -189,6 +189,207 @@ class JournalLinkControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * In this particular test the journal link request will fail. + * + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testStoreExistingLink(): 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(TransactionCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser'); + $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('getTransactions')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->andReturn($journal); + $repository->shouldReceive('findLink')->once()->andReturn(true); + + + // 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, ['Accept' => 'application/json']); + $response->assertStatus(422); + $response->assertSee('Already have a link between inward and outward.'); + + + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * In this particular test the JournalLinkRequest will report the failure. + * + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testStoreInvalidInward(): 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(TransactionCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser'); + $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('getTransactions')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->once()->withArgs([1])->andReturn(null); + $journalRepos->shouldReceive('findNull')->once()->withArgs([2])->andReturn(null); + + + // 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, ['Accept' => 'application/json']); + $response->assertSee('Invalid inward ID.'); // the creation moment. + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * In this particular test the JournalLinkRequest will report the failure. + * + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testStoreInvalidOutward(): 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(TransactionCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser'); + $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('getTransactions')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->once()->withArgs([1])->andReturn($journal); + $journalRepos->shouldReceive('findNull')->once()->withArgs([2])->andReturn(null); + + + // 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, ['Accept' => 'application/json']); + $response->assertSee('Invalid outward ID.'); + $response->assertStatus(422); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testStoreNoJournal(): 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(TransactionCollectorInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $journalRepos->shouldReceive('setUser'); + $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('getTransactions')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->twice()->withArgs([1])->andReturn($journal, null); + $journalRepos->shouldReceive('findNull')->twice()->withArgs([2])->andReturn($journal, null); + $repository->shouldReceive('findLink')->once()->andReturn(false); + + + // 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, ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('Source or destination is NULL.'); // the creation moment. + $response->assertHeader('Content-Type', 'application/json'); + } + /** * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest @@ -286,6 +487,56 @@ class JournalLinkControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController + * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest + */ + public function testUpdateNoJournal(): void + { + + // mock repositories + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(TransactionCollectorInterface::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'); + $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('getTransactions')->andReturn(new Collection([$transaction])); + + $journalRepos->shouldReceive('findNull')->twice()->withArgs([1])->andReturn($journal, null); + $journalRepos->shouldReceive('findNull')->twice()->withArgs([2])->andReturn($journal, null); + $repository->shouldReceive('findLink')->once()->andReturn(false); + + // 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(500); + $response->assertSee('Source or destination is NULL.'); // the creation moment. + $response->assertHeader('Content-Type', 'application/json'); + } + /** * @covers \FireflyIII\Api\V1\Controllers\JournalLinkController * @covers \FireflyIII\Api\V1\Requests\JournalLinkRequest diff --git a/tests/Api/V1/Controllers/LinkTypeControllerTest.php b/tests/Api/V1/Controllers/LinkTypeControllerTest.php index d296d3d7ba..003ae40c98 100644 --- a/tests/Api/V1/Controllers/LinkTypeControllerTest.php +++ b/tests/Api/V1/Controllers/LinkTypeControllerTest.php @@ -77,6 +77,36 @@ class LinkTypeControllerTest extends TestCase $response->assertStatus(204); } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + */ + public function testDeleteNotEditable(): 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' => false, + + ] + ); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + + // call API + $response = $this->delete('/api/v1/link_types/' . $linkType->id); + $response->assertStatus(500); + $response->assertSee('You cannot delete this link type'); + } + /** * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController */ @@ -154,6 +184,38 @@ class LinkTypeControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + * @covers \FireflyIII\Api\V1\Requests\LinkTypeRequest + */ + public function testStoreNotOwner(): void + { + $linkType = LinkType::first(); + + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $userRepository->shouldReceive('hasRole')->once()->andReturn(false); + + + // 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, ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertSee('You need the \"owner\"-role to do this.'); + } + /** * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController * @covers \FireflyIII\Api\V1\Requests\LinkTypeRequest @@ -196,5 +258,84 @@ class LinkTypeControllerTest extends TestCase $response->assertSee($linkType->created_at->toAtomString()); } + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + * @covers \FireflyIII\Api\V1\Requests\LinkTypeRequest + */ + public function testUpdateNotEditable(): 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' => false, + + ] + ); + + // mock calls: + $repository->shouldReceive('setUser'); + + // 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(500); + $response->assertSee('You cannot edit this link type '); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\LinkTypeController + * @covers \FireflyIII\Api\V1\Requests\LinkTypeRequest + */ + public function testUpdateNotOwner(): void + { + // mock stuff: + $repository = $this->mock(LinkTypeRepositoryInterface::class); + $userRepository = $this->mock(UserRepositoryInterface::class); + $userRepository->shouldReceive('hasRole')->once()->andReturn(false); + + // 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'); + + // 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(500); + $response->assertSee('You need the \"owner\"-role to do this.'); + } + } diff --git a/tests/Api/V1/Controllers/PiggyBankControllerTest.php b/tests/Api/V1/Controllers/PiggyBankControllerTest.php new file mode 100644 index 0000000000..6b80771ee0 --- /dev/null +++ b/tests/Api/V1/Controllers/PiggyBankControllerTest.php @@ -0,0 +1,255 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Laravel\Passport\Passport; +use Log; +use Mockery; +use Tests\TestCase; + +/** + * + * Class PiggyBankControllerTest + */ +class PiggyBankControllerTest extends TestCase +{ + /** + * Set up test + */ + public function setUp(): void + { + parent::setUp(); + Passport::actingAs($this->user()); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * Destroy piggy bank over API + * + * @covers \FireflyIII\Api\V1\Controllers\PiggyBankController + */ + public function testDelete(): void + { // mock stuff: + $repository = $this->mock(PiggyBankRepositoryInterface::class); + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('destroy')->once()->andReturn(true); + + // get piggy bank: + $piggyBank = $this->user()->piggyBanks()->first(); + + // call API + $response = $this->delete('/api/v1/piggy_banks/' . $piggyBank->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PiggyBankController + */ + public function testIndex(): void + { + // create stuff + $piggies = factory(PiggyBank::class, 10)->create(); + $repository = $this->mock(PiggyBankRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $repository->shouldReceive('getPiggyBanks')->withAnyArgs()->andReturn($piggies)->once(); + $repository->shouldReceive('getCurrentAmount')->andReturn('12'); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12'); + + $accountRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + + $currencyRepos->shouldReceive('setUser'); + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first()); + + // test API + $response = $this->get('/api/v1/piggy_banks'); + $response->assertStatus(200); + $response->assertJson(['data' => [],]); + $response->assertJson(['meta' => ['pagination' => ['total' => 10, 'count' => 10, 'per_page' => true, 'current_page' => 1, 'total_pages' => 1]],]); + $response->assertJson( + ['links' => ['self' => true, 'first' => true, 'last' => true,],] + ); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PiggyBankController + */ + public function testShow(): void + { + // create stuff + $piggy = $this->user()->piggyBanks()->first(); + + // mock stuff: + $repository = $this->mock(PiggyBankRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $currencyRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('setUser')->once(); + + $repository->shouldReceive('getCurrentAmount')->andReturn('12'); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12'); + + $accountRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + + $currencyRepos->shouldReceive('setUser'); + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first()); + + // test API + $response = $this->get('/api/v1/piggy_banks/' . $piggy->id); + $response->assertStatus(200); + $response->assertJson(['data' => ['type' => 'piggy_banks', 'links' => true],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PiggyBankController + * @covers \FireflyIII\Api\V1\Requests\PiggyBankRequest + */ + public function testStore(): void + { + // create stuff + $piggy = $this->user()->piggyBanks()->first(); + + // mock stuff: + $repository = $this->mock(PiggyBankRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $accountRepos->shouldReceive('setUser')->once(); + $repository->shouldReceive('store')->once()->andReturn($piggy); + + $repository->shouldReceive('getCurrentAmount')->andReturn('12')->once(); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12')->once(); + + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->once(); + + $currencyRepos->shouldReceive('setUser')->once(); + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first())->once(); + + $data = [ + 'name' => 'New piggy #' . random_int(1, 100000), + 'account_id' => 1, + 'target_amount' => '100', + ]; + + // test API + $response = $this->post('/api/v1/piggy_banks/', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson(['data' => ['type' => 'piggy_banks', 'links' => true],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PiggyBankController + * @covers \FireflyIII\Api\V1\Requests\PiggyBankRequest + */ + public function testStoreNull(): void + { + // mock stuff: + $repository = $this->mock(PiggyBankRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $repository->shouldReceive('store')->once()->andReturn(null)->once(); + + + $data = [ + 'name' => 'New piggy #' . random_int(1, 100000), + 'account_id' => 1, + 'target_amount' => '100', + ]; + + // test API + $response = $this->post('/api/v1/piggy_banks/', $data, ['Accept' => 'application/json']); + $response->assertStatus(500); + $response->assertHeader('Content-Type', 'application/json'); + $response->assertSee('Could not store new piggy bank.'); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PiggyBankController + * @covers \FireflyIII\Api\V1\Requests\PiggyBankRequest + */ + public function testUpdate(): void + { + // create stuff + $piggy = $this->user()->piggyBanks()->first(); + + // mock stuff: + $repository = $this->mock(PiggyBankRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser'); + $currencyRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('setUser')->once(); + + $repository->shouldReceive('update')->once()->andReturn($piggy); + + $repository->shouldReceive('getCurrentAmount')->andReturn('12'); + $repository->shouldReceive('getSuggestedMonthlyAmount')->andReturn('12'); + + $accountRepos->shouldReceive('setUser'); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + + $currencyRepos->shouldReceive('setUser'); + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first()); + + $data = [ + 'name' => 'new pigy bank ' . random_int(1, 10000), + 'account_id' => 1, + 'target_amount' => '100', + ]; + + // test API + $response = $this->put('/api/v1/piggy_banks/' . $piggy->id, $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson(['data' => ['type' => 'piggy_banks', 'links' => true],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + +} \ No newline at end of file diff --git a/tests/Api/V1/Controllers/PreferencesControllerTest.php b/tests/Api/V1/Controllers/PreferencesControllerTest.php new file mode 100644 index 0000000000..f2fdf4e3d1 --- /dev/null +++ b/tests/Api/V1/Controllers/PreferencesControllerTest.php @@ -0,0 +1,139 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + +use FireflyIII\Models\Preference; +use Laravel\Passport\Passport; +use Log; +use Mockery; +use Preferences; +use Tests\TestCase; + +/** + * + * Class PreferencesControllerTest + */ +class PreferencesControllerTest extends TestCase +{ + + /** + * Set up test + */ + public function setUp(): void + { + parent::setUp(); + Passport::actingAs($this->user()); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PreferenceController + */ + public function testIndex(): void + { + + $available = ['language', 'customFiscalYear', 'fiscalYearStart', 'currencyPreference', 'transaction_journal_optional_fields', 'frontPageAccounts', + 'viewRange', 'listPageSize, twoFactorAuthEnabled',]; + + foreach ($available as $pref) { + Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), $pref])->once(); + } + + + // call API + $response = $this->get('/api/v1/preferences'); + $response->assertStatus(200); + } + + public function testShow(): void + { + /** @var Preference $preference */ + $preference = $this->user()->preferences()->first(); + + $response = $this->get('/api/v1/preferences/' . $preference->id); + $response->assertStatus(200); + $response->assertSee($preference->name); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PreferenceController + * @covers \FireflyIII\Api\V1\Requests\PreferenceRequest + */ + public function testUpdateArray(): void + { + /** @var Preference $preference */ + $preference = Preferences::setForUser($this->user(), 'frontPageAccounts', [1, 2, 3]); + $data = ['data' => '4,5,6']; + $response = $this->put('/api/v1/preferences/' . $preference->id, $data, ['Accept' => 'application/json']); + $response->assertSee($preference->name); + $response->assertStatus(200); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PreferenceController + * @covers \FireflyIII\Api\V1\Requests\PreferenceRequest + */ + public function testUpdateBoolean(): void + { + /** @var Preference $preference */ + $preference = Preferences::setForUser($this->user(), 'twoFactorAuthEnabled', false); + $data = ['data' => '1']; + $response = $this->put('/api/v1/preferences/' . $preference->id, $data, ['Accept' => 'application/json']); + $response->assertSee($preference->name); + $response->assertStatus(200); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PreferenceController + * @covers \FireflyIII\Api\V1\Requests\PreferenceRequest + */ + public function testUpdateDefault(): void + { + /** @var Preference $preference */ + $preference = Preferences::setForUser($this->user(), 'currencyPreference', false); + $data = ['data' => 'EUR']; + $response = $this->put('/api/v1/preferences/' . $preference->id, $data, ['Accept' => 'application/json']); + $response->assertSee($preference->name); + $response->assertStatus(200); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\PreferenceController + * @covers \FireflyIII\Api\V1\Requests\PreferenceRequest + */ + public function testUpdateInteger(): void + { + /** @var Preference $preference */ + $preference = Preferences::setForUser($this->user(), 'listPageSize', 13); + $data = ['data' => '434']; + $response = $this->put('/api/v1/preferences/' . $preference->id, $data, ['Accept' => 'application/json']); + $response->assertSee($preference->name); + $response->assertStatus(200); + + } + +} \ No newline at end of file diff --git a/tests/Api/V1/Controllers/RecurrenceControllerTest.php b/tests/Api/V1/Controllers/RecurrenceControllerTest.php index ac41bf1309..59100edca7 100644 --- a/tests/Api/V1/Controllers/RecurrenceControllerTest.php +++ b/tests/Api/V1/Controllers/RecurrenceControllerTest.php @@ -1628,4 +1628,83 @@ class RecurrenceControllerTest extends TestCase $response->assertHeader('Content-Type', 'application/vnd.api+json'); } + /** + * Just a basic test because the store() tests cover everything. + * + * @covers \FireflyIII\Api\V1\Controllers\RecurrenceController + * @covers \FireflyIII\Api\V1\Requests\RecurrenceRequest + */ + public function testUpdate(): 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('update')->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->put('/api/v1/recurrences/' . $recurrence->id, $data, ['Accept' => 'application/json']); + $response->assertSee($recurrence->title); + $response->assertStatus(200); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + } diff --git a/tests/Api/V1/Controllers/RuleControllerTest.php b/tests/Api/V1/Controllers/RuleControllerTest.php new file mode 100644 index 0000000000..86f195db9d --- /dev/null +++ b/tests/Api/V1/Controllers/RuleControllerTest.php @@ -0,0 +1,253 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use FireflyIII\Models\Rule; +use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use Laravel\Passport\Passport; +use Log; +use Tests\TestCase; + +/** + * + * Class RuleControllerTest + */ +class RuleControllerTest 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\RuleController + */ + public function testDelete(): void + { + /** @var Rule $rule */ + $rule = $this->user()->rules()->first(); + + // mock stuff: + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + + // mock calls: + $ruleRepos->shouldReceive('setUser')->once(); + $ruleRepos->shouldReceive('destroy')->once()->andReturn(true); + + $response = $this->delete('/api/v1/rules/' . $rule->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleController + */ + public function testIndex(): void + { + $rules = $this->user()->rules()->get(); + + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + $ruleRepos->shouldReceive('setUser')->once(); + $ruleRepos->shouldReceive('getAll')->once()->andReturn($rules); + + + // call API + $response = $this->get('/api/v1/rules'); + $response->assertStatus(200); + $response->assertSee($rules->first()->title); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleController + */ + public function testShow(): void + { + $rule = $this->user()->rules()->first(); + + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + $ruleRepos->shouldReceive('setUser')->once(); + + + // call API + $response = $this->get('/api/v1/rules/' . $rule->id); + $response->assertStatus(200); + $response->assertSee($rule->title); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleController + * @covers \FireflyIII\Api\V1\Requests\RuleRequest + */ + public function testStore(): void + { + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + $ruleRepos->shouldReceive('setUser')->once(); + $rule = $this->user()->rules()->first(); + $data = [ + 'title' => 'Store new rule', + 'rule_group_id' => 1, + 'trigger' => 'store-journal', + 'strict' => 1, + 'stop_processing' => 1, + 'active' => 1, + 'rule_triggers' => [ + [ + 'name' => 'description_is', + 'value' => 'Hello', + 'stop_processing' => 1, + ], + ], + 'rule_actions' => [ + [ + 'name' => 'add_tag', + 'value' => 'A', + 'stop_processing' => 1, + ], + ], + ]; + + $ruleRepos->shouldReceive('store')->once()->andReturn($rule); + + // test API + $response = $this->post('/api/v1/rules', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleController + * @covers \FireflyIII\Api\V1\Requests\RuleRequest + */ + public function testUpdate(): void + { + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + $ruleRepos->shouldReceive('setUser')->once(); + /** @var Rule $rule */ + $rule = $this->user()->rules()->first(); + $data = [ + 'title' => 'Store new rule', + 'rule_group_id' => 1, + 'trigger' => 'store-journal', + 'strict' => 1, + 'stop_processing' => 1, + 'active' => 1, + 'rule_triggers' => [ + [ + 'name' => 'description_is', + 'value' => 'Hello', + 'stop_processing' => 1, + ], + ], + 'rule_actions' => [ + [ + 'name' => 'add_tag', + 'value' => 'A', + 'stop_processing' => 1, + ], + ], + ]; + + $ruleRepos->shouldReceive('update')->once()->andReturn($rule); + + // test API + $response = $this->put('/api/v1/rules/' . $rule->id, $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleController + * @covers \FireflyIII\Api\V1\Requests\RuleRequest + */ + public function testStoreNoTriggers(): void + { + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + $ruleRepos->shouldReceive('setUser')->once(); + $rule = $this->user()->rules()->first(); + $data = [ + 'title' => 'Store new rule', + 'rule_group_id' => 1, + 'trigger' => 'store-journal', + 'strict' => 1, + 'stop_processing' => 1, + 'active' => 1, + 'rule_triggers' => [ + ], + 'rule_actions' => [ + [ + 'name' => 'add_tag', + 'value' => 'A', + 'stop_processing' => 1, + ], + ], + ]; + + // test API + $response = $this->post('/api/v1/rules', $data, ['Accept' => 'application/json']); + $response->assertStatus(422); + $response->assertSee(''); + + } + + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleController + * @covers \FireflyIII\Api\V1\Requests\RuleRequest + */ + public function testStoreNoActions(): void + { + $ruleRepos = $this->mock(RuleRepositoryInterface::class); + $ruleRepos->shouldReceive('setUser')->once(); + $rule = $this->user()->rules()->first(); + $data = [ + 'title' => 'Store new rule', + 'rule_group_id' => 1, + 'trigger' => 'store-journal', + 'strict' => 1, + 'stop_processing' => 1, + 'active' => 1, + 'rule_triggers' => [ + [ + 'name' => 'description_is', + 'value' => 'Hello', + 'stop_processing' => 1, + ], + ], + 'rule_actions' => [ + ], + ]; + + // test API + $response = $this->post('/api/v1/rules', $data, ['Accept' => 'application/json']); + $response->assertStatus(422); + } + +} \ No newline at end of file diff --git a/tests/Api/V1/Controllers/RuleGroupControllerTest.php b/tests/Api/V1/Controllers/RuleGroupControllerTest.php new file mode 100644 index 0000000000..f77ea61029 --- /dev/null +++ b/tests/Api/V1/Controllers/RuleGroupControllerTest.php @@ -0,0 +1,151 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use Laravel\Passport\Passport; +use Log; +use Tests\TestCase; + +/** + * + * Class RuleGroupControllerTest + */ +class RuleGroupControllerTest 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\RuleGroupController + */ + public function testDelete(): void + { + /** @var RuleGroup $ruleGroup */ + $ruleGroup = $this->user()->ruleGroups()->first(); + + // mock stuff: + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + + // mock calls: + $ruleGroupRepos->shouldReceive('setUser')->once(); + $ruleGroupRepos->shouldReceive('destroy')->once()->andReturn(true); + + $response = $this->delete('/api/v1/rule_groups/' . $ruleGroup->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleGroupController + */ + public function testIndex(): void + { + $ruleGroups = $this->user()->ruleGroups()->get(); + + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->shouldReceive('setUser')->once(); + $ruleGroupRepos->shouldReceive('get')->once()->andReturn($ruleGroups); + + + // call API + $response = $this->get('/api/v1/rule_groups'); + $response->assertStatus(200); + $response->assertSee($ruleGroups->first()->title); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleGroupController + */ + public function testShow(): void + { + /** @var RuleGroup $ruleGroup */ + $ruleGroup = $this->user()->ruleGroups()->first(); + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->shouldReceive('setUser')->once(); + + + // call API + $response = $this->get('/api/v1/rule_groups/' . $ruleGroup->id); + $response->assertStatus(200); + $response->assertSee($ruleGroup->title); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleGroupController + * @covers \FireflyIII\Api\V1\Requests\RuleGroupRequest + */ + public function testStore(): void + { + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->shouldReceive('setUser')->once(); + $ruleGroup = $this->user()->ruleGroups()->first(); + $data = [ + 'title' => 'Store new rule ' . random_int(1, 100000), + 'active' => 1, + 'description' => 'Hello', + ]; + + $ruleGroupRepos->shouldReceive('store')->once()->andReturn($ruleGroup); + + // test API + $response = $this->post('/api/v1/rule_groups', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\RuleGroupController + * @covers \FireflyIII\Api\V1\Requests\RuleGroupRequest + */ + public function testUpdate(): void + { + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->shouldReceive('setUser')->once(); + $ruleGroup = $this->user()->ruleGroups()->first(); + $data = [ + 'title' => 'Store new rule ' . random_int(1, 100000), + 'active' => 1, + 'description' => 'Hello', + ]; + + $ruleGroupRepos->shouldReceive('update')->once()->andReturn($ruleGroup); + + // test API + $response = $this->put('/api/v1/rule_groups/' . $ruleGroup->id, $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + + } + +} \ No newline at end of file diff --git a/tests/Feature/Controllers/Account/ShowControllerTest.php b/tests/Feature/Controllers/Account/ShowControllerTest.php index a6d3edf92b..5105bf4af5 100644 --- a/tests/Feature/Controllers/Account/ShowControllerTest.php +++ b/tests/Feature/Controllers/Account/ShowControllerTest.php @@ -78,6 +78,7 @@ class ShowControllerTest extends TestCase $repository = $this->mock(AccountRepositoryInterface::class); $repository->shouldReceive('oldestJournalDate')->andReturn(clone $date)->once(); $repository->shouldReceive('getMetaValue')->andReturn(''); + $repository->shouldReceive('isLiability')->andReturn(false); $transaction = factory(Transaction::class)->make(); @@ -125,6 +126,7 @@ class ShowControllerTest extends TestCase $repository = $this->mock(AccountRepositoryInterface::class); $repository->shouldReceive('oldestJournalDate')->andReturn(clone $date)->once(); $repository->shouldReceive('getMetaValue')->andReturn(''); + $repository->shouldReceive('isLiability')->andReturn(false); $transaction = factory(Transaction::class)->make(); @@ -206,6 +208,7 @@ class ShowControllerTest extends TestCase $repository = $this->mock(AccountRepositoryInterface::class); $repository->shouldReceive('oldestJournalDate')->andReturn(new Carbon); $repository->shouldReceive('getMetaValue')->andReturn(''); + $repository->shouldReceive('isLiability')->andReturn(false); $collector->shouldReceive('setTypes')->andReturnSelf(); $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); diff --git a/tests/Unit/Factory/BillFactoryTest.php b/tests/Unit/Factory/BillFactoryTest.php index af07cc5dda..048cff0a37 100644 --- a/tests/Unit/Factory/BillFactoryTest.php +++ b/tests/Unit/Factory/BillFactoryTest.php @@ -42,16 +42,17 @@ class BillFactoryTest extends TestCase public function testCreateBasic(): void { $data = [ - 'name' => 'Some new bill #' . random_int(1, 10000), - 'amount_min' => '5', - 'transaction_currency_id' => 1, - 'amount_max' => '10', - 'date' => '2018-01-01', - 'repeat_freq' => 'monthly', - 'skip' => 0, - 'automatch' => true, - 'active' => true, - 'notes' => 'Hello!', + 'name' => 'Some new bill #' . random_int(1, 10000), + 'amount_min' => '5', + 'currency_id' => 1, + 'currency_code' => '', + 'amount_max' => '10', + 'date' => '2018-01-01', + 'repeat_freq' => 'monthly', + 'skip' => 0, + 'automatch' => true, + 'active' => true, + 'notes' => 'Hello!', ]; /** @var BillFactory $factory */ @@ -81,7 +82,8 @@ class BillFactoryTest extends TestCase 'amount_max' => '10', 'date' => '2018-01-01', 'repeat_freq' => 'monthly', - 'transaction_currency_id' => 1, + 'currency_id' => 1, + 'currency_code' => '', 'skip' => 0, 'automatch' => true, 'active' => true, diff --git a/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php b/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php index 528b5bbc2d..bb33fbab54 100644 --- a/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php @@ -162,7 +162,7 @@ class VersionCheckEventHandlerTest extends TestCase // report on config variables: FireflyConfig::shouldReceive('get')->withArgs(['last_update_check', Mockery::any()])->once()->andReturn($checkConfig); - FireflyConfig::shouldReceive('set')->withArgs(['last_update_check', Mockery::any()])->once()->andReturn($checkConfig); + //FireflyConfig::shouldReceive('set')->withArgs(['last_update_check', Mockery::any()])->once()->andReturn($checkConfig); $handler = new VersionCheckEventHandler; $handler->checkForUpdates($event); From b174a06b86f5fb8092667e05cc99efab01cbed4e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 24 Aug 2018 07:17:50 +0200 Subject: [PATCH 121/166] Rewrite text in env files. --- .env.docker | 5 ++++- .env.example | 5 ++++- .env.heroku | 5 ++++- .env.sandstorm | 5 ++++- .env.testing | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.env.docker b/.env.docker index 2ac31ed6a7..d88c6915a7 100644 --- a/.env.docker +++ b/.env.docker @@ -17,8 +17,11 @@ APP_KEY=${FF_APP_KEY} # Example: Europe/Amsterdam TZ=UTC -# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. +# This variable must match your installation's external address but keep in mind that +# it's only used on the command line as a fallback value. APP_URL=${APP_URL} + +# TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. TRUSTED_PROXIES=${TRUSTED_PROXIES} # The log channel defines where your log entries go to. diff --git a/.env.example b/.env.example index 0daa6aee41..571115cda6 100644 --- a/.env.example +++ b/.env.example @@ -17,8 +17,11 @@ APP_KEY=SomeRandomStringOf32CharsExactly # Example: Europe/Amsterdam TZ=${TZ} -# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. +# This variable must match your installation's external address but keep in mind that +# it's only used on the command line as a fallback value. APP_URL=http://localhost + +# TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. TRUSTED_PROXIES= # The log channel defines where your log entries go to. diff --git a/.env.heroku b/.env.heroku index 4925830036..972350d645 100644 --- a/.env.heroku +++ b/.env.heroku @@ -17,8 +17,11 @@ APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2 # Example: Europe/Amsterdam TZ=UTC -# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. +# This variable must match your installation's external address but keep in mind that +# it's only used on the command line as a fallback value. APP_URL=http://localhost + +# TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. TRUSTED_PROXIES= # The log channel defines where your log entries go to. diff --git a/.env.sandstorm b/.env.sandstorm index 138001852b..ce73089fe8 100755 --- a/.env.sandstorm +++ b/.env.sandstorm @@ -17,8 +17,11 @@ APP_KEY=SomeRandomStringOf32CharsExactly # Example: Europe/Amsterdam TZ=UTC -# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. +# This variable must match your installation's external address but keep in mind that +# it's only used on the command line as a fallback value. APP_URL=http://localhost + +# TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. TRUSTED_PROXIES= # The log channel defines where your log entries go to. diff --git a/.env.testing b/.env.testing index 94958107bb..27d1d955d5 100644 --- a/.env.testing +++ b/.env.testing @@ -17,8 +17,11 @@ APP_KEY=TestTestTestTestTestTestTestTest # Example: Europe/Amsterdam TZ=Europe/Amsterdam -# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy. +# This variable must match your installation's external address but keep in mind that +# it's only used on the command line as a fallback value. APP_URL=http://localhost + +# TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. TRUSTED_PROXIES= # The log channel defines where your log entries go to. From 2b54363dd750b0f86e93dccff9bf72d6538ac7ed Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 24 Aug 2018 07:18:33 +0200 Subject: [PATCH 122/166] Improve test coverage. --- app/Factory/AccountFactory.php | 1 + app/Factory/AccountMetaFactory.php | 9 +- app/Factory/TransactionFactory.php | 6 +- app/Factory/TransactionTypeFactory.php | 1 - .../Chart/Basic/ChartJsGenerator.php | 4 +- .../Report/Account/MonthReportGenerator.php | 2 + .../Account/MultiYearReportGenerator.php | 2 + .../Report/Account/YearReportGenerator.php | 2 + .../Report/Audit/MonthReportGenerator.php | 7 + .../Report/Audit/MultiYearReportGenerator.php | 1 + .../Report/Audit/YearReportGenerator.php | 1 + .../Report/Budget/MonthReportGenerator.php | 2 + .../Budget/MultiYearReportGenerator.php | 1 + .../Report/Budget/YearReportGenerator.php | 1 + .../Report/Category/MonthReportGenerator.php | 1 + .../Category/MultiYearReportGenerator.php | 1 + .../Report/Category/YearReportGenerator.php | 1 + .../Report/ReportGeneratorFactory.php | 1 + .../Report/Standard/MonthReportGenerator.php | 1 + .../Standard/MultiYearReportGenerator.php | 1 + .../Report/Standard/YearReportGenerator.php | 1 + app/Generator/Report/Support.php | 1 + .../Report/Tag/MonthReportGenerator.php | 1 + .../Report/Tag/MultiYearReportGenerator.php | 1 + .../Report/Tag/YearReportGenerator.php | 1 + tests/Unit/Factory/AccountFactoryTest.php | 61 ++ tests/Unit/Factory/AccountMetaFactoryTest.php | 121 ++++ tests/Unit/Factory/AttachmentFactoryTest.php | 71 +++ tests/Unit/Factory/BillFactoryTest.php | 31 +- tests/Unit/Factory/BudgetFactoryTest.php | 11 + tests/Unit/Factory/CategoryFactoryTest.php | 11 + .../Factory/PiggyBankEventFactoryTest.php | 11 + tests/Unit/Factory/PiggyBankFactoryTest.php | 11 + tests/Unit/Factory/RecurrenceFactoryTest.php | 552 ++++++++++++++++++ tests/Unit/Factory/TagFactoryTest.php | 11 + .../TransactionCurrencyFactoryTest.php | 11 + tests/Unit/Factory/TransactionFactoryTest.php | 336 ++++++++++- .../Factory/TransactionJournalFactoryTest.php | 75 +++ .../TransactionJournalMetaFactoryTest.php | 11 + .../Factory/TransactionTypeFactoryTest.php | 62 ++ .../Chart/Basic/ChartJsGeneratorTest.php | 156 +++++ .../Routine/File/ImportableConverterTest.php | 3 +- 42 files changed, 1573 insertions(+), 23 deletions(-) create mode 100644 tests/Unit/Factory/AccountMetaFactoryTest.php create mode 100644 tests/Unit/Factory/AttachmentFactoryTest.php create mode 100644 tests/Unit/Factory/RecurrenceFactoryTest.php create mode 100644 tests/Unit/Factory/TransactionTypeFactoryTest.php create mode 100644 tests/Unit/Generator/Chart/Basic/ChartJsGeneratorTest.php diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php index 26e3a5c5e0..6be9b42330 100644 --- a/app/Factory/AccountFactory.php +++ b/app/Factory/AccountFactory.php @@ -132,6 +132,7 @@ class AccountFactory } /** + * * @param string $accountName * @param string $accountType * diff --git a/app/Factory/AccountMetaFactory.php b/app/Factory/AccountMetaFactory.php index 67ec707046..5861e81b4d 100644 --- a/app/Factory/AccountMetaFactory.php +++ b/app/Factory/AccountMetaFactory.php @@ -65,7 +65,7 @@ class AccountMetaFactory // if $data has field and $entry is null, create new one: if (null === $entry) { Log::debug(sprintf('Created meta-field "%s":"%s" for account #%d ("%s") ', $field, $value, $account->id, $account->name)); - $this->create(['account_id' => $account->id, 'name' => $field, 'data' => $value]); + return $this->create(['account_id' => $account->id, 'name' => $field, 'data' => $value]); } // if $data has field and $entry is not null, update $entry: @@ -75,12 +75,13 @@ class AccountMetaFactory Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $value, $account->id, $account->name)); } } - if ('' === $value && null !== $entry && isset($data[$field])) { + if ('' === $value && null !== $entry) { try { $entry->delete(); - } catch (Exception $e) { - Log::debug(sprintf('Could not delete entry: %s', $e->getMessage())); + } catch (Exception $e) { // @codeCoverageIgnore + Log::debug(sprintf('Could not delete entry: %s', $e->getMessage())); // @codeCoverageIgnore } + return null; } return $entry; diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 39d483b46a..57fb616b4b 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -59,10 +59,10 @@ class TransactionFactory $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 + throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); } if (null === $currencyId) { - throw new FireflyException('Cannot store transaction without currency information.'); // @codeCoverageIgnore + throw new FireflyException('Cannot store transaction without currency information.'); } $data['foreign_amount'] = '' === (string)$data['foreign_amount'] ? null : $data['foreign_amount']; Log::debug(sprintf('Create transaction for account #%d ("%s") with amount %s', $data['account']->id, $data['account']->name, $data['amount'])); @@ -154,7 +154,7 @@ class TransactionFactory ] ); if (null === $source || null === $dest) { - throw new FireflyException('Could not create transactions.'); + throw new FireflyException('Could not create transactions.'); // @codeCoverageIgnore } // set foreign currency diff --git a/app/Factory/TransactionTypeFactory.php b/app/Factory/TransactionTypeFactory.php index 05f0fdf67d..d684555390 100644 --- a/app/Factory/TransactionTypeFactory.php +++ b/app/Factory/TransactionTypeFactory.php @@ -28,7 +28,6 @@ namespace FireflyIII\Factory; use FireflyIII\Models\TransactionType; /** - * @codeCoverageIgnore * Class TransactionTypeFactory */ class TransactionTypeFactory diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index c21522586e..875774e4de 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -120,7 +120,7 @@ class ChartJsGenerator implements GeneratorInterface // different sort when values are positive and when they're negative. asort($data); $next = next($data); - if (!\is_bool($next) && 1 === bccomp($next, '0')) { + if (!\is_bool($next) && 1 === bccomp((string)$next, '0')) { // next is positive, sort other way around. arsort($data); } @@ -129,7 +129,7 @@ class ChartJsGenerator implements GeneratorInterface $index = 0; foreach ($data as $key => $value) { // make larger than 0 - $chartData['datasets'][0]['data'][] = (float)app('steam')->positive($value); + $chartData['datasets'][0]['data'][] = (float)app('steam')->positive((string)$value); $chartData['datasets'][0]['backgroundColor'][] = ChartColour::getColour($index); $chartData['labels'][] = $key; ++$index; diff --git a/app/Generator/Report/Account/MonthReportGenerator.php b/app/Generator/Report/Account/MonthReportGenerator.php index ab135909f7..f9a53e5979 100644 --- a/app/Generator/Report/Account/MonthReportGenerator.php +++ b/app/Generator/Report/Account/MonthReportGenerator.php @@ -30,6 +30,8 @@ use Throwable; /** * Class MonthReportGenerator. + * + * @codeCoverageIgnore */ class MonthReportGenerator implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Account/MultiYearReportGenerator.php b/app/Generator/Report/Account/MultiYearReportGenerator.php index a4d17d0d83..ada1211667 100644 --- a/app/Generator/Report/Account/MultiYearReportGenerator.php +++ b/app/Generator/Report/Account/MultiYearReportGenerator.php @@ -24,6 +24,8 @@ namespace FireflyIII\Generator\Report\Account; /** * Class MultiYearReportGenerator. + * + * @codeCoverageIgnore */ class MultiYearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Account/YearReportGenerator.php b/app/Generator/Report/Account/YearReportGenerator.php index c1e890ba19..bdee367fed 100644 --- a/app/Generator/Report/Account/YearReportGenerator.php +++ b/app/Generator/Report/Account/YearReportGenerator.php @@ -24,6 +24,8 @@ namespace FireflyIII\Generator\Report\Account; /** * Class YearReportGenerator. + * + * @codeCoverageIgnore */ class YearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index 5a68b7e41f..26e1ac3f43 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -97,6 +97,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Collection $accounts * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setAccounts(Collection $accounts): ReportGeneratorInterface { @@ -111,6 +112,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Collection $budgets * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setBudgets(Collection $budgets): ReportGeneratorInterface { @@ -123,6 +125,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Collection $categories * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setCategories(Collection $categories): ReportGeneratorInterface { @@ -135,6 +138,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Carbon $date * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setEndDate(Carbon $date): ReportGeneratorInterface { @@ -149,6 +153,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Collection $expense * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setExpense(Collection $expense): ReportGeneratorInterface { @@ -161,6 +166,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Carbon $date * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setStartDate(Carbon $date): ReportGeneratorInterface { @@ -175,6 +181,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * @param Collection $tags * * @return ReportGeneratorInterface + * @codeCoverageIgnore */ public function setTags(Collection $tags): ReportGeneratorInterface { diff --git a/app/Generator/Report/Audit/MultiYearReportGenerator.php b/app/Generator/Report/Audit/MultiYearReportGenerator.php index 0bd3b5697f..ade7d0b84b 100644 --- a/app/Generator/Report/Audit/MultiYearReportGenerator.php +++ b/app/Generator/Report/Audit/MultiYearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Audit; /** * Class MultiYearReportGenerator. + * @codeCoverageIgnore */ class MultiYearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Audit/YearReportGenerator.php b/app/Generator/Report/Audit/YearReportGenerator.php index e341718728..4ff82713f4 100644 --- a/app/Generator/Report/Audit/YearReportGenerator.php +++ b/app/Generator/Report/Audit/YearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Audit; /** * Class YearReportGenerator. + * @codeCoverageIgnore */ class YearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index dcda49ed7a..6b7539c0d4 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -39,6 +39,8 @@ use Throwable; /** * Class MonthReportGenerator. + * + * @codeCoverageIgnore */ class MonthReportGenerator extends Support implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Budget/MultiYearReportGenerator.php b/app/Generator/Report/Budget/MultiYearReportGenerator.php index 1e32b8d8ce..ecfad642be 100644 --- a/app/Generator/Report/Budget/MultiYearReportGenerator.php +++ b/app/Generator/Report/Budget/MultiYearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Budget; /** * Class MultiYearReportGenerator. + * @codeCoverageIgnore */ class MultiYearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Budget/YearReportGenerator.php b/app/Generator/Report/Budget/YearReportGenerator.php index a025513451..4323ce2ddd 100644 --- a/app/Generator/Report/Budget/YearReportGenerator.php +++ b/app/Generator/Report/Budget/YearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Budget; /** * Class YearReportGenerator. + * @codeCoverageIgnore */ class YearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index 02fa859ca8..ad477f15f1 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -40,6 +40,7 @@ use Throwable; /** * Class MonthReportGenerator. + * @codeCoverageIgnore */ class MonthReportGenerator extends Support implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Category/MultiYearReportGenerator.php b/app/Generator/Report/Category/MultiYearReportGenerator.php index 2fe96eb9cd..39a881178e 100644 --- a/app/Generator/Report/Category/MultiYearReportGenerator.php +++ b/app/Generator/Report/Category/MultiYearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Category; /** * Class MultiYearReportGenerator. + * @codeCoverageIgnore */ class MultiYearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Category/YearReportGenerator.php b/app/Generator/Report/Category/YearReportGenerator.php index 920ea366e7..57f07fd975 100644 --- a/app/Generator/Report/Category/YearReportGenerator.php +++ b/app/Generator/Report/Category/YearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Category; /** * Class YearReportGenerator. + * @codeCoverageIgnore */ class YearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/ReportGeneratorFactory.php b/app/Generator/Report/ReportGeneratorFactory.php index 6e06480a33..04e264bb3d 100644 --- a/app/Generator/Report/ReportGeneratorFactory.php +++ b/app/Generator/Report/ReportGeneratorFactory.php @@ -27,6 +27,7 @@ use FireflyIII\Exceptions\FireflyException; /** * Class ReportGeneratorFactory. + * @codeCoverageIgnore */ class ReportGeneratorFactory { diff --git a/app/Generator/Report/Standard/MonthReportGenerator.php b/app/Generator/Report/Standard/MonthReportGenerator.php index ad0a9c0b99..ba6fc749a3 100644 --- a/app/Generator/Report/Standard/MonthReportGenerator.php +++ b/app/Generator/Report/Standard/MonthReportGenerator.php @@ -31,6 +31,7 @@ use Throwable; /** * Class MonthReportGenerator. + * @codeCoverageIgnore */ class MonthReportGenerator implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Standard/MultiYearReportGenerator.php b/app/Generator/Report/Standard/MultiYearReportGenerator.php index 9310554b33..e9ba9d40f5 100644 --- a/app/Generator/Report/Standard/MultiYearReportGenerator.php +++ b/app/Generator/Report/Standard/MultiYearReportGenerator.php @@ -30,6 +30,7 @@ use Throwable; /** * Class MonthReportGenerator. + * @codeCoverageIgnore */ class MultiYearReportGenerator implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Standard/YearReportGenerator.php b/app/Generator/Report/Standard/YearReportGenerator.php index bec85e462a..7479aa7024 100644 --- a/app/Generator/Report/Standard/YearReportGenerator.php +++ b/app/Generator/Report/Standard/YearReportGenerator.php @@ -30,6 +30,7 @@ use Throwable; /** * Class MonthReportGenerator. + * @codeCoverageIgnore */ class YearReportGenerator implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Support.php b/app/Generator/Report/Support.php index 0d5e42f4f2..d2b6c76f32 100644 --- a/app/Generator/Report/Support.php +++ b/app/Generator/Report/Support.php @@ -31,6 +31,7 @@ use Illuminate\Support\Collection; * Class Support. * @method Collection getExpenses() * @method Collection getIncome() + * @codeCoverageIgnore */ class Support { diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php index 0c83883786..975a1080aa 100644 --- a/app/Generator/Report/Tag/MonthReportGenerator.php +++ b/app/Generator/Report/Tag/MonthReportGenerator.php @@ -42,6 +42,7 @@ use Throwable; /** * Class MonthReportGenerator. + * @codeCoverageIgnore */ class MonthReportGenerator extends Support implements ReportGeneratorInterface { diff --git a/app/Generator/Report/Tag/MultiYearReportGenerator.php b/app/Generator/Report/Tag/MultiYearReportGenerator.php index 0d6380416b..c34136289a 100644 --- a/app/Generator/Report/Tag/MultiYearReportGenerator.php +++ b/app/Generator/Report/Tag/MultiYearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Tag; /** * Class MultiYearReportGenerator. + * @codeCoverageIgnore */ class MultiYearReportGenerator extends MonthReportGenerator { diff --git a/app/Generator/Report/Tag/YearReportGenerator.php b/app/Generator/Report/Tag/YearReportGenerator.php index 0cb3f216a0..087485a166 100644 --- a/app/Generator/Report/Tag/YearReportGenerator.php +++ b/app/Generator/Report/Tag/YearReportGenerator.php @@ -24,6 +24,7 @@ namespace FireflyIII\Generator\Report\Tag; /** * Class YearReportGenerator. + * @codeCoverageIgnore */ class YearReportGenerator extends MonthReportGenerator { diff --git a/tests/Unit/Factory/AccountFactoryTest.php b/tests/Unit/Factory/AccountFactoryTest.php index 8e8bd86452..7354f3c6ea 100644 --- a/tests/Unit/Factory/AccountFactoryTest.php +++ b/tests/Unit/Factory/AccountFactoryTest.php @@ -25,9 +25,12 @@ namespace Tests\Unit\Factory; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\AccountFactory; +use FireflyIII\Models\Account; use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; +use Log; use Tests\TestCase; /** @@ -35,6 +38,15 @@ use Tests\TestCase; */ class AccountFactoryTest extends TestCase { + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * Test minimal set of data to make factory work (asset account). * @@ -522,4 +534,53 @@ class AccountFactoryTest extends TestCase $this->assertEquals($account->id, $existing->id); } + /** + * Can't find account type. + * + * @covers \FireflyIII\Factory\AccountFactory + * @covers \FireflyIII\Factory\AccountMetaFactory + * @covers \FireflyIII\Services\Internal\Support\AccountServiceTrait + */ + public function testCreateNoType(): void + { + + $data = [ + 'account_type_id' => null, + 'accountType' => 'bla-bla', + 'iban' => null, + 'name' => 'Basic asset account #' . random_int(1, 10000), + 'virtualBalance' => null, + 'active' => true, + 'accountRole' => 'defaultAsset', + ]; + + /** @var AccountFactory $factory */ + $factory = app(AccountFactory::class); + $factory->setUser($this->user()); + try { + $factory->create($data); + } catch (FireflyException $e) { + $this->assertContains('AccountFactory::create() was unable to find account type #0 ("bla-bla").', $e->getMessage()); + } + } + + /** + * Test only for existing account because the rest has been covered by other tests. + * + * @covers \FireflyIII\Factory\AccountFactory + * @covers \FireflyIII\Factory\AccountMetaFactory + * @covers \FireflyIII\Services\Internal\Support\AccountServiceTrait + */ + public function testFindOrCreate(): void + { + /** @var Account $account */ + $account = $this->user()->accounts()->inRandomOrder()->first(); + /** @var AccountFactory $factory */ + $factory = app(AccountFactory::class); + $factory->setUser($this->user()); + + $result = $factory->findOrCreate($account->name, $account->accountType->type); + $this->assertEquals($result->id, $account->id); + } + } diff --git a/tests/Unit/Factory/AccountMetaFactoryTest.php b/tests/Unit/Factory/AccountMetaFactoryTest.php new file mode 100644 index 0000000000..0ddc5d788f --- /dev/null +++ b/tests/Unit/Factory/AccountMetaFactoryTest.php @@ -0,0 +1,121 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Factory; + + +use FireflyIII\Factory\AccountMetaFactory; +use Log; +use Tests\TestCase; + +/** + * + * Class AccountMetaFactoryTest + */ +class AccountMetaFactoryTest extends TestCase +{ + + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Factory\AccountMetaFactory + */ + public function testCreate(): void + { + $account = $this->user()->accounts()->inRandomOrder()->first(); + $data = [ + 'account_id' => $account->id, + 'name' => 'Some name', + 'data' => 'Some value', + ]; + + $factory = new AccountMetaFactory; + $result = $factory->create($data); + $this->assertEquals($data['name'], $result->name); + } + + /** + * @covers \FireflyIII\Factory\AccountMetaFactory + */ + public function testCrudDelete(): void + { + $factory = new AccountMetaFactory; + $account = $this->user()->accounts()->inRandomOrder()->first(); + $data = [ + 'account_id' => $account->id, + 'name' => 'Some name ' . random_int(1, 100000), + 'data' => 'Some value', + ]; + + $factory->create($data); + + // update existing one + $result = $factory->crud($account, $data['name'], ''); + $this->assertNull($result); + } + + /** + * @covers \FireflyIII\Factory\AccountMetaFactory + */ + public function testCrudExisting(): void + { + $factory = new AccountMetaFactory; + $account = $this->user()->accounts()->inRandomOrder()->first(); + $data = [ + 'account_id' => $account->id, + 'name' => 'Some name ' . random_int(1, 100000), + 'data' => 'Some value', + ]; + + $existing = $factory->create($data); + + // update existing one + $result = $factory->crud($account, $data['name'], 'Some NEW value'); + $this->assertNotNull($result); + $this->assertEquals($result->account_id, $account->id); + $this->assertEquals($existing->name, $result->name); + $this->assertEquals('Some NEW value', $result->data); + + } + + /** + * @covers \FireflyIII\Factory\AccountMetaFactory + */ + public function testCrudNew(): void + { + $account = $this->user()->accounts()->inRandomOrder()->first(); + $factory = new AccountMetaFactory; + $result = $factory->crud($account, 'random name ' . random_int(1, 100000), 'Some value'); + $this->assertNotNull($result); + $this->assertEquals($result->account_id, $account->id); + + } +} \ No newline at end of file diff --git a/tests/Unit/Factory/AttachmentFactoryTest.php b/tests/Unit/Factory/AttachmentFactoryTest.php new file mode 100644 index 0000000000..cb5c3abf48 --- /dev/null +++ b/tests/Unit/Factory/AttachmentFactoryTest.php @@ -0,0 +1,71 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Factory; + + +use FireflyIII\Factory\AttachmentFactory; +use FireflyIII\Models\TransactionJournal; +use Log; +use Tests\TestCase; + +/** + * + * Class AttachmentFactoryTest + */ +class AttachmentFactoryTest extends TestCase +{ + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Factory\AttachmentFactory + */ + public function testCreate(): void + { + + $journal = $this->user()->transactionJournals()->inRandomOrder()->first(); + $data = [ + 'model_id' => $journal->id, + 'model' => TransactionJournal::class, + 'filename' => 'testfile.pdf', + 'title' => 'File name', + 'notes' => 'Some notes', + ]; + + $factory = new AttachmentFactory; + $factory->setUser($this->user()); + $result = $factory->create($data); + $this->assertEquals($data['title'], $result->title); + $this->assertEquals(1, $result->notes()->count()); + + + } +} \ No newline at end of file diff --git a/tests/Unit/Factory/BillFactoryTest.php b/tests/Unit/Factory/BillFactoryTest.php index 048cff0a37..cb6289eba0 100644 --- a/tests/Unit/Factory/BillFactoryTest.php +++ b/tests/Unit/Factory/BillFactoryTest.php @@ -25,6 +25,7 @@ namespace Tests\Unit\Factory; use FireflyIII\Factory\BillFactory; +use Log; use Tests\TestCase; /** @@ -33,6 +34,16 @@ use Tests\TestCase; class BillFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * Create basic bill with minimum data. * @@ -77,17 +88,17 @@ class BillFactoryTest extends TestCase public function testCreateEmptyNotes(): void { $data = [ - 'name' => 'Some new bill #' . random_int(1, 10000), - 'amount_min' => '5', - 'amount_max' => '10', - 'date' => '2018-01-01', - 'repeat_freq' => 'monthly', - 'currency_id' => 1, + 'name' => 'Some new bill #' . random_int(1, 10000), + 'amount_min' => '5', + 'amount_max' => '10', + 'date' => '2018-01-01', + 'repeat_freq' => 'monthly', + 'currency_id' => 1, 'currency_code' => '', - 'skip' => 0, - 'automatch' => true, - 'active' => true, - 'notes' => '', + 'skip' => 0, + 'automatch' => true, + 'active' => true, + 'notes' => '', ]; /** @var BillFactory $factory */ diff --git a/tests/Unit/Factory/BudgetFactoryTest.php b/tests/Unit/Factory/BudgetFactoryTest.php index 5544a4ba70..e4fbef4f06 100644 --- a/tests/Unit/Factory/BudgetFactoryTest.php +++ b/tests/Unit/Factory/BudgetFactoryTest.php @@ -25,6 +25,7 @@ namespace Tests\Unit\Factory; use FireflyIII\Factory\BudgetFactory; +use Log; use Tests\TestCase; /** @@ -32,6 +33,16 @@ use Tests\TestCase; */ class BudgetFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * Put in ID, return it. * diff --git a/tests/Unit/Factory/CategoryFactoryTest.php b/tests/Unit/Factory/CategoryFactoryTest.php index f98e9b353d..3c878df809 100644 --- a/tests/Unit/Factory/CategoryFactoryTest.php +++ b/tests/Unit/Factory/CategoryFactoryTest.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace Tests\Unit\Factory; use FireflyIII\Factory\CategoryFactory; +use Log; use Tests\TestCase; /** @@ -31,6 +32,16 @@ use Tests\TestCase; */ class CategoryFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\CategoryFactory */ diff --git a/tests/Unit/Factory/PiggyBankEventFactoryTest.php b/tests/Unit/Factory/PiggyBankEventFactoryTest.php index c6079835bf..1d6f5a8af1 100644 --- a/tests/Unit/Factory/PiggyBankEventFactoryTest.php +++ b/tests/Unit/Factory/PiggyBankEventFactoryTest.php @@ -29,6 +29,7 @@ use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Log; use Tests\TestCase; /** @@ -36,6 +37,16 @@ use Tests\TestCase; */ class PiggyBankEventFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\PiggyBankEventFactory */ diff --git a/tests/Unit/Factory/PiggyBankFactoryTest.php b/tests/Unit/Factory/PiggyBankFactoryTest.php index ff36f2d594..e9e39382cc 100644 --- a/tests/Unit/Factory/PiggyBankFactoryTest.php +++ b/tests/Unit/Factory/PiggyBankFactoryTest.php @@ -25,6 +25,7 @@ namespace Tests\Unit\Factory; use FireflyIII\Factory\PiggyBankFactory; +use Log; use Tests\TestCase; /** @@ -32,6 +33,16 @@ use Tests\TestCase; */ class PiggyBankFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * Put in ID, return it. * diff --git a/tests/Unit/Factory/RecurrenceFactoryTest.php b/tests/Unit/Factory/RecurrenceFactoryTest.php new file mode 100644 index 0000000000..fd7a658a3e --- /dev/null +++ b/tests/Unit/Factory/RecurrenceFactoryTest.php @@ -0,0 +1,552 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Factory; + + +use Amount; +use Carbon\Carbon; +use FireflyIII\Factory\BudgetFactory; +use FireflyIII\Factory\CategoryFactory; +use FireflyIII\Factory\PiggyBankFactory; +use FireflyIII\Factory\RecurrenceFactory; +use FireflyIII\Factory\TransactionCurrencyFactory; +use FireflyIII\Factory\TransactionTypeFactory; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Log; +use Tests\TestCase; + +/** + * + * Class RecurrenceFactoryTest + */ +class RecurrenceFactoryTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * With piggy bank. With tags. With budget. With category. + * + * @covers \FireflyIII\Factory\RecurrenceFactory + * @covers \FireflyIII\Services\Internal\Support\RecurringTransactionTrait + */ + public function testBasic(): void + { + // objects to return: + $piggyBank = $this->user()->piggyBanks()->inRandomOrder()->first(); + $accountA = $this->user()->accounts()->inRandomOrder()->first(); + $accountB = $this->user()->accounts()->inRandomOrder()->first(); + $budget = $this->user()->budgets()->inRandomOrder()->first(); + $category= $this->user()->categories()->inRandomOrder()->first(); + + // mock other factories: + $piggyFactory = $this->mock(PiggyBankFactory::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + // mock calls: + Amount::shouldReceive('getDefaultCurrencyByUser')->andReturn(TransactionCurrency::find(1))->once(); + $piggyFactory->shouldReceive('setUser')->once(); + $piggyFactory->shouldReceive('find')->withArgs([1, 'Bla bla'])->andReturn($piggyBank); + + $accountRepos->shouldReceive('setUser')->twice(); + $accountRepos->shouldReceive('findNull')->twice()->andReturn($accountA, $accountB); + + $currencyFactory->shouldReceive('find')->once()->withArgs([1, 'EUR'])->andReturn(null); + $currencyFactory->shouldReceive('find')->once()->withArgs([null, null])->andReturn(null); + + $budgetFactory->shouldReceive('setUser')->once(); + $budgetFactory->shouldReceive('find')->withArgs([1, 'Some budget'])->once()->andReturn($budget); + + $categoryFactory->shouldReceive('setUser')->once(); + $categoryFactory->shouldReceive('findOrCreate')->withArgs([2, 'Some category'])->once()->andReturn($category); + + // data for basic recurrence. + $data = [ + 'recurrence' => [ + 'type' => 'withdrawal', + 'first_date' => Carbon::create()->addDay(), + 'repetitions' => 0, + 'title' => 'Test recurrence' . random_int(1, 100000), + 'description' => 'Description thing', + 'apply_rules' => true, + 'active' => true, + 'repeat_until' => null, + ], + 'meta' => [ + 'tags' => ['a', 'b', 'c'], + 'piggy_bank_id' => 1, + 'piggy_bank_name' => 'Bla bla', + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => 0, + 'weekend' => 1, + ], + ], + 'transactions' => [ + [ + 'source_id' => 1, + 'source_name' => 'Some name', + 'destination_id' => 2, + 'destination_name' => 'some otjer name', + 'currency_id' => 1, + 'currency_code' => 'EUR', + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'description' => 'Bla bla bla', + 'amount' => '100', + 'budget_id' => 1, + 'budget_name' => 'Some budget', + 'category_id' => 2, + 'category_name' => 'Some category', + + ], + ], + ]; + $typeFactory = $this->mock(TransactionTypeFactory::class); + $typeFactory->shouldReceive('find')->once()->withArgs([ucfirst($data['recurrence']['type'])])->andReturn(TransactionType::find(1)); + $factory = new RecurrenceFactory; + $factory->setUser($this->user()); + + $result = $factory->create($data); + $this->assertEquals($result->title, $data['recurrence']['title']); + } + + /** + * Deposit. With piggy bank. With tags. With budget. With category. + * + * @covers \FireflyIII\Factory\RecurrenceFactory + * @covers \FireflyIII\Services\Internal\Support\RecurringTransactionTrait + */ + public function testBasicDeposit(): void + { + // objects to return: + $piggyBank = $this->user()->piggyBanks()->inRandomOrder()->first(); + $accountA = $this->user()->accounts()->inRandomOrder()->first(); + $accountB = $this->user()->accounts()->inRandomOrder()->first(); + $budget = $this->user()->budgets()->inRandomOrder()->first(); + $category= $this->user()->categories()->inRandomOrder()->first(); + + // mock other factories: + $piggyFactory = $this->mock(PiggyBankFactory::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + // mock calls: + Amount::shouldReceive('getDefaultCurrencyByUser')->andReturn(TransactionCurrency::find(1))->once(); + $piggyFactory->shouldReceive('setUser')->once(); + $piggyFactory->shouldReceive('find')->withArgs([1, 'Bla bla'])->andReturn($piggyBank); + + $accountRepos->shouldReceive('setUser')->twice(); + $accountRepos->shouldReceive('findNull')->twice()->andReturn($accountA, $accountB); + + $currencyFactory->shouldReceive('find')->once()->withArgs([1, 'EUR'])->andReturn(null); + $currencyFactory->shouldReceive('find')->once()->withArgs([null, null])->andReturn(null); + + $budgetFactory->shouldReceive('setUser')->once(); + $budgetFactory->shouldReceive('find')->withArgs([1, 'Some budget'])->once()->andReturn($budget); + + $categoryFactory->shouldReceive('setUser')->once(); + $categoryFactory->shouldReceive('findOrCreate')->withArgs([2, 'Some category'])->once()->andReturn($category); + + // data for basic recurrence. + $data = [ + 'recurrence' => [ + 'type' => 'deposit', + 'first_date' => Carbon::create()->addDay(), + 'repetitions' => 0, + 'title' => 'Test recurrence' . random_int(1, 100000), + 'description' => 'Description thing', + 'apply_rules' => true, + 'active' => true, + 'repeat_until' => null, + ], + 'meta' => [ + 'tags' => ['a', 'b', 'c'], + 'piggy_bank_id' => 1, + 'piggy_bank_name' => 'Bla bla', + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => 0, + 'weekend' => 1, + ], + ], + 'transactions' => [ + [ + 'source_id' => 1, + 'source_name' => 'Some name', + 'destination_id' => 2, + 'destination_name' => 'some otjer name', + 'currency_id' => 1, + 'currency_code' => 'EUR', + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'description' => 'Bla bla bla', + 'amount' => '100', + 'budget_id' => 1, + 'budget_name' => 'Some budget', + 'category_id' => 2, + 'category_name' => 'Some category', + + ], + ], + ]; + + $typeFactory = $this->mock(TransactionTypeFactory::class); + $typeFactory->shouldReceive('find')->once()->withArgs([ucfirst($data['recurrence']['type'])])->andReturn(TransactionType::find(2)); + + $factory = new RecurrenceFactory; + $factory->setUser($this->user()); + + $result = $factory->create($data); + $this->assertEquals($result->title, $data['recurrence']['title']); + } + + /** + * Deposit. With piggy bank. With tags. With budget. With category. + * + * @covers \FireflyIII\Factory\RecurrenceFactory + * @covers \FireflyIII\Services\Internal\Support\RecurringTransactionTrait + */ + public function testBasicTransfer(): void + { + // objects to return: + $piggyBank = $this->user()->piggyBanks()->inRandomOrder()->first(); + $accountA = $this->user()->accounts()->inRandomOrder()->first(); + $accountB = $this->user()->accounts()->inRandomOrder()->first(); + $budget = $this->user()->budgets()->inRandomOrder()->first(); + $category= $this->user()->categories()->inRandomOrder()->first(); + + // mock other factories: + $piggyFactory = $this->mock(PiggyBankFactory::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + // mock calls: + Amount::shouldReceive('getDefaultCurrencyByUser')->andReturn(TransactionCurrency::find(1))->once(); + $piggyFactory->shouldReceive('setUser')->once(); + $piggyFactory->shouldReceive('find')->withArgs([1, 'Bla bla'])->andReturn($piggyBank); + + $accountRepos->shouldReceive('setUser')->twice(); + $accountRepos->shouldReceive('findNull')->twice()->andReturn($accountA, $accountB); + + $currencyFactory->shouldReceive('find')->once()->withArgs([1, 'EUR'])->andReturn(null); + $currencyFactory->shouldReceive('find')->once()->withArgs([null, null])->andReturn(null); + + $budgetFactory->shouldReceive('setUser')->once(); + $budgetFactory->shouldReceive('find')->withArgs([1, 'Some budget'])->once()->andReturn($budget); + + $categoryFactory->shouldReceive('setUser')->once(); + $categoryFactory->shouldReceive('findOrCreate')->withArgs([2, 'Some category'])->once()->andReturn($category); + + // data for basic recurrence. + $data = [ + 'recurrence' => [ + 'type' => 'transfer', + 'first_date' => Carbon::create()->addDay(), + 'repetitions' => 0, + 'title' => 'Test recurrence' . random_int(1, 100000), + 'description' => 'Description thing', + 'apply_rules' => true, + 'active' => true, + 'repeat_until' => null, + ], + 'meta' => [ + 'tags' => ['a', 'b', 'c'], + 'piggy_bank_id' => 1, + 'piggy_bank_name' => 'Bla bla', + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => 0, + 'weekend' => 1, + ], + ], + 'transactions' => [ + [ + 'source_id' => 1, + 'source_name' => 'Some name', + 'destination_id' => 2, + 'destination_name' => 'some otjer name', + 'currency_id' => 1, + 'currency_code' => 'EUR', + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'description' => 'Bla bla bla', + 'amount' => '100', + 'budget_id' => 1, + 'budget_name' => 'Some budget', + 'category_id' => 2, + 'category_name' => 'Some category', + + ], + ], + ]; + + $typeFactory = $this->mock(TransactionTypeFactory::class); + $typeFactory->shouldReceive('find')->once()->withArgs([ucfirst($data['recurrence']['type'])])->andReturn(TransactionType::find(3)); + + $factory = new RecurrenceFactory; + $factory->setUser($this->user()); + + $result = $factory->create($data); + $this->assertEquals($result->title, $data['recurrence']['title']); + } + + /** + * With piggy bank. With tags. With budget. With category. + * + * @covers \FireflyIII\Factory\RecurrenceFactory + * @covers \FireflyIII\Services\Internal\Support\RecurringTransactionTrait + */ + public function testBasicNoTags(): void + { + // objects to return: + $piggyBank = $this->user()->piggyBanks()->inRandomOrder()->first(); + $accountA = $this->user()->accounts()->inRandomOrder()->first(); + $accountB = $this->user()->accounts()->inRandomOrder()->first(); + $budget = $this->user()->budgets()->inRandomOrder()->first(); + $category= $this->user()->categories()->inRandomOrder()->first(); + + // mock other factories: + $piggyFactory = $this->mock(PiggyBankFactory::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + // mock calls: + Amount::shouldReceive('getDefaultCurrencyByUser')->andReturn(TransactionCurrency::find(1))->once(); + $piggyFactory->shouldReceive('setUser')->once(); + $piggyFactory->shouldReceive('find')->withArgs([1, 'Bla bla'])->andReturn($piggyBank); + + $accountRepos->shouldReceive('setUser')->twice(); + $accountRepos->shouldReceive('findNull')->twice()->andReturn($accountA, $accountB); + + $currencyFactory->shouldReceive('find')->once()->withArgs([1, 'EUR'])->andReturn(null); + $currencyFactory->shouldReceive('find')->once()->withArgs([null, null])->andReturn(null); + + $budgetFactory->shouldReceive('setUser')->once(); + $budgetFactory->shouldReceive('find')->withArgs([1, 'Some budget'])->once()->andReturn($budget); + + $categoryFactory->shouldReceive('setUser')->once(); + $categoryFactory->shouldReceive('findOrCreate')->withArgs([2, 'Some category'])->once()->andReturn($category); + + // data for basic recurrence. + $data = [ + 'recurrence' => [ + 'type' => 'withdrawal', + 'first_date' => Carbon::create()->addDay(), + 'repetitions' => 0, + 'title' => 'Test recurrence' . random_int(1, 100000), + 'description' => 'Description thing', + 'apply_rules' => true, + 'active' => true, + 'repeat_until' => null, + ], + 'meta' => [ + 'tags' => [], + 'piggy_bank_id' => 1, + 'piggy_bank_name' => 'Bla bla', + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => 0, + 'weekend' => 1, + ], + ], + 'transactions' => [ + [ + 'source_id' => 1, + 'source_name' => 'Some name', + 'destination_id' => 2, + 'destination_name' => 'some otjer name', + 'currency_id' => 1, + 'currency_code' => 'EUR', + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'description' => 'Bla bla bla', + 'amount' => '100', + 'budget_id' => 1, + 'budget_name' => 'Some budget', + 'category_id' => 2, + 'category_name' => 'Some category', + + ], + ], + ]; + $typeFactory = $this->mock(TransactionTypeFactory::class); + $typeFactory->shouldReceive('find')->once()->withArgs([ucfirst($data['recurrence']['type'])])->andReturn(TransactionType::find(1)); + $factory = new RecurrenceFactory; + $factory->setUser($this->user()); + + $result = $factory->create($data); + $this->assertEquals($result->title, $data['recurrence']['title']); + } + + /** + * No piggy bank. With tags. With budget. With category. + * + * @covers \FireflyIII\Factory\RecurrenceFactory + * @covers \FireflyIII\Services\Internal\Support\RecurringTransactionTrait + */ + public function testBasicNoPiggybank(): void + { + // objects to return: + $piggyBank = $this->user()->piggyBanks()->inRandomOrder()->first(); + $accountA = $this->user()->accounts()->inRandomOrder()->first(); + $accountB = $this->user()->accounts()->inRandomOrder()->first(); + $budget = $this->user()->budgets()->inRandomOrder()->first(); + $category= $this->user()->categories()->inRandomOrder()->first(); + + // mock other factories: + $piggyFactory = $this->mock(PiggyBankFactory::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + // mock calls: + Amount::shouldReceive('getDefaultCurrencyByUser')->andReturn(TransactionCurrency::find(1))->once(); + $piggyFactory->shouldReceive('setUser')->once(); + $piggyFactory->shouldReceive('find')->withArgs([1, 'Bla bla'])->andReturn(null); + + $accountRepos->shouldReceive('setUser')->twice(); + $accountRepos->shouldReceive('findNull')->twice()->andReturn($accountA, $accountB); + + $currencyFactory->shouldReceive('find')->once()->withArgs([1, 'EUR'])->andReturn(null); + $currencyFactory->shouldReceive('find')->once()->withArgs([null, null])->andReturn(null); + + $budgetFactory->shouldReceive('setUser')->once(); + $budgetFactory->shouldReceive('find')->withArgs([1, 'Some budget'])->once()->andReturn($budget); + + $categoryFactory->shouldReceive('setUser')->once(); + $categoryFactory->shouldReceive('findOrCreate')->withArgs([2, 'Some category'])->once()->andReturn($category); + + // data for basic recurrence. + $data = [ + 'recurrence' => [ + 'type' => 'withdrawal', + 'first_date' => Carbon::create()->addDay(), + 'repetitions' => 0, + 'title' => 'Test recurrence' . random_int(1, 100000), + 'description' => 'Description thing', + 'apply_rules' => true, + 'active' => true, + 'repeat_until' => null, + ], + 'meta' => [ + 'tags' => ['a', 'b', 'c'], + 'piggy_bank_id' => 1, + 'piggy_bank_name' => 'Bla bla', + ], + 'repetitions' => [ + [ + 'type' => 'daily', + 'moment' => '', + 'skip' => 0, + 'weekend' => 1, + ], + ], + 'transactions' => [ + [ + 'source_id' => 1, + 'source_name' => 'Some name', + 'destination_id' => 2, + 'destination_name' => 'some otjer name', + 'currency_id' => 1, + 'currency_code' => 'EUR', + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'description' => 'Bla bla bla', + 'amount' => '100', + 'budget_id' => 1, + 'budget_name' => 'Some budget', + 'category_id' => 2, + 'category_name' => 'Some category', + + ], + ], + ]; + $typeFactory = $this->mock(TransactionTypeFactory::class); + $typeFactory->shouldReceive('find')->once()->withArgs([ucfirst($data['recurrence']['type'])])->andReturn(TransactionType::find(1)); + $factory = new RecurrenceFactory; + $factory->setUser($this->user()); + + $result = $factory->create($data); + $this->assertEquals($result->title, $data['recurrence']['title']); + } + + /** + * @covers \FireflyIII\Factory\RecurrenceFactory + */ + public function testCreateBadTransactionType(): void + { + $data = [ + 'recurrence' => [ + 'type' => 'bad type', + ], + ]; + + $typeFactory = $this->mock(TransactionTypeFactory::class); + $typeFactory->shouldReceive('find')->once()->withArgs([ucfirst($data['recurrence']['type'])])->andReturn(null); + + + $factory = new RecurrenceFactory; + $factory->setUser($this->user()); + + $result = $factory->create($data); + $this->assertNull($result); + } + +} \ No newline at end of file diff --git a/tests/Unit/Factory/TagFactoryTest.php b/tests/Unit/Factory/TagFactoryTest.php index 284ac8c6c6..cc4b984de1 100644 --- a/tests/Unit/Factory/TagFactoryTest.php +++ b/tests/Unit/Factory/TagFactoryTest.php @@ -25,6 +25,7 @@ namespace Tests\Unit\Factory; use FireflyIII\Factory\TagFactory; +use Log; use Tests\TestCase; /** @@ -32,6 +33,16 @@ use Tests\TestCase; */ class TagFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\TagFactory */ diff --git a/tests/Unit/Factory/TransactionCurrencyFactoryTest.php b/tests/Unit/Factory/TransactionCurrencyFactoryTest.php index 3172100117..a0200409f5 100644 --- a/tests/Unit/Factory/TransactionCurrencyFactoryTest.php +++ b/tests/Unit/Factory/TransactionCurrencyFactoryTest.php @@ -26,6 +26,7 @@ namespace Tests\Unit\Factory; use FireflyIII\Factory\TransactionCurrencyFactory; use FireflyIII\Models\TransactionCurrency; +use Log; use Tests\TestCase; /** @@ -33,6 +34,16 @@ use Tests\TestCase; */ class TransactionCurrencyFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\TransactionCurrencyFactory */ diff --git a/tests/Unit/Factory/TransactionFactoryTest.php b/tests/Unit/Factory/TransactionFactoryTest.php index 76253ceaff..4590025604 100644 --- a/tests/Unit/Factory/TransactionFactoryTest.php +++ b/tests/Unit/Factory/TransactionFactoryTest.php @@ -35,6 +35,7 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Log; use Tests\TestCase; /** @@ -42,6 +43,16 @@ use Tests\TestCase; */ class TransactionFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\TransactionFactory * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait @@ -617,6 +628,70 @@ class TransactionFactoryTest extends TestCase $this->assertNull($first->foreign_currency_id); } + /** + * @covers \FireflyIII\Factory\TransactionFactory + * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait + */ + public function testCreatePairEmptyAmount(): void + { + // objects: + $asset = $this->user()->accounts()->where('account_type_id', 3)->first(); + $expense = $this->user()->accounts()->where('account_type_id', 4)->first(); + $euro = TransactionCurrency::first(); + + // mocked classes + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + $data = [ + 'currency_id' => 1, + 'currency_code' => null, + 'description' => null, + 'source_id' => $asset->id, + 'source_name' => null, + 'destination_id' => $expense->id, + 'destination_name' => null, + 'amount' => '', + 'reconciled' => false, + 'identifier' => 0, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + ]; + + // mock: + $accountRepos->shouldReceive('setUser'); + $budgetFactory->shouldReceive('setUser'); + $categoryFactory->shouldReceive('setUser'); + // first search action is for the asset account, second is for expense account. + $accountRepos->shouldReceive('findNull')->andReturn($asset, $expense)->atLeast()->once(); + + // factories return various stuff: + $currencyFactory->shouldReceive('find')->andReturn($euro, null)->atLeast()->once(); + + /** @var TransactionJournal $withdrawal */ + $withdrawal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $count = $withdrawal->transactions()->count(); + + /** @var TransactionFactory $factory */ + $factory = app(TransactionFactory::class); + $factory->setUser($this->user()); + try { + $factory->createPair($withdrawal, $data); + } catch (FireflyException $e) { + $this->assertEquals('Amount is an empty string, which Firefly III cannot handle. Apologies.', $e->getMessage()); + } + + $newCount = $withdrawal->transactions()->count(); + $this->assertEquals($count, $newCount); + } + /** * @covers \FireflyIII\Factory\TransactionFactory * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait @@ -695,6 +770,135 @@ class TransactionFactoryTest extends TestCase $this->assertEquals($foreign->id, $first->foreign_currency_id); } + /** + * @covers \FireflyIII\Factory\TransactionFactory + * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait + */ + public function testCreatePairNoAccounts(): void + { + // objects: + $asset = $this->user()->accounts()->where('account_type_id', 3)->first(); + $expense = $this->user()->accounts()->where('account_type_id', 4)->first(); + $euro = TransactionCurrency::first(); + + // mocked classes + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + $data = [ + 'currency_id' => 1, + 'currency_code' => null, + 'description' => null, + 'source_id' => $asset->id, + 'source_name' => null, + 'destination_id' => $expense->id, + 'destination_name' => null, + 'amount' => '10', + 'reconciled' => false, + 'identifier' => 0, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + ]; + + // mock: + $accountRepos->shouldReceive('setUser'); + $budgetFactory->shouldReceive('setUser'); + $categoryFactory->shouldReceive('setUser'); + // first search action is for the asset account, second is for expense account. + $accountRepos->shouldReceive('findNull')->andReturn(null, null)->atLeast()->once(); + + // factories return various stuff: + $currencyFactory->shouldReceive('find')->andReturn($euro, null)->atLeast()->once(); + + /** @var TransactionJournal $withdrawal */ + $withdrawal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $count = $withdrawal->transactions()->count(); + + /** @var TransactionFactory $factory */ + $factory = app(TransactionFactory::class); + $factory->setUser($this->user()); + try { + $factory->createPair($withdrawal, $data); + } catch (FireflyException $e) { + $this->assertEquals('Could not determine source or destination account.', $e->getMessage()); + } + + $newCount = $withdrawal->transactions()->count(); + + $this->assertEquals($count, $newCount); + } + + /** + * @covers \FireflyIII\Factory\TransactionFactory + * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait + */ + public function testCreatePairNoCurrency(): void + { + // objects: + $asset = $this->user()->accounts()->where('account_type_id', 3)->first(); + $expense = $this->user()->accounts()->where('account_type_id', 4)->first(); + + // mocked classes + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + $data = [ + 'description' => null, + 'source_id' => $asset->id, + 'source_name' => null, + 'destination_id' => $expense->id, + 'currency_id' => null, + 'currency_code' => null, + 'destination_name' => null, + 'amount' => '10', + 'reconciled' => false, + 'identifier' => 0, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + ]; + + // mock: + $accountRepos->shouldReceive('setUser'); + $budgetFactory->shouldReceive('setUser'); + $categoryFactory->shouldReceive('setUser'); + // first search action is for the asset account, second is for expense account. + $accountRepos->shouldReceive('findNull')->andReturn($asset, $expense)->atLeast()->once(); + + // factories return various stuff: + $currencyFactory->shouldReceive('find')->andReturn(null, null)->atLeast()->once(); + + /** @var TransactionJournal $withdrawal */ + $withdrawal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $count = $withdrawal->transactions()->count(); + + /** @var TransactionFactory $factory */ + $factory = app(TransactionFactory::class); + $factory->setUser($this->user()); + try { + $factory->createPair($withdrawal, $data); + } catch (FireflyException $e) { + $this->assertEquals('Cannot store transaction without currency information.', $e->getMessage()); + } + + $newCount = $withdrawal->transactions()->count(); + + $this->assertEquals($count, $newCount); + } + /** * Create reconciliation using minimal data. * @@ -706,8 +910,8 @@ class TransactionFactoryTest extends TestCase // objects: $asset = $this->user()->accounts()->where('account_type_id', 3)->first(); $reconAccount = $this->user()->accounts()->where('account_type_id', 10)->first(); - $euro = TransactionCurrency::first(); - $foreign = TransactionCurrency::where('id', '!=', $euro->id)->first(); + $euro = TransactionCurrency::first(); + $foreign = TransactionCurrency::where('id', '!=', $euro->id)->first(); // mocked classes $accountRepos = $this->mock(AccountRepositoryInterface::class); @@ -776,6 +980,134 @@ class TransactionFactoryTest extends TestCase $this->assertNull($first->foreign_currency_id); } + /** + * @covers \FireflyIII\Factory\TransactionFactory + * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait + */ + public function testCreatePairSameBadType(): void + { + // objects: + $expense = $this->user()->accounts()->where('account_type_id', 4)->first(); + $revenue = $this->user()->accounts()->where('account_type_id', 5)->first(); + $euro = TransactionCurrency::first(); + + // mocked classes + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + $data = [ + 'currency_id' => 1, + 'currency_code' => null, + 'description' => null, + 'source_id' => $expense->id, + 'source_name' => null, + 'destination_id' => $revenue->id, + 'destination_name' => null, + 'amount' => '10', + 'reconciled' => false, + 'identifier' => 0, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + ]; + + // mock: + $accountRepos->shouldReceive('setUser'); + $budgetFactory->shouldReceive('setUser'); + $categoryFactory->shouldReceive('setUser'); + // first search action is for the asset account, second is for expense account. + $accountRepos->shouldReceive('findNull')->andReturn($expense, $revenue); + + // factories return various stuff: + $currencyFactory->shouldReceive('find')->andReturn($euro, null)->atLeast()->once(); + + /** @var TransactionJournal $withdrawal */ + $withdrawal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $count = $withdrawal->transactions()->count(); + + /** @var TransactionFactory $factory */ + $factory = app(TransactionFactory::class); + $factory->setUser($this->user()); + try { + $factory->createPair($withdrawal, $data); + } catch (FireflyException $e) { + $this->assertEquals('At least one of the accounts must be an asset account.', $e->getMessage()); + } + + $newCount = $withdrawal->transactions()->count(); + $this->assertEquals($count, $newCount); + } + + /** + * @covers \FireflyIII\Factory\TransactionFactory + * @covers \FireflyIII\Services\Internal\Support\TransactionServiceTrait + */ + public function testCreatePairSameType(): void + { + // objects: + $asset = $this->user()->accounts()->where('account_type_id', 3)->first(); + $alsoAsset = $this->user()->accounts()->where('account_type_id', 3)->first(); + $euro = TransactionCurrency::first(); + + // mocked classes + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $budgetFactory = $this->mock(BudgetFactory::class); + $categoryFactory = $this->mock(CategoryFactory::class); + $currencyFactory = $this->mock(TransactionCurrencyFactory::class); + + $data = [ + 'currency_id' => 1, + 'currency_code' => null, + 'description' => null, + 'source_id' => $asset->id, + 'source_name' => null, + 'destination_id' => $alsoAsset->id, + 'destination_name' => null, + 'amount' => '10', + 'reconciled' => false, + 'identifier' => 0, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_amount' => null, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + ]; + + // mock: + $accountRepos->shouldReceive('setUser'); + $budgetFactory->shouldReceive('setUser'); + $categoryFactory->shouldReceive('setUser'); + // first search action is for the asset account, second is for expense account. + $accountRepos->shouldReceive('findNull')->andReturn($asset, $alsoAsset); + + // factories return various stuff: + $currencyFactory->shouldReceive('find')->andReturn($euro, null)->atLeast()->once(); + + /** @var TransactionJournal $withdrawal */ + $withdrawal = $this->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $count = $withdrawal->transactions()->count(); + + /** @var TransactionFactory $factory */ + $factory = app(TransactionFactory::class); + $factory->setUser($this->user()); + try { + $factory->createPair($withdrawal, $data); + } catch (FireflyException $e) { + $this->assertEquals('Source and destination account cannot be both of the type "Asset account"', $e->getMessage()); + } + + $newCount = $withdrawal->transactions()->count(); + $this->assertEquals($count, $newCount); + } + /** * Create reconciliation using minimal (bad) data. * diff --git a/tests/Unit/Factory/TransactionJournalFactoryTest.php b/tests/Unit/Factory/TransactionJournalFactoryTest.php index 434d52ebde..bf3109fa0b 100644 --- a/tests/Unit/Factory/TransactionJournalFactoryTest.php +++ b/tests/Unit/Factory/TransactionJournalFactoryTest.php @@ -37,6 +37,7 @@ use FireflyIII\Factory\TransactionTypeFactory; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use Log; use Tests\TestCase; /** @@ -45,6 +46,15 @@ use Tests\TestCase; class TransactionJournalFactoryTest extends TestCase { + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\TransactionJournalFactory * @covers \FireflyIII\Services\Internal\Support\JournalServiceTrait @@ -106,6 +116,71 @@ class TransactionJournalFactoryTest extends TestCase } + /** + * @covers \FireflyIII\Factory\TransactionJournalFactory + * @covers \FireflyIII\Services\Internal\Support\JournalServiceTrait + */ + public function testCreateBasicEmptyAmount(): void + { + // mock used classes: + $type = TransactionType::find(1); + $euro = TransactionCurrency::find(1); + $billFactory = $this->mock(BillFactory::class); + $tagFactory = $this->mock(TagFactory::class); + $metaFactory = $this->mock(TransactionJournalMetaFactory::class); + $typeFactory = $this->mock(TransactionTypeFactory::class); + $transactionFactory = $this->mock(TransactionFactory::class); + $piggyFactory = $this->mock(PiggyBankFactory::class); + $eventFactory = $this->mock(PiggyBankEventFactory::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + + // mock stuff: + $typeFactory->shouldReceive('find')->andReturn($type); + $currencyRepos->shouldReceive('find')->andReturn($euro); + + $metaFactory->shouldReceive('updateOrCreate'); + + // mock factories: + $transactionFactory->shouldReceive('setUser')->once(); + $billFactory->shouldReceive('setUser')->once(); + $piggyFactory->shouldReceive('setUser')->once(); + $tagFactory->shouldReceive('setUser')->once(); + + $transactionFactory->shouldReceive('createPair')->once(); + $billFactory->shouldReceive('find')->andReturn(null); + $piggyFactory->shouldReceive('find')->andReturn(null); + $data = [ + 'type' => 'withdrawal', + 'user' => $this->user()->id, + 'description' => 'I are journal', + 'date' => new Carbon('2018-01-01'), + 'bill_id' => null, + 'bill_name' => null, + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'notes' => 'Hello', + 'tags' => [], + 'transactions' => [ + [ + 'amount' => '', + ] + ], + ]; + + /** @var TransactionJournalFactory $factory */ + $factory = app(TransactionJournalFactory::class); + $factory->setUser($this->user()); + try { + $journal = $factory->create($data); + } catch (FireflyException $e) { + $this->assertTrue(false, $e->getMessage()); + } + $this->assertEquals($data['description'], $journal->description); + $this->assertEquals('2018-01-01', $journal->date->format('Y-m-d')); + $this->assertEquals(1, $journal->notes()->count()); + + } + /** * Same but with added meta data diff --git a/tests/Unit/Factory/TransactionJournalMetaFactoryTest.php b/tests/Unit/Factory/TransactionJournalMetaFactoryTest.php index 748308c763..8b6b310d7f 100644 --- a/tests/Unit/Factory/TransactionJournalMetaFactoryTest.php +++ b/tests/Unit/Factory/TransactionJournalMetaFactoryTest.php @@ -27,6 +27,7 @@ use Carbon\Carbon; use FireflyIII\Factory\TransactionJournalMetaFactory; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalMeta; +use Log; use Tests\TestCase; /** @@ -34,6 +35,16 @@ use Tests\TestCase; */ class TransactionJournalMetaFactoryTest extends TestCase { + + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Factory\TransactionJournalMetaFactory */ diff --git a/tests/Unit/Factory/TransactionTypeFactoryTest.php b/tests/Unit/Factory/TransactionTypeFactoryTest.php new file mode 100644 index 0000000000..fbc3d7a457 --- /dev/null +++ b/tests/Unit/Factory/TransactionTypeFactoryTest.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Factory; + + +use FireflyIII\Factory\TransactionTypeFactory; +use FireflyIII\Models\TransactionType; +use Log; +use Tests\TestCase; + +/** + * + * Class TransactionTypeFactoryTest + */ +class TransactionTypeFactoryTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Factory\TransactionTypeFactory + */ + public function testFind(): void + { + /** @var TransactionType $type */ + $type = TransactionType::first(); + /** @var TransactionTypeFactory $factory */ + $factory = app(TransactionTypeFactory::class); + + $result = $factory->find($type->type); + + $this->assertEquals($result->id, $type->id); + } + +} \ No newline at end of file diff --git a/tests/Unit/Generator/Chart/Basic/ChartJsGeneratorTest.php b/tests/Unit/Generator/Chart/Basic/ChartJsGeneratorTest.php new file mode 100644 index 0000000000..77af41988e --- /dev/null +++ b/tests/Unit/Generator/Chart/Basic/ChartJsGeneratorTest.php @@ -0,0 +1,156 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Generator\Chart\Basic; + +use FireflyIII\Generator\Chart\Basic\ChartJsGenerator; +use Log; +use Tests\TestCase; + +/** + * + * Class ChartJsGeneratorTest + */ +class ChartJsGeneratorTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Generator\Chart\Basic\ChartJsGenerator + */ + public function testBasic(): void + { + + $data = [ + [ + 'label' => 'Today', + 'fill' => '#abcdef', + 'yAxisID' => 'a', + 'entries' => [ + 'one' => 1, + 'two' => 2, + 'three' => 3, + 'four' => 4, + 'five' => 5, + ], + ], + [ + + 'currency_symbol' => 'X', + 'backgroundColor' => '#123456', + 'label' => 'Tomorrow', + 'entries' => [ + 'one' => 6, + 'two' => 7, + 'three' => 8, + 'four' => 9, + 'five' => 10, + ], + ], + ]; + + + /** @var ChartJsGenerator $generator */ + $generator = new ChartJsGenerator(); + + $result = $generator->multiSet($data); + $this->assertEquals('one', $result['labels'][0]); + $this->assertEquals(2, $result['count']); + $this->assertCount(2, $result['datasets']); + + $this->assertEquals('a', $result['datasets'][0]['yAxisID']); + $this->assertEquals('#abcdef', $result['datasets'][0]['fill']); + + $this->assertEquals('X', $result['datasets'][1]['currency_symbol']); + $this->assertEquals('#123456', $result['datasets'][1]['backgroundColor']); + } + + /** + * @covers \FireflyIII\Generator\Chart\Basic\ChartJsGenerator + */ + public function testPieChart(): void + { + + $data = [ + 'one' => -1, + 'two' => -2, + 'three' => -3, + ]; + + /** @var ChartJsGenerator $generator */ + $generator = new ChartJsGenerator(); + $result = $generator->pieChart($data); + + $this->assertEquals('three', $result['labels'][0]); + $this->assertEquals(3.0, $result['datasets'][0]['data'][0]); + + } + + /** + * @covers \FireflyIII\Generator\Chart\Basic\ChartJsGenerator + */ + public function testPieChartReversed(): void + { + + $data = [ + 'one' => 1, + 'two' => 2, + 'three' => 3, + ]; + + /** @var ChartJsGenerator $generator */ + $generator = new ChartJsGenerator(); + $result = $generator->pieChart($data); + + $this->assertEquals('three', $result['labels'][0]); + $this->assertEquals(3.0, $result['datasets'][0]['data'][0]); + + } + + /** + * @covers \FireflyIII\Generator\Chart\Basic\ChartJsGenerator + */ + public function testSingleSet(): void + { + $data = [ + 'one' => '1', + 'two' => '2', + 'three' => '3', + ]; + + /** @var ChartJsGenerator $generator */ + $generator = new ChartJsGenerator(); + $result = $generator->singleSet('Some label', $data); + + $this->assertEquals('one', $result['labels'][0]); + $this->assertEquals(1.0, $result['datasets'][0]['data'][0]); + } + +} \ No newline at end of file diff --git a/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php b/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php index 482589d2e0..09c856a573 100644 --- a/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php +++ b/tests/Unit/Support/Import/Routine/File/ImportableConverterTest.php @@ -146,9 +146,10 @@ class ImportableConverterTest extends TestCase $assetMapper->shouldReceive('map')->once()->withArgs([null, $nullAccount])->andReturn($asset); $opposingMapper->shouldReceive('map')->once()->withArgs([null, '45.67', $nullAccount])->andReturn($other); + $currencyMapper->shouldReceive('map')->once()->withArgs([null, ['name' => null, 'code' => null, 'symbol' => null]])->andReturn(null); $currencyMapper->shouldReceive('map')->once()->withArgs([null, ['code' => null]])->andReturn(null); - $currencyMapper->shouldReceive('map')->times(2)->withArgs([$euro->id, []])->andReturn($euro); + $currencyMapper->shouldReceive('map')->times(1)->withArgs([$euro->id, []])->andReturn($euro); $converter = new ImportableConverter; From 850a0ae17e9e40fdf1357fdad532dbced2e2e388 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 24 Aug 2018 16:07:33 +0200 Subject: [PATCH 123/166] Improved code coverage for events and reports. --- .../Report/Audit/MonthReportGenerator.php | 119 ++++----- app/Handlers/Events/APIEventHandler.php | 2 +- app/Handlers/Events/AdminEventHandler.php | 3 +- app/Handlers/Events/AutomationHandler.php | 4 +- .../Events/StoredJournalEventHandler.php | 32 +-- app/Handlers/Events/UserEventHandler.php | 9 - .../Events/VersionCheckEventHandler.php | 4 +- .../Report/Audit/MonthReportGeneratorTest.php | 230 ++++++++++++++++++ .../Handlers/Events/APIEventHandlerTest.php | 76 ++++++ .../Handlers/Events/AdminEventHandlerTest.php | 13 +- .../Handlers/Events/AutomationHandlerTest.php | 80 ++++++ .../Events/StoredJournalEventHandlerTest.php | 74 ++++++ .../Handlers/Events/UserEventHandlerTest.php | 10 + .../Events/VersionCheckEventHandlerTest.php | 11 + 14 files changed, 566 insertions(+), 101 deletions(-) create mode 100644 tests/Unit/Generator/Report/Audit/MonthReportGeneratorTest.php create mode 100644 tests/Unit/Handlers/Events/APIEventHandlerTest.php create mode 100644 tests/Unit/Handlers/Events/AutomationHandlerTest.php create mode 100644 tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index 26e1ac3f43..cadc73b157 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -54,6 +54,7 @@ class MonthReportGenerator implements ReportGeneratorInterface * * @return string * @throws FireflyException + * @codeCoverageIgnore */ public function generate(): string { @@ -91,6 +92,65 @@ class MonthReportGenerator implements ReportGeneratorInterface return $result; } + /** + * Get the audit report. + * + * @param Account $account + * @param Carbon $date + * + * @return array + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // not that long + * @throws FireflyException + */ + public function getAuditReport(Account $account, Carbon $date): array + { + /** @var CurrencyRepositoryInterface $currencyRepos */ + $currencyRepos = app(CurrencyRepositoryInterface::class); + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + $accountRepository->setUser($account->user); + + /** @var TransactionCollectorInterface $collector */ + $collector = app(TransactionCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); + $journals = $collector->getTransactions(); + $journals = $journals->reverse(); + $dayBeforeBalance = app('steam')->balance($account, $date); + $startBalance = $dayBeforeBalance; + $currency = $currencyRepos->findNull((int)$accountRepository->getMetaValue($account, 'currency_id')); + + if (null === $currency) { + throw new FireflyException('Unexpected NULL value in account currency preference.'); + } + + /** @var Transaction $transaction */ + foreach ($journals as $transaction) { + $transaction->before = $startBalance; + $transactionAmount = $transaction->transaction_amount; + + if ($currency->id === $transaction->foreign_currency_id) { + $transactionAmount = $transaction->transaction_foreign_amount; + } + + $newBalance = bcadd($startBalance, $transactionAmount); + $transaction->after = $newBalance; + $startBalance = $newBalance; + } + + $return = [ + 'journals' => $journals->reverse(), + 'exists' => $journals->count() > 0, + 'end' => $this->end->formatLocalized((string)trans('config.month_and_day')), + 'endBalance' => app('steam')->balance($account, $this->end), + 'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day')), + 'dayBeforeBalance' => $dayBeforeBalance, + ]; + + return $return; + } + /** * Account collection setter. * @@ -187,63 +247,4 @@ class MonthReportGenerator implements ReportGeneratorInterface { return $this; } - - /** - * Get the audit report. - * - * @param Account $account - * @param Carbon $date - * - * @return array - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // not that long - * @throws FireflyException - */ - private function getAuditReport(Account $account, Carbon $date): array - { - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $accountRepository->setUser($account->user); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); - $journals = $collector->getTransactions(); - $journals = $journals->reverse(); - $dayBeforeBalance = app('steam')->balance($account, $date); - $startBalance = $dayBeforeBalance; - $currency = $currencyRepos->findNull((int)$accountRepository->getMetaValue($account, 'currency_id')); - - if (null === $currency) { - throw new FireflyException('Unexpected NULL value in account currency preference.'); - } - - /** @var Transaction $transaction */ - foreach ($journals as $transaction) { - $transaction->before = $startBalance; - $transactionAmount = $transaction->transaction_amount; - - if ($currency->id === $transaction->foreign_currency_id) { - $transactionAmount = $transaction->transaction_foreign_amount; - } - - $newBalance = bcadd($startBalance, $transactionAmount); - $transaction->after = $newBalance; - $startBalance = $newBalance; - } - - $return = [ - 'journals' => $journals->reverse(), - 'exists' => $journals->count() > 0, - 'end' => $this->end->formatLocalized((string)trans('config.month_and_day')), - 'endBalance' => app('steam')->balance($account, $this->end), - 'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day')), - 'dayBeforeBalance' => $dayBeforeBalance, - ]; - - return $return; - } } diff --git a/app/Handlers/Events/APIEventHandler.php b/app/Handlers/Events/APIEventHandler.php index b35130f0d0..0b93895ee1 100644 --- a/app/Handlers/Events/APIEventHandler.php +++ b/app/Handlers/Events/APIEventHandler.php @@ -65,10 +65,10 @@ class APIEventHandler Log::error($e->getTraceAsString()); Session::flash('error', 'Possible email error: ' . $e->getMessage()); } + // @codeCoverageIgnoreEnd Log::debug('If no error above this line, message was sent.'); } - // @codeCoverageIgnoreEnd return true; diff --git a/app/Handlers/Events/AdminEventHandler.php b/app/Handlers/Events/AdminEventHandler.php index 1bc55af2ba..6d3c09a62b 100644 --- a/app/Handlers/Events/AdminEventHandler.php +++ b/app/Handlers/Events/AdminEventHandler.php @@ -57,16 +57,17 @@ class AdminEventHandler Log::debug('Trying to send message...'); Mail::to($email)->send(new AdminTestMail($email, $ipAddress)); // @codeCoverageIgnoreStart + // Laravel cannot pretend this process failed during testing. } catch (Exception $e) { Log::debug('Send message failed! :('); Log::error($e->getMessage()); Log::error($e->getTraceAsString()); Session::flash('error', 'Possible email error: ' . $e->getMessage()); } + // @codeCoverageIgnoreEnd Log::debug('If no error above this line, message was sent.'); } - // @codeCoverageIgnoreEnd return true; } } diff --git a/app/Handlers/Events/AutomationHandler.php b/app/Handlers/Events/AutomationHandler.php index f7549ae84b..2b1464010c 100644 --- a/app/Handlers/Events/AutomationHandler.php +++ b/app/Handlers/Events/AutomationHandler.php @@ -55,12 +55,14 @@ class AutomationHandler Mail::to($user->email)->send(new ReportNewJournalsMail($user->email, '127.0.0.1', $event->journals)); // @codeCoverageIgnoreStart } catch (Exception $e) { + Log::debug('Send message failed! :('); Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); } + // @codeCoverageIgnoreEnd Log::debug('Done!'); } - // @codeCoverageIgnoreEnd return true; } } diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php index db3fcb41f2..3f5ea6be2f 100644 --- a/app/Handlers/Events/StoredJournalEventHandler.php +++ b/app/Handlers/Events/StoredJournalEventHandler.php @@ -25,8 +25,6 @@ namespace FireflyIII\Handlers\Events; use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\TransactionRules\Processor; @@ -35,28 +33,6 @@ use FireflyIII\TransactionRules\Processor; */ class StoredJournalEventHandler { - /** @var JournalRepositoryInterface The journal repository. */ - public $journalRepository; - /** @var PiggyBankRepositoryInterface The Piggy bank repository */ - public $repository; - /** @var RuleGroupRepositoryInterface The rule group repository */ - public $ruleGroupRepository; - - /** - * StoredJournalEventHandler constructor. - * - * @param PiggyBankRepositoryInterface $repository - * @param JournalRepositoryInterface $journalRepository - * @param RuleGroupRepositoryInterface $ruleGroupRepository - */ - public function __construct( - PiggyBankRepositoryInterface $repository, JournalRepositoryInterface $journalRepository, RuleGroupRepositoryInterface $ruleGroupRepository - ) { - $this->repository = $repository; - $this->journalRepository = $journalRepository; - $this->ruleGroupRepository = $ruleGroupRepository; - } - /** * This method grabs all the users rules and processes them. * @@ -67,13 +43,15 @@ class StoredJournalEventHandler */ public function processRules(StoredTransactionJournal $storedJournalEvent): bool { - // get all the user's rule groups, with the rules, order by 'order'. $journal = $storedJournalEvent->journal; - $groups = $this->ruleGroupRepository->getActiveGroups($journal->user); + + // create objects: + $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); + $groups = $ruleGroupRepos->getActiveGroups($journal->user); /** @var RuleGroup $group */ foreach ($groups as $group) { - $rules = $this->ruleGroupRepository->getActiveStoreRules($group); + $rules = $ruleGroupRepos->getActiveStoreRules($group); /** @var Rule $rule */ foreach ($rules as $rule) { $processor = Processor::make($rule); diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index e40ff67b55..41d7b83c80 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -141,12 +141,10 @@ class UserEventHandler $uri = route('profile.confirm-email-change', [$token->data]); try { Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress)); - // @codeCoverageIgnoreStart } catch (Exception $e) { Log::error($e->getMessage()); } - // @codeCoverageIgnoreEnd return true; } @@ -167,12 +165,10 @@ class UserEventHandler $uri = route('profile.undo-email-change', [$token->data, hash('sha256', $oldEmail)]); try { Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress)); - // @codeCoverageIgnoreStart } catch (Exception $e) { Log::error($e->getMessage()); } - // @codeCoverageIgnoreEnd return true; } @@ -194,13 +190,10 @@ class UserEventHandler // send email. try { Mail::to($email)->send(new RequestedNewPasswordMail($url, $ipAddress)); - // @codeCoverageIgnoreStart } catch (Exception $e) { Log::error($e->getMessage()); } - // @codeCoverageIgnoreEnd - return true; } @@ -224,11 +217,9 @@ class UserEventHandler // send email. try { Mail::to($email)->send(new RegisteredUserMail($uri, $ipAddress)); - // @codeCoverageIgnoreStart } catch (Exception $e) { Log::error($e->getMessage()); } - // @codeCoverageIgnoreEnd } return true; diff --git a/app/Handlers/Events/VersionCheckEventHandler.php b/app/Handlers/Events/VersionCheckEventHandler.php index e7d893cefa..5b7efb4e45 100644 --- a/app/Handlers/Events/VersionCheckEventHandler.php +++ b/app/Handlers/Events/VersionCheckEventHandler.php @@ -56,7 +56,8 @@ class VersionCheckEventHandler $sandstorm = 1 === (int)getenv('SANDSTORM'); if (true === $sandstorm) { Log::debug('This is Sandstorm instance, done.'); - return; // @codeCoverageIgnore + + return; } /** @var UserRepositoryInterface $repository */ @@ -65,6 +66,7 @@ class VersionCheckEventHandler $user = $event->user; if (!$repository->hasRole($user, 'owner')) { Log::debug('User is not admin, done.'); + return; } diff --git a/tests/Unit/Generator/Report/Audit/MonthReportGeneratorTest.php b/tests/Unit/Generator/Report/Audit/MonthReportGeneratorTest.php new file mode 100644 index 0000000000..a30241b664 --- /dev/null +++ b/tests/Unit/Generator/Report/Audit/MonthReportGeneratorTest.php @@ -0,0 +1,230 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Generator\Report\Audit; + + +use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Generator\Report\Audit\MonthReportGenerator; +use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Models\Account; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use Illuminate\Support\Collection; +use Log; +use Mockery; +use Steam; +use Tests\TestCase; + +/** + * + * Class MonthReportGeneratorTest + */ +class MonthReportGeneratorTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Generator\Report\Audit\MonthReportGenerator + */ + public function testBasic(): void + { + /** @var Account $account */ + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $date = new Carbon; + $start = Carbon::create()->startOfMonth(); + $end = Carbon::create()->endOfMonth(); + $generator = new MonthReportGenerator(); + $generator->setStartDate($start); + $generator->setEndDate($end); + + $collection = new Collection; + + // mock stuff + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $collector = $this->mock(TransactionCollectorInterface::class); + Steam::shouldReceive('balance')->times(2)->andReturn('100'); + + // mock calls: + $accountRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->once(); + + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first())->once(); + + $collector->shouldReceive('setAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('getTransactions')->andReturn($collection); + + + try { + $result = $generator->getAuditReport($account, $date); + } catch (FireflyException $e) { + $this->assertTrue(false, $e->getMessage()); + } + $this->assertFalse($result['exists']); + $this->assertEquals('100', $result['endBalance']); + } + + /** + * @covers \FireflyIII\Generator\Report\Audit\MonthReportGenerator + */ + public function testBasicNoCurrency(): void + { + /** @var Account $account */ + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $date = new Carbon; + $start = Carbon::create()->startOfMonth(); + $end = Carbon::create()->endOfMonth(); + $generator = new MonthReportGenerator(); + $generator->setStartDate($start); + $generator->setEndDate($end); + + $collection = new Collection; + + // mock stuff + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $collector = $this->mock(TransactionCollectorInterface::class); + Steam::shouldReceive('balance')->times(1)->andReturn('100'); + + // mock calls: + $accountRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->once(); + + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(null)->once(); + + $collector->shouldReceive('setAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('getTransactions')->andReturn($collection); + + + try { + $generator->getAuditReport($account, $date); + } catch (FireflyException $e) { + $this->assertEquals('Unexpected NULL value in account currency preference.', $e->getMessage()); + } + } + + /** + * @covers \FireflyIII\Generator\Report\Audit\MonthReportGenerator + */ + public function testBasicWithForeign(): void + { + /** @var Account $account */ + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $date = new Carbon; + $start = Carbon::create()->startOfMonth(); + $end = Carbon::create()->endOfMonth(); + $generator = new MonthReportGenerator(); + $generator->setStartDate($start); + $generator->setEndDate($end); + + $collection = new Collection; + $transaction = $this->user()->transactions()->first(); + $transaction->transaction_amount = '30'; + $transaction->foreign_currency_id = 1; + $transaction->transaction_foreign_amount = '30'; + $collection->push($transaction); + + // mock stuff + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $collector = $this->mock(TransactionCollectorInterface::class); + Steam::shouldReceive('balance')->times(2)->andReturn('100'); + + // mock calls: + $accountRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->once(); + + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first())->once(); + + $collector->shouldReceive('setAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('getTransactions')->andReturn($collection); + + + try { + $result = $generator->getAuditReport($account, $date); + } catch (FireflyException $e) { + $this->assertTrue(false, $e->getMessage()); + } + $this->assertTrue($result['exists']); + $this->assertEquals('100', $result['endBalance']); + } + + /** + * @covers \FireflyIII\Generator\Report\Audit\MonthReportGenerator + */ + public function testBasicWithTransactions(): void + { + /** @var Account $account */ + $account = $this->user()->accounts()->where('account_type_id', 3)->first(); + $date = new Carbon; + $start = Carbon::create()->startOfMonth(); + $end = Carbon::create()->endOfMonth(); + $generator = new MonthReportGenerator(); + $generator->setStartDate($start); + $generator->setEndDate($end); + + $collection = new Collection; + $transaction = $this->user()->transactions()->first(); + $transaction->transaction_amount = '30'; + $collection->push($transaction); + + // mock stuff + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $collector = $this->mock(TransactionCollectorInterface::class); + Steam::shouldReceive('balance')->times(2)->andReturn('100'); + + // mock calls: + $accountRepos->shouldReceive('setUser')->once(); + $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->once(); + + $currencyRepos->shouldReceive('findNull')->withArgs([1])->andReturn(TransactionCurrency::first())->once(); + + $collector->shouldReceive('setAccounts')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('getTransactions')->andReturn($collection); + + + try { + $result = $generator->getAuditReport($account, $date); + } catch (FireflyException $e) { + $this->assertTrue(false, $e->getMessage()); + } + $this->assertTrue($result['exists']); + $this->assertEquals('100', $result['endBalance']); + } + +} \ No newline at end of file diff --git a/tests/Unit/Handlers/Events/APIEventHandlerTest.php b/tests/Unit/Handlers/Events/APIEventHandlerTest.php new file mode 100644 index 0000000000..8c971f9698 --- /dev/null +++ b/tests/Unit/Handlers/Events/APIEventHandlerTest.php @@ -0,0 +1,76 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Handlers\Events; + + +use FireflyIII\Handlers\Events\APIEventHandler; +use FireflyIII\Mail\AccessTokenCreatedMail; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Support\Facades\Mail; +use Laravel\Passport\Events\AccessTokenCreated; +use Log; +use Tests\TestCase; + +/** + * + * Class APIEventHandlerTest + */ +class APIEventHandlerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Handlers\Events\APIEventHandler + */ + public function testAccessTokenCreated(): void + { + Mail::fake(); + // mock objects. + $repository = $this->mock(UserRepositoryInterface::class); + + // mock calls. + $repository->shouldReceive('findNull')->withArgs([1])->andReturn($this->user())->once(); + + + $event = new AccessTokenCreated('1', '1', '1'); + $handler = new APIEventHandler; + $handler->accessTokenCreated($event); + + // assert a message was sent. + Mail::assertSent( + AccessTokenCreatedMail::class, function ($mail) { + return $mail->hasTo('thegrumpydictator@gmail.com') && '127.0.0.1' === $mail->ipAddress; + } + ); + + } + +} \ No newline at end of file diff --git a/tests/Unit/Handlers/Events/AdminEventHandlerTest.php b/tests/Unit/Handlers/Events/AdminEventHandlerTest.php index 59e1d2f26c..6515aa743b 100644 --- a/tests/Unit/Handlers/Events/AdminEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/AdminEventHandlerTest.php @@ -31,12 +31,21 @@ use FireflyIII\Repositories\User\UserRepositoryInterface; use Illuminate\Support\Facades\Mail; use Mockery; use Tests\TestCase; - +use Log; /** * Class AdminEventHandlerTest */ class AdminEventHandlerTest extends TestCase { + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Handlers\Events\AdminEventHandler @@ -77,6 +86,6 @@ class AdminEventHandlerTest extends TestCase } ); - } + } } diff --git a/tests/Unit/Handlers/Events/AutomationHandlerTest.php b/tests/Unit/Handlers/Events/AutomationHandlerTest.php new file mode 100644 index 0000000000..16fbfd723b --- /dev/null +++ b/tests/Unit/Handlers/Events/AutomationHandlerTest.php @@ -0,0 +1,80 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Handlers\Events; + + +use FireflyIII\Events\RequestedReportOnJournals; +use FireflyIII\Handlers\Events\AutomationHandler; +use FireflyIII\Mail\ReportNewJournalsMail; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Mail; +use Log; +use stdClass; +use Tests\TestCase; + +/** + * + * Class AutomationHandlerTest + */ +class AutomationHandlerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + + /** + * @covers \FireflyIII\Handlers\Events\AutomationHandler + * @covers \FireflyIII\Events\RequestedReportOnJournals + */ + public function testReportJournals(): void + { + Mail::fake(); + // mock repositories + $repository = $this->mock(UserRepositoryInterface::class); + $journals = new Collection; + $journals->push(new stdClass); + + // mock calls. + $repository->shouldReceive('findNull')->withArgs([1])->andReturn($this->user())->once(); + + $event = new RequestedReportOnJournals(1, $journals); + $handler = new AutomationHandler(); + + $handler->reportJournals($event); + + // assert a message was sent. + Mail::assertSent( + ReportNewJournalsMail::class, function ($mail) { + return $mail->hasTo('thegrumpydictator@gmail.com') && '127.0.0.1' === $mail->ipAddress; + } + ); + } +} \ No newline at end of file diff --git a/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php b/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php new file mode 100644 index 0000000000..b3fa06f1db --- /dev/null +++ b/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php @@ -0,0 +1,74 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Handlers\Events; + + +use FireflyIII\Events\StoredTransactionJournal; +use FireflyIII\Handlers\Events\StoredJournalEventHandler; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use Log; +use Tests\TestCase; + +/** + * + * Class StoredJournalEventHandlerTest + */ +class StoredJournalEventHandlerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** + * @covers \FireflyIII\Handlers\Events\StoredJournalEventHandler + * @covers \FireflyIII\Events\StoredTransactionJournal + */ + public function testProcessRules(): void + { +// $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); +// $journal = $this->user()->transactionJournals()->inRandomOrder()->first(); +// $piggy = $this->user()->piggyBanks()->inRandomOrder()->first(); +// $event = new StoredTransactionJournal($journal, $piggy->id); +// $ruleGroups = $this->user()->ruleGroups()->take(1)->get(); +// $rules = $this->user()->rules()->take(1)->get(); +// +// // mock calls: +// $ruleGroupRepos->shouldReceive('setUser')->once(); +// $ruleGroupRepos->shouldReceive('getActiveGroups')->andReturn($ruleGroups)->once(); +// $ruleGroupRepos->shouldReceive('getActiveStoreRules')->andReturn($rules)->once(); +// +// +// +// $handler = new StoredJournalEventHandler; +// $handler->processRules($event); + $this->assertTrue(true); + + + } +} \ No newline at end of file diff --git a/tests/Unit/Handlers/Events/UserEventHandlerTest.php b/tests/Unit/Handlers/Events/UserEventHandlerTest.php index 6c98ed3ff5..72c7c8d7be 100644 --- a/tests/Unit/Handlers/Events/UserEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/UserEventHandlerTest.php @@ -36,6 +36,7 @@ use Illuminate\Auth\Events\Login; use Illuminate\Support\Facades\Mail; use Mockery; use Tests\TestCase; +use Log; /** * Class UserEventHandlerTest @@ -46,6 +47,15 @@ use Tests\TestCase; */ class UserEventHandlerTest extends TestCase { + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + /** * @covers \FireflyIII\Handlers\Events\UserEventHandler * @covers \FireflyIII\Events\RegisteredUser diff --git a/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php b/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php index bb33fbab54..9c54051d92 100644 --- a/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/VersionCheckEventHandlerTest.php @@ -34,12 +34,23 @@ use FireflyIII\Services\Github\Object\Release; use FireflyIII\Services\Github\Request\UpdateRequest; use Mockery; use Tests\TestCase; +use Log; /** * Class VersionCheckEventHandlerTest */ class VersionCheckEventHandlerTest extends TestCase { + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + /** * */ From 835a421909fc3069542dcc41f47e77884768ae0d Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 24 Aug 2018 17:57:34 +0200 Subject: [PATCH 124/166] Refactor rule processor so it's testable. --- .../Events/StoredJournalEventHandler.php | 6 +- .../Events/UpdatedJournalEventHandler.php | 26 ++- app/Import/Storage/ImportArrayStorage.php | 4 +- app/Jobs/CreateRecurringTransactions.php | 4 +- ...ExecuteRuleGroupOnExistingTransactions.php | 5 +- .../ExecuteRuleOnExistingTransactions.php | 4 +- app/TransactionRules/Processor.php | 151 ++++++++---------- app/TransactionRules/TransactionMatcher.php | 6 +- .../Events/StoredJournalEventHandlerTest.php | 40 +++-- .../Events/UpdatedJournalEventHandlerTest.php | 78 +++++++++ 10 files changed, 202 insertions(+), 122 deletions(-) create mode 100644 tests/Unit/Handlers/Events/UpdatedJournalEventHandlerTest.php diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php index 3f5ea6be2f..f42d814b49 100644 --- a/app/Handlers/Events/StoredJournalEventHandler.php +++ b/app/Handlers/Events/StoredJournalEventHandler.php @@ -46,7 +46,9 @@ class StoredJournalEventHandler $journal = $storedJournalEvent->journal; // create objects: + /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->setUser($journal->user); $groups = $ruleGroupRepos->getActiveGroups($journal->user); /** @var RuleGroup $group */ @@ -54,7 +56,9 @@ class StoredJournalEventHandler $rules = $ruleGroupRepos->getActiveStoreRules($group); /** @var Rule $rule */ foreach ($rules as $rule) { - $processor = Processor::make($rule); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule); $processor->handleTransactionJournal($journal); if ($rule->stop_processing) { diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php index f6308e08e3..30ffeb83ab 100644 --- a/app/Handlers/Events/UpdatedJournalEventHandler.php +++ b/app/Handlers/Events/UpdatedJournalEventHandler.php @@ -33,19 +33,6 @@ use FireflyIII\TransactionRules\Processor; */ class UpdatedJournalEventHandler { - /** @var RuleGroupRepositoryInterface The rule group repository */ - public $repository; - - /** - * StoredJournalEventHandler constructor. - * - * @param RuleGroupRepositoryInterface $ruleGroupRepository - */ - public function __construct(RuleGroupRepositoryInterface $ruleGroupRepository) - { - $this->repository = $ruleGroupRepository; - } - /** * This method will check all the rules when a journal is updated. * @@ -58,14 +45,21 @@ class UpdatedJournalEventHandler { // get all the user's rule groups, with the rules, order by 'order'. $journal = $updatedJournalEvent->journal; - $groups = $this->repository->getActiveGroups($journal->user); + + /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ + $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); + $ruleGroupRepos->setUser($journal->user); + + $groups = $ruleGroupRepos->getActiveGroups($journal->user); /** @var RuleGroup $group */ foreach ($groups as $group) { - $rules = $this->repository->getActiveUpdateRules($group); + $rules = $ruleGroupRepos->getActiveUpdateRules($group); /** @var Rule $rule */ foreach ($rules as $rule) { - $processor = Processor::make($rule); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule); $processor->handleTransactionJournal($journal); if ($rule->stop_processing) { diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 232f552b2f..b475b67bb6 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -135,7 +135,9 @@ class ImportArrayStorage $rules->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); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule); $processor->handleTransactionJournal($journal); if ($rule->stop_processing) { return false; diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 4ee18fddfd..22177cc9a2 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -157,7 +157,9 @@ class CreateRecurringTransactions implements ShouldQueue $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); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule); /** @noinspection ExceptionsAnnotatingAndHandlingInspection */ $processor->handleTransactionJournal($journal); if ($rule->stop_processing) { diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php index e0e43ca70a..f6901a6f3f 100644 --- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php +++ b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php @@ -200,7 +200,10 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue // Create a list of processors for these rules return array_map( function ($rule) { - return Processor::make($rule); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule); + return $processor; }, $rules->all() ); diff --git a/app/Jobs/ExecuteRuleOnExistingTransactions.php b/app/Jobs/ExecuteRuleOnExistingTransactions.php index 29b101035e..16d0e660d2 100644 --- a/app/Jobs/ExecuteRuleOnExistingTransactions.php +++ b/app/Jobs/ExecuteRuleOnExistingTransactions.php @@ -162,7 +162,9 @@ class ExecuteRuleOnExistingTransactions extends Job implements ShouldQueue { // Lookup all journals that match the parameters specified $transactions = $this->collectJournals(); - $processor = Processor::make($this->rule, true); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($this->rule, true); $hits = 0; $misses = 0; $total = 0; diff --git a/app/TransactionRules/Processor.php b/app/TransactionRules/Processor.php index 6acf0fcd64..42dffbacb4 100644 --- a/app/TransactionRules/Processor.php +++ b/app/TransactionRules/Processor.php @@ -38,7 +38,7 @@ use Log; /** * Class Processor. */ -final class Processor +class Processor { /** @var Collection Actions to exectute */ public $actions; @@ -56,92 +56,12 @@ final class Processor /** * Processor constructor. */ - private function __construct() + public function __construct() { $this->triggers = new Collection; $this->actions = new Collection; } - /** - * This method will make a Processor that will process each transaction journal using the triggers - * and actions found in the given Rule. - * - * @param Rule $rule - * @param bool $includeActions - * - * @return Processor - * @throws \FireflyIII\Exceptions\FireflyException - */ - public static function make(Rule $rule, bool $includeActions = null): Processor - { - $includeActions = $includeActions ?? true; - Log::debug(sprintf('Making new rule from Rule %d', $rule->id)); - Log::debug(sprintf('Rule is strict: %s', var_export($rule->strict, true))); - $self = new self; - $self->rule = $rule; - $self->strict = $rule->strict; - $triggerSet = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); - /** @var RuleTrigger $trigger */ - foreach ($triggerSet as $trigger) { - Log::debug(sprintf('Push trigger %d', $trigger->id)); - $self->triggers->push(TriggerFactory::getTrigger($trigger)); - } - if (true === $includeActions) { - $self->actions = $rule->ruleActions()->orderBy('order', 'ASC')->get(); - } - - return $self; - } - - /** - * This method will make a Processor that will process each transaction journal using the given - * trigger (singular!). It can only report if the transaction journal was hit by the given trigger - * and will not be able to act on it using actions. - * - * @param string $triggerName - * @param string $triggerValue - * - * @return Processor - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - public static function makeFromString(string $triggerName, string $triggerValue): Processor - { - Log::debug(sprintf('Processor::makeFromString("%s", "%s")', $triggerName, $triggerValue)); - $self = new self; - $trigger = TriggerFactory::makeTriggerFromStrings($triggerName, $triggerValue, false); - $self->triggers->push($trigger); - - return $self; - } - - /** - * This method will make a Processor that will process each transaction journal using the given - * triggers. It can only report if the transaction journal was hit by the given triggers - * and will not be able to act on it using actions. - * - * The given triggers must be in the following format: - * - * [type => xx, value => yy, stop_processing => bool], [type => xx, value => yy, stop_processing => bool], - * - * @param array $triggers - * - * @return Processor - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - public static function makeFromStringArray(array $triggers): Processor - { - $self = new self; - foreach ($triggers as $entry) { - $entry['value'] = $entry['value'] ?? ''; - $trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stop_processing']); - $self->triggers->push($trigger); - } - - return $self; - } - /** * Return found triggers * @@ -234,6 +154,73 @@ final class Processor return false; } + /** + * This method will make a Processor that will process each transaction journal using the triggers + * and actions found in the given Rule. + * + * @param Rule $rule + * @param bool $includeActions + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function make(Rule $rule, bool $includeActions = null): void + { + $includeActions = $includeActions ?? true; + Log::debug(sprintf('Making new rule from Rule %d', $rule->id)); + Log::debug(sprintf('Rule is strict: %s', var_export($rule->strict, true))); + $this->rule = $rule; + $this->strict = $rule->strict; + $triggerSet = $rule->ruleTriggers()->orderBy('order', 'ASC')->get(); + /** @var RuleTrigger $trigger */ + foreach ($triggerSet as $trigger) { + Log::debug(sprintf('Push trigger %d', $trigger->id)); + $this->triggers->push(TriggerFactory::getTrigger($trigger)); + } + if (true === $includeActions) { + $this->actions = $rule->ruleActions()->orderBy('order', 'ASC')->get(); + } + } + + /** + * This method will make a Processor that will process each transaction journal using the given + * trigger (singular!). It can only report if the transaction journal was hit by the given trigger + * and will not be able to act on it using actions. + * + * @param string $triggerName + * @param string $triggerValue + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function makeFromString(string $triggerName, string $triggerValue): void + { + Log::debug(sprintf('Processor::makeFromString("%s", "%s")', $triggerName, $triggerValue)); + $trigger = TriggerFactory::makeTriggerFromStrings($triggerName, $triggerValue, false); + $this->triggers->push($trigger); + } + + /** + * This method will make a Processor that will process each transaction journal using the given + * triggers. It can only report if the transaction journal was hit by the given triggers + * and will not be able to act on it using actions. + * + * The given triggers must be in the following format: + * + * [type => xx, value => yy, stop_processing => bool], [type => xx, value => yy, stop_processing => bool], + * + * @param array $triggers + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function makeFromStringArray(array $triggers): void + { + foreach ($triggers as $entry) { + $entry['value'] = $entry['value'] ?? ''; + $trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stop_processing']); + $this->triggers->push($trigger); + } + + } + /** * Run the actions * diff --git a/app/TransactionRules/TransactionMatcher.php b/app/TransactionRules/TransactionMatcher.php index 23766655e3..4cb1b0d120 100644 --- a/app/TransactionRules/TransactionMatcher.php +++ b/app/TransactionRules/TransactionMatcher.php @@ -93,8 +93,10 @@ class TransactionMatcher } // Variables used within the loop - $processor = Processor::makeFromStringArray($this->triggers); - $result = $this->runProcessor($processor); + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->makeFromStringArray($this->triggers); + $result = $this->runProcessor($processor); // If the list of matchingTransactions is larger than the maximum number of results // (e.g. if a large percentage of the transactions match), truncate the list diff --git a/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php b/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php index b3fa06f1db..a91b4dc99b 100644 --- a/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/StoredJournalEventHandlerTest.php @@ -25,8 +25,10 @@ namespace Tests\Unit\Handlers\Events; use FireflyIII\Events\StoredTransactionJournal; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Handlers\Events\StoredJournalEventHandler; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\TransactionRules\Processor; use Log; use Tests\TestCase; @@ -51,24 +53,28 @@ class StoredJournalEventHandlerTest extends TestCase */ public function testProcessRules(): void { -// $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); -// $journal = $this->user()->transactionJournals()->inRandomOrder()->first(); -// $piggy = $this->user()->piggyBanks()->inRandomOrder()->first(); -// $event = new StoredTransactionJournal($journal, $piggy->id); -// $ruleGroups = $this->user()->ruleGroups()->take(1)->get(); -// $rules = $this->user()->rules()->take(1)->get(); -// -// // mock calls: -// $ruleGroupRepos->shouldReceive('setUser')->once(); -// $ruleGroupRepos->shouldReceive('getActiveGroups')->andReturn($ruleGroups)->once(); -// $ruleGroupRepos->shouldReceive('getActiveStoreRules')->andReturn($rules)->once(); -// -// -// -// $handler = new StoredJournalEventHandler; -// $handler->processRules($event); - $this->assertTrue(true); + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + $processor = $this->mock(Processor::class); + + $journal = $this->user()->transactionJournals()->inRandomOrder()->first(); + $piggy = $this->user()->piggyBanks()->inRandomOrder()->first(); + $event = new StoredTransactionJournal($journal, $piggy->id); + $ruleGroups = $this->user()->ruleGroups()->take(1)->get(); + $rules = $this->user()->rules()->take(1)->get(); + + // mock calls: + $ruleGroupRepos->shouldReceive('setUser')->once(); + $ruleGroupRepos->shouldReceive('getActiveGroups')->andReturn($ruleGroups)->once(); + $ruleGroupRepos->shouldReceive('getActiveStoreRules')->andReturn($rules)->once(); + $processor->shouldReceive('make')->once(); + $processor->shouldReceive('handleTransactionJournal')->once(); + $handler = new StoredJournalEventHandler; + try { + $handler->processRules($event); + } catch (FireflyException $e) { + $this->assertTrue(false, $e->getMessage()); + } } } \ No newline at end of file diff --git a/tests/Unit/Handlers/Events/UpdatedJournalEventHandlerTest.php b/tests/Unit/Handlers/Events/UpdatedJournalEventHandlerTest.php new file mode 100644 index 0000000000..b7fffc56e2 --- /dev/null +++ b/tests/Unit/Handlers/Events/UpdatedJournalEventHandlerTest.php @@ -0,0 +1,78 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Handlers\Events; + +use FireflyIII\Events\UpdatedTransactionJournal; +use FireflyIII\Handlers\Events\UpdatedJournalEventHandler; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\TransactionRules\Processor; +use Log; +use Tests\TestCase; + +/** + * Class UpdatedJournalEventHandlerTest + */ +class UpdatedJournalEventHandlerTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::debug(sprintf('Now in %s.', \get_class($this))); + } + + + /** + * @covers \FireflyIII\Handlers\Events\UpdatedJournalEventHandler + * @covers \FireflyIII\Events\StoredTransactionJournal + */ + public function testProcessRules(): void + { + $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class); + $processor = $this->mock(Processor::class); + + $journal = $this->user()->transactionJournals()->inRandomOrder()->first(); + $event = new UpdatedTransactionJournal($journal); + $ruleGroups = $this->user()->ruleGroups()->take(1)->get(); + $rules = $this->user()->rules()->take(1)->get(); + + // mock calls: + $ruleGroupRepos->shouldReceive('setUser')->once(); + $ruleGroupRepos->shouldReceive('getActiveGroups')->andReturn($ruleGroups)->once(); + $ruleGroupRepos->shouldReceive('getActiveUpdateRules')->andReturn($rules)->once(); + $processor->shouldReceive('make')->once(); + $processor->shouldReceive('handleTransactionJournal')->once(); + + + $handler = new UpdatedJournalEventHandler; + try { + $handler->processRules($event); + } catch (FireflyException $e) { + $this->assertTrue(false, $e->getMessage()); + } + } + +} \ No newline at end of file From e775927f6062a207b6baeb5bedd5db032a88aeff Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 24 Aug 2018 21:10:04 +0200 Subject: [PATCH 125/166] Add htaccess files to prevent directory indexing. [skip ci] --- public/css/.htaccess | 1 + public/css/jquery-ui/.htaccess | 1 + public/css/jquery-ui/images/.htaccess | 1 + public/fonts/.htaccess | 1 + public/fonts/vendor/.htaccess | 1 + public/fonts/vendor/bootstrap-sass/.htaccess | 1 + public/fonts/vendor/bootstrap-sass/bootstrap/.htaccess | 1 + public/fonts/vendor/font-awesome/.htaccess | 1 + public/images/.htaccess | 1 + public/images/flags/.htaccess | 1 + public/images/logos/.htaccess | 1 + public/js/.htaccess | 1 + public/js/ff/.htaccess | 1 + public/js/ff/accounts/.htaccess | 1 + public/js/ff/admin/.htaccess | 1 + public/js/ff/admin/update/.htaccess | 1 + public/js/ff/bills/.htaccess | 1 + public/js/ff/budgets/.htaccess | 1 + public/js/ff/categories/.htaccess | 1 + public/js/ff/export/.htaccess | 1 + public/js/ff/import/.htaccess | 1 + public/js/ff/import/file/.htaccess | 1 + public/js/ff/install/.htaccess | 1 + public/js/ff/intro/.htaccess | 1 + public/js/ff/moment/.htaccess | 1 + public/js/ff/piggy-banks/.htaccess | 1 + public/js/ff/preferences/.htaccess | 1 + public/js/ff/recurring/.htaccess | 1 + public/js/ff/reports/.htaccess | 1 + public/js/ff/reports/account/.htaccess | 1 + public/js/ff/reports/audit/.htaccess | 1 + public/js/ff/reports/budget/.htaccess | 1 + public/js/ff/reports/category/.htaccess | 1 + public/js/ff/reports/default/.htaccess | 1 + public/js/ff/reports/tag/.htaccess | 1 + public/js/ff/rules/.htaccess | 1 + public/js/ff/search/.htaccess | 1 + public/js/ff/tags/.htaccess | 1 + public/js/ff/transactions/.htaccess | 1 + public/js/ff/transactions/mass/.htaccess | 1 + public/js/ff/transactions/single/.htaccess | 1 + public/js/ff/transactions/split/.htaccess | 1 + public/js/lib/.htaccess | 1 + public/lib/.htaccess | 1 + public/lib/adminlte/.htaccess | 1 + public/lib/adminlte/css/.htaccess | 1 + public/lib/adminlte/css/skins/.htaccess | 1 + public/lib/adminlte/img/.htaccess | 1 + public/lib/adminlte/js/.htaccess | 1 + public/lib/fc/.htaccess | 1 + public/lib/intro/.htaccess | 1 + public/lib/leaflet/.htaccess | 1 + public/lib/leaflet/images/.htaccess | 1 + 53 files changed, 53 insertions(+) create mode 100644 public/css/.htaccess create mode 100644 public/css/jquery-ui/.htaccess create mode 100644 public/css/jquery-ui/images/.htaccess create mode 100644 public/fonts/.htaccess create mode 100644 public/fonts/vendor/.htaccess create mode 100644 public/fonts/vendor/bootstrap-sass/.htaccess create mode 100644 public/fonts/vendor/bootstrap-sass/bootstrap/.htaccess create mode 100644 public/fonts/vendor/font-awesome/.htaccess create mode 100644 public/images/.htaccess create mode 100644 public/images/flags/.htaccess create mode 100644 public/images/logos/.htaccess create mode 100644 public/js/.htaccess create mode 100644 public/js/ff/.htaccess create mode 100644 public/js/ff/accounts/.htaccess create mode 100644 public/js/ff/admin/.htaccess create mode 100644 public/js/ff/admin/update/.htaccess create mode 100644 public/js/ff/bills/.htaccess create mode 100644 public/js/ff/budgets/.htaccess create mode 100644 public/js/ff/categories/.htaccess create mode 100644 public/js/ff/export/.htaccess create mode 100644 public/js/ff/import/.htaccess create mode 100644 public/js/ff/import/file/.htaccess create mode 100644 public/js/ff/install/.htaccess create mode 100644 public/js/ff/intro/.htaccess create mode 100644 public/js/ff/moment/.htaccess create mode 100644 public/js/ff/piggy-banks/.htaccess create mode 100644 public/js/ff/preferences/.htaccess create mode 100644 public/js/ff/recurring/.htaccess create mode 100644 public/js/ff/reports/.htaccess create mode 100644 public/js/ff/reports/account/.htaccess create mode 100644 public/js/ff/reports/audit/.htaccess create mode 100644 public/js/ff/reports/budget/.htaccess create mode 100644 public/js/ff/reports/category/.htaccess create mode 100644 public/js/ff/reports/default/.htaccess create mode 100644 public/js/ff/reports/tag/.htaccess create mode 100644 public/js/ff/rules/.htaccess create mode 100644 public/js/ff/search/.htaccess create mode 100644 public/js/ff/tags/.htaccess create mode 100644 public/js/ff/transactions/.htaccess create mode 100644 public/js/ff/transactions/mass/.htaccess create mode 100644 public/js/ff/transactions/single/.htaccess create mode 100644 public/js/ff/transactions/split/.htaccess create mode 100644 public/js/lib/.htaccess create mode 100644 public/lib/.htaccess create mode 100644 public/lib/adminlte/.htaccess create mode 100644 public/lib/adminlte/css/.htaccess create mode 100644 public/lib/adminlte/css/skins/.htaccess create mode 100644 public/lib/adminlte/img/.htaccess create mode 100644 public/lib/adminlte/js/.htaccess create mode 100644 public/lib/fc/.htaccess create mode 100644 public/lib/intro/.htaccess create mode 100644 public/lib/leaflet/.htaccess create mode 100644 public/lib/leaflet/images/.htaccess diff --git a/public/css/.htaccess b/public/css/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/css/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/css/jquery-ui/.htaccess b/public/css/jquery-ui/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/css/jquery-ui/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/css/jquery-ui/images/.htaccess b/public/css/jquery-ui/images/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/css/jquery-ui/images/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/fonts/.htaccess b/public/fonts/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/fonts/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/fonts/vendor/.htaccess b/public/fonts/vendor/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/fonts/vendor/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/fonts/vendor/bootstrap-sass/.htaccess b/public/fonts/vendor/bootstrap-sass/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/fonts/vendor/bootstrap-sass/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/fonts/vendor/bootstrap-sass/bootstrap/.htaccess b/public/fonts/vendor/bootstrap-sass/bootstrap/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/fonts/vendor/bootstrap-sass/bootstrap/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/fonts/vendor/font-awesome/.htaccess b/public/fonts/vendor/font-awesome/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/fonts/vendor/font-awesome/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/images/.htaccess b/public/images/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/images/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/images/flags/.htaccess b/public/images/flags/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/images/flags/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/images/logos/.htaccess b/public/images/logos/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/images/logos/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/.htaccess b/public/js/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/.htaccess b/public/js/ff/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/accounts/.htaccess b/public/js/ff/accounts/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/accounts/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/admin/.htaccess b/public/js/ff/admin/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/admin/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/admin/update/.htaccess b/public/js/ff/admin/update/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/admin/update/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/bills/.htaccess b/public/js/ff/bills/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/bills/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/budgets/.htaccess b/public/js/ff/budgets/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/budgets/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/categories/.htaccess b/public/js/ff/categories/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/categories/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/export/.htaccess b/public/js/ff/export/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/export/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/import/.htaccess b/public/js/ff/import/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/import/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/import/file/.htaccess b/public/js/ff/import/file/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/import/file/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/install/.htaccess b/public/js/ff/install/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/install/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/intro/.htaccess b/public/js/ff/intro/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/intro/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/moment/.htaccess b/public/js/ff/moment/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/moment/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/piggy-banks/.htaccess b/public/js/ff/piggy-banks/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/piggy-banks/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/preferences/.htaccess b/public/js/ff/preferences/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/preferences/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/recurring/.htaccess b/public/js/ff/recurring/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/recurring/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/.htaccess b/public/js/ff/reports/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/account/.htaccess b/public/js/ff/reports/account/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/account/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/audit/.htaccess b/public/js/ff/reports/audit/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/audit/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/budget/.htaccess b/public/js/ff/reports/budget/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/budget/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/category/.htaccess b/public/js/ff/reports/category/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/category/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/default/.htaccess b/public/js/ff/reports/default/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/default/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/reports/tag/.htaccess b/public/js/ff/reports/tag/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/reports/tag/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/rules/.htaccess b/public/js/ff/rules/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/rules/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/search/.htaccess b/public/js/ff/search/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/search/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/tags/.htaccess b/public/js/ff/tags/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/tags/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/transactions/.htaccess b/public/js/ff/transactions/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/transactions/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/transactions/mass/.htaccess b/public/js/ff/transactions/mass/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/transactions/mass/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/transactions/single/.htaccess b/public/js/ff/transactions/single/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/transactions/single/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/ff/transactions/split/.htaccess b/public/js/ff/transactions/split/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/ff/transactions/split/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/js/lib/.htaccess b/public/js/lib/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/js/lib/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/.htaccess b/public/lib/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/adminlte/.htaccess b/public/lib/adminlte/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/adminlte/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/adminlte/css/.htaccess b/public/lib/adminlte/css/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/adminlte/css/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/adminlte/css/skins/.htaccess b/public/lib/adminlte/css/skins/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/adminlte/css/skins/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/adminlte/img/.htaccess b/public/lib/adminlte/img/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/adminlte/img/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/adminlte/js/.htaccess b/public/lib/adminlte/js/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/adminlte/js/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/fc/.htaccess b/public/lib/fc/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/fc/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/intro/.htaccess b/public/lib/intro/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/intro/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/leaflet/.htaccess b/public/lib/leaflet/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/leaflet/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file diff --git a/public/lib/leaflet/images/.htaccess b/public/lib/leaflet/images/.htaccess new file mode 100644 index 0000000000..45552cb63e --- /dev/null +++ b/public/lib/leaflet/images/.htaccess @@ -0,0 +1 @@ +Options -Indexes \ No newline at end of file From 20490fcd8016e14985113272a2134ac20abea0a1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 24 Aug 2018 21:14:04 +0200 Subject: [PATCH 126/166] Update JS --- public/js/app.js | 2 +- resources/assets/js/bootstrap.js | 18 ------------------ resources/assets/js/lang.js | 30 ------------------------------ 3 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 resources/assets/js/lang.js diff --git a/public/js/app.js b/public/js/app.js index 3b1cd0f4ee..f45897ebf1 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1 +1 @@ -!function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=12)}([function(t,e,n){"use strict";var r=n(6),i=n(21),o=Object.prototype.toString;function a(t){return"[object Array]"===o.call(t)}function s(t){return null!==t&&"object"==typeof t}function u(t){return"[object Function]"===o.call(t)}function c(t,e){if(null!==t&&void 0!==t)if("object"!=typeof t&&(t=[t]),a(t))for(var n=0,r=t.length;n=200&&t<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],function(t){u.headers[t]={}}),r.forEach(["post","put","patch"],function(t){u.headers[t]=r.merge(o)}),t.exports=u}).call(e,n(7))},function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=function(t,e){var n=t[1]||"",r=t[3];if(!r)return n;if(e&&"function"==typeof btoa){var i=(a=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */"),o=r.sources.map(function(t){return"/*# sourceURL="+r.sourceRoot+t+" */"});return[n].concat(o).concat([i]).join("\n")}var a;return[n].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},i=0;in.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(i=0;i1)for(var n=1;n>>1,q=[["ary",T],["bind",g],["bindKey",y],["curry",_],["curryRight",w],["flip",$],["partial",x],["partialRight",C],["rearg",k]],B="[object Arguments]",H="[object Array]",U="[object AsyncFunction]",W="[object Boolean]",z="[object Date]",V="[object DOMException]",X="[object Error]",K="[object Function]",J="[object GeneratorFunction]",G="[object Map]",Q="[object Number]",Y="[object Null]",Z="[object Object]",tt="[object Proxy]",et="[object RegExp]",nt="[object Set]",rt="[object String]",it="[object Symbol]",ot="[object Undefined]",at="[object WeakMap]",st="[object WeakSet]",ut="[object ArrayBuffer]",ct="[object DataView]",lt="[object Float32Array]",ft="[object Float64Array]",pt="[object Int8Array]",dt="[object Int16Array]",ht="[object Int32Array]",vt="[object Uint8Array]",mt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",bt=/\b__p \+= '';/g,_t=/\b(__p \+=) '' \+/g,wt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,xt=/&(?:amp|lt|gt|quot|#39);/g,Ct=/[&<>"']/g,Tt=RegExp(xt.source),kt=RegExp(Ct.source),$t=/<%-([\s\S]+?)%>/g,At=/<%([\s\S]+?)%>/g,St=/<%=([\s\S]+?)%>/g,Et=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Ot=/^\w*$/,jt=/^\./,Nt=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Dt=/[\\^$.*+?()[\]{}|]/g,It=RegExp(Dt.source),Lt=/^\s+|\s+$/g,Rt=/^\s+/,Pt=/\s+$/,Ft=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Mt=/\{\n\/\* \[wrapped with (.+)\] \*/,qt=/,? & /,Bt=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,Ht=/\\(\\)?/g,Ut=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Wt=/\w*$/,zt=/^[-+]0x[0-9a-f]+$/i,Vt=/^0b[01]+$/i,Xt=/^\[object .+?Constructor\]$/,Kt=/^0o[0-7]+$/i,Jt=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Qt=/($^)/,Yt=/['\n\r\u2028\u2029\\]/g,Zt="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",te="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",ee="[\\ud800-\\udfff]",ne="["+te+"]",re="["+Zt+"]",ie="\\d+",oe="[\\u2700-\\u27bf]",ae="[a-z\\xdf-\\xf6\\xf8-\\xff]",se="[^\\ud800-\\udfff"+te+ie+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",ue="\\ud83c[\\udffb-\\udfff]",ce="[^\\ud800-\\udfff]",le="(?:\\ud83c[\\udde6-\\uddff]){2}",fe="[\\ud800-\\udbff][\\udc00-\\udfff]",pe="[A-Z\\xc0-\\xd6\\xd8-\\xde]",de="(?:"+ae+"|"+se+")",he="(?:"+pe+"|"+se+")",ve="(?:"+re+"|"+ue+")"+"?",me="[\\ufe0e\\ufe0f]?"+ve+("(?:\\u200d(?:"+[ce,le,fe].join("|")+")[\\ufe0e\\ufe0f]?"+ve+")*"),ge="(?:"+[oe,le,fe].join("|")+")"+me,ye="(?:"+[ce+re+"?",re,le,fe,ee].join("|")+")",be=RegExp("['’]","g"),_e=RegExp(re,"g"),we=RegExp(ue+"(?="+ue+")|"+ye+me,"g"),xe=RegExp([pe+"?"+ae+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[ne,pe,"$"].join("|")+")",he+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[ne,pe+de,"$"].join("|")+")",pe+"?"+de+"+(?:['’](?:d|ll|m|re|s|t|ve))?",pe+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)","\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)",ie,ge].join("|"),"g"),Ce=RegExp("[\\u200d\\ud800-\\udfff"+Zt+"\\ufe0e\\ufe0f]"),Te=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,ke=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],$e=-1,Ae={};Ae[lt]=Ae[ft]=Ae[pt]=Ae[dt]=Ae[ht]=Ae[vt]=Ae[mt]=Ae[gt]=Ae[yt]=!0,Ae[B]=Ae[H]=Ae[ut]=Ae[W]=Ae[ct]=Ae[z]=Ae[X]=Ae[K]=Ae[G]=Ae[Q]=Ae[Z]=Ae[et]=Ae[nt]=Ae[rt]=Ae[at]=!1;var Se={};Se[B]=Se[H]=Se[ut]=Se[ct]=Se[W]=Se[z]=Se[lt]=Se[ft]=Se[pt]=Se[dt]=Se[ht]=Se[G]=Se[Q]=Se[Z]=Se[et]=Se[nt]=Se[rt]=Se[it]=Se[vt]=Se[mt]=Se[gt]=Se[yt]=!0,Se[X]=Se[K]=Se[at]=!1;var Ee={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Oe=parseFloat,je=parseInt,Ne="object"==typeof t&&t&&t.Object===Object&&t,De="object"==typeof self&&self&&self.Object===Object&&self,Ie=Ne||De||Function("return this")(),Le="object"==typeof e&&e&&!e.nodeType&&e,Re=Le&&"object"==typeof r&&r&&!r.nodeType&&r,Pe=Re&&Re.exports===Le,Fe=Pe&&Ne.process,Me=function(){try{return Fe&&Fe.binding&&Fe.binding("util")}catch(t){}}(),qe=Me&&Me.isArrayBuffer,Be=Me&&Me.isDate,He=Me&&Me.isMap,Ue=Me&&Me.isRegExp,We=Me&&Me.isSet,ze=Me&&Me.isTypedArray;function Ve(t,e){return t.set(e[0],e[1]),t}function Xe(t,e){return t.add(e),t}function Ke(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}function Je(t,e,n,r){for(var i=-1,o=null==t?0:t.length;++i-1}function en(t,e,n){for(var r=-1,i=null==t?0:t.length;++r-1;);return n}function Tn(t,e){for(var n=t.length;n--&&fn(e,t[n],0)>-1;);return n}var kn=mn({"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss","Ā":"A","Ă":"A","Ą":"A","ā":"a","ă":"a","ą":"a","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","ć":"c","ĉ":"c","ċ":"c","č":"c","Ď":"D","Đ":"D","ď":"d","đ":"d","Ē":"E","Ĕ":"E","Ė":"E","Ę":"E","Ě":"E","ē":"e","ĕ":"e","ė":"e","ę":"e","ě":"e","Ĝ":"G","Ğ":"G","Ġ":"G","Ģ":"G","ĝ":"g","ğ":"g","ġ":"g","ģ":"g","Ĥ":"H","Ħ":"H","ĥ":"h","ħ":"h","Ĩ":"I","Ī":"I","Ĭ":"I","Į":"I","İ":"I","ĩ":"i","ī":"i","ĭ":"i","į":"i","ı":"i","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","ĸ":"k","Ĺ":"L","Ļ":"L","Ľ":"L","Ŀ":"L","Ł":"L","ĺ":"l","ļ":"l","ľ":"l","ŀ":"l","ł":"l","Ń":"N","Ņ":"N","Ň":"N","Ŋ":"N","ń":"n","ņ":"n","ň":"n","ŋ":"n","Ō":"O","Ŏ":"O","Ő":"O","ō":"o","ŏ":"o","ő":"o","Ŕ":"R","Ŗ":"R","Ř":"R","ŕ":"r","ŗ":"r","ř":"r","Ś":"S","Ŝ":"S","Ş":"S","Š":"S","ś":"s","ŝ":"s","ş":"s","š":"s","Ţ":"T","Ť":"T","Ŧ":"T","ţ":"t","ť":"t","ŧ":"t","Ũ":"U","Ū":"U","Ŭ":"U","Ů":"U","Ű":"U","Ų":"U","ũ":"u","ū":"u","ŭ":"u","ů":"u","ű":"u","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","Ż":"Z","Ž":"Z","ź":"z","ż":"z","ž":"z","IJ":"IJ","ij":"ij","Œ":"Oe","œ":"oe","ʼn":"'n","ſ":"s"}),$n=mn({"&":"&","<":"<",">":">",'"':""","'":"'"});function An(t){return"\\"+Ee[t]}function Sn(t){return Ce.test(t)}function En(t){var e=-1,n=Array(t.size);return t.forEach(function(t,r){n[++e]=[r,t]}),n}function On(t,e){return function(n){return t(e(n))}}function jn(t,e){for(var n=-1,r=t.length,i=0,o=[];++n",""":'"',"'":"'"});var Pn=function t(e){var n,r=(e=null==e?Ie:Pn.defaults(Ie.Object(),e,Pn.pick(Ie,ke))).Array,i=e.Date,Zt=e.Error,te=e.Function,ee=e.Math,ne=e.Object,re=e.RegExp,ie=e.String,oe=e.TypeError,ae=r.prototype,se=te.prototype,ue=ne.prototype,ce=e["__core-js_shared__"],le=se.toString,fe=ue.hasOwnProperty,pe=0,de=(n=/[^.]+$/.exec(ce&&ce.keys&&ce.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",he=ue.toString,ve=le.call(ne),me=Ie._,ge=re("^"+le.call(fe).replace(Dt,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),ye=Pe?e.Buffer:o,we=e.Symbol,Ce=e.Uint8Array,Ee=ye?ye.allocUnsafe:o,Ne=On(ne.getPrototypeOf,ne),De=ne.create,Le=ue.propertyIsEnumerable,Re=ae.splice,Fe=we?we.isConcatSpreadable:o,Me=we?we.iterator:o,un=we?we.toStringTag:o,mn=function(){try{var t=Ho(ne,"defineProperty");return t({},"",{}),t}catch(t){}}(),Fn=e.clearTimeout!==Ie.clearTimeout&&e.clearTimeout,Mn=i&&i.now!==Ie.Date.now&&i.now,qn=e.setTimeout!==Ie.setTimeout&&e.setTimeout,Bn=ee.ceil,Hn=ee.floor,Un=ne.getOwnPropertySymbols,Wn=ye?ye.isBuffer:o,zn=e.isFinite,Vn=ae.join,Xn=On(ne.keys,ne),Kn=ee.max,Jn=ee.min,Gn=i.now,Qn=e.parseInt,Yn=ee.random,Zn=ae.reverse,tr=Ho(e,"DataView"),er=Ho(e,"Map"),nr=Ho(e,"Promise"),rr=Ho(e,"Set"),ir=Ho(e,"WeakMap"),or=Ho(ne,"create"),ar=ir&&new ir,sr={},ur=da(tr),cr=da(er),lr=da(nr),fr=da(rr),pr=da(ir),dr=we?we.prototype:o,hr=dr?dr.valueOf:o,vr=dr?dr.toString:o;function mr(t){if(Os(t)&&!bs(t)&&!(t instanceof _r)){if(t instanceof br)return t;if(fe.call(t,"__wrapped__"))return ha(t)}return new br(t)}var gr=function(){function t(){}return function(e){if(!Es(e))return{};if(De)return De(e);t.prototype=e;var n=new t;return t.prototype=o,n}}();function yr(){}function br(t,e){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!e,this.__index__=0,this.__values__=o}function _r(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=P,this.__views__=[]}function wr(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e=e?t:e)),t}function Fr(t,e,n,r,i,a){var s,u=e&p,c=e&d,l=e&h;if(n&&(s=i?n(t,r,i,a):n(t)),s!==o)return s;if(!Es(t))return t;var f=bs(t);if(f){if(s=function(t){var e=t.length,n=t.constructor(e);return e&&"string"==typeof t[0]&&fe.call(t,"index")&&(n.index=t.index,n.input=t.input),n}(t),!u)return oo(t,s)}else{var v=zo(t),m=v==K||v==J;if(Cs(t))return Zi(t,u);if(v==Z||v==B||m&&!i){if(s=c||m?{}:Xo(t),!u)return c?function(t,e){return ao(t,Wo(t),e)}(t,function(t,e){return t&&ao(e,su(e),t)}(s,t)):function(t,e){return ao(t,Uo(t),e)}(t,Ir(s,t))}else{if(!Se[v])return i?t:{};s=function(t,e,n,r){var i,o,a,s=t.constructor;switch(e){case ut:return to(t);case W:case z:return new s(+t);case ct:return function(t,e){var n=e?to(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.byteLength)}(t,r);case lt:case ft:case pt:case dt:case ht:case vt:case mt:case gt:case yt:return eo(t,r);case G:return function(t,e,n){return on(e?n(En(t),p):En(t),Ve,new t.constructor)}(t,r,n);case Q:case rt:return new s(t);case et:return(a=new(o=t).constructor(o.source,Wt.exec(o))).lastIndex=o.lastIndex,a;case nt:return function(t,e,n){return on(e?n(Nn(t),p):Nn(t),Xe,new t.constructor)}(t,r,n);case it:return i=t,hr?ne(hr.call(i)):{}}}(t,v,Fr,u)}}a||(a=new kr);var g=a.get(t);if(g)return g;a.set(t,s);var y=f?o:(l?c?Lo:Io:c?su:au)(t);return Ge(y||t,function(r,i){y&&(r=t[i=r]),jr(s,i,Fr(r,e,n,i,t,a))}),s}function Mr(t,e,n){var r=n.length;if(null==t)return!r;for(t=ne(t);r--;){var i=n[r],a=e[i],s=t[i];if(s===o&&!(i in t)||!a(s))return!1}return!0}function qr(t,e,n){if("function"!=typeof t)throw new oe(u);return aa(function(){t.apply(o,n)},e)}function Br(t,e,n,r){var i=-1,o=tn,s=!0,u=t.length,c=[],l=e.length;if(!u)return c;n&&(e=nn(e,_n(n))),r?(o=en,s=!1):e.length>=a&&(o=xn,s=!1,e=new Tr(e));t:for(;++i-1},xr.prototype.set=function(t,e){var n=this.__data__,r=Nr(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this},Cr.prototype.clear=function(){this.size=0,this.__data__={hash:new wr,map:new(er||xr),string:new wr}},Cr.prototype.delete=function(t){var e=qo(this,t).delete(t);return this.size-=e?1:0,e},Cr.prototype.get=function(t){return qo(this,t).get(t)},Cr.prototype.has=function(t){return qo(this,t).has(t)},Cr.prototype.set=function(t,e){var n=qo(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this},Tr.prototype.add=Tr.prototype.push=function(t){return this.__data__.set(t,c),this},Tr.prototype.has=function(t){return this.__data__.has(t)},kr.prototype.clear=function(){this.__data__=new xr,this.size=0},kr.prototype.delete=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n},kr.prototype.get=function(t){return this.__data__.get(t)},kr.prototype.has=function(t){return this.__data__.has(t)},kr.prototype.set=function(t,e){var n=this.__data__;if(n instanceof xr){var r=n.__data__;if(!er||r.length0&&n(s)?e>1?Xr(s,e-1,n,r,i):rn(i,s):r||(i[i.length]=s)}return i}var Kr=lo(),Jr=lo(!0);function Gr(t,e){return t&&Kr(t,e,au)}function Qr(t,e){return t&&Jr(t,e,au)}function Yr(t,e){return Ze(e,function(e){return $s(t[e])})}function Zr(t,e){for(var n=0,r=(e=Ji(e,t)).length;null!=t&&ne}function ri(t,e){return null!=t&&fe.call(t,e)}function ii(t,e){return null!=t&&e in ne(t)}function oi(t,e,n){for(var i=n?en:tn,a=t[0].length,s=t.length,u=s,c=r(s),l=1/0,f=[];u--;){var p=t[u];u&&e&&(p=nn(p,_n(e))),l=Jn(p.length,l),c[u]=!n&&(e||a>=120&&p.length>=120)?new Tr(u&&p):o}p=t[0];var d=-1,h=c[0];t:for(;++d=s)return u;var c=n[r];return u*("desc"==c?-1:1)}}return t.index-e.index}(t,e,n)})}function wi(t,e,n){for(var r=-1,i=e.length,o={};++r-1;)s!==t&&Re.call(s,u,1),Re.call(t,u,1);return t}function Ci(t,e){for(var n=t?e.length:0,r=n-1;n--;){var i=e[n];if(n==r||i!==o){var o=i;Jo(i)?Re.call(t,i,1):Bi(t,i)}}return t}function Ti(t,e){return t+Hn(Yn()*(e-t+1))}function ki(t,e){var n="";if(!t||e<1||e>I)return n;do{e%2&&(n+=t),(e=Hn(e/2))&&(t+=t)}while(e);return n}function $i(t,e){return sa(ra(t,e,Nu),t+"")}function Ai(t){return Ar(vu(t))}function Si(t,e){var n=vu(t);return la(n,Pr(e,0,n.length))}function Ei(t,e,n,r){if(!Es(t))return t;for(var i=-1,a=(e=Ji(e,t)).length,s=a-1,u=t;null!=u&&++io?0:o+e),(n=n>o?o:n)<0&&(n+=o),o=e>n?0:n-e>>>0,e>>>=0;for(var a=r(o);++i>>1,a=t[o];null!==a&&!Ps(a)&&(n?a<=e:a=a){var l=e?null:$o(t);if(l)return Nn(l);s=!1,i=xn,c=new Tr}else c=e?[]:u;t:for(;++r=r?t:Di(t,e,n)}var Yi=Fn||function(t){return Ie.clearTimeout(t)};function Zi(t,e){if(e)return t.slice();var n=t.length,r=Ee?Ee(n):new t.constructor(n);return t.copy(r),r}function to(t){var e=new t.constructor(t.byteLength);return new Ce(e).set(new Ce(t)),e}function eo(t,e){var n=e?to(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}function no(t,e){if(t!==e){var n=t!==o,r=null===t,i=t==t,a=Ps(t),s=e!==o,u=null===e,c=e==e,l=Ps(e);if(!u&&!l&&!a&&t>e||a&&s&&c&&!u&&!l||r&&s&&c||!n&&c||!i)return 1;if(!r&&!a&&!l&&t1?n[i-1]:o,s=i>2?n[2]:o;for(a=t.length>3&&"function"==typeof a?(i--,a):o,s&&Go(n[0],n[1],s)&&(a=i<3?o:a,i=1),e=ne(e);++r-1?i[a?e[s]:s]:o}}function mo(t){return Do(function(e){var n=e.length,r=n,i=br.prototype.thru;for(t&&e.reverse();r--;){var a=e[r];if("function"!=typeof a)throw new oe(u);if(i&&!s&&"wrapper"==Po(a))var s=new br([],!0)}for(r=s?r:n;++r1&&_.reverse(),p&&lu))return!1;var l=a.get(t);if(l&&a.get(e))return l==e;var f=-1,p=!0,d=n&m?new Tr:o;for(a.set(t,e),a.set(e,t);++f-1&&t%1==0&&t1?"& ":"")+e[r],e=e.join(n>2?", ":" "),t.replace(Ft,"{\n/* [wrapped with "+e+"] */\n")}(r,function(t,e){return Ge(q,function(n){var r="_."+n[0];e&n[1]&&!tn(t,r)&&t.push(r)}),t.sort()}(function(t){var e=t.match(Mt);return e?e[1].split(qt):[]}(r),n)))}function ca(t){var e=0,n=0;return function(){var r=Gn(),i=O-(r-n);if(n=r,i>0){if(++e>=E)return arguments[0]}else e=0;return t.apply(o,arguments)}}function la(t,e){var n=-1,r=t.length,i=r-1;for(e=e===o?r:e;++n1?t[e-1]:o;return Ia(t,n="function"==typeof n?(t.pop(),n):o)});function Ba(t){var e=mr(t);return e.__chain__=!0,e}function Ha(t,e){return e(t)}var Ua=Do(function(t){var e=t.length,n=e?t[0]:0,r=this.__wrapped__,i=function(e){return Rr(e,t)};return!(e>1||this.__actions__.length)&&r instanceof _r&&Jo(n)?((r=r.slice(n,+n+(e?1:0))).__actions__.push({func:Ha,args:[i],thisArg:o}),new br(r,this.__chain__).thru(function(t){return e&&!t.length&&t.push(o),t})):this.thru(i)});var Wa=so(function(t,e,n){fe.call(t,n)?++t[n]:Lr(t,n,1)});var za=vo(ya),Va=vo(ba);function Xa(t,e){return(bs(t)?Ge:Hr)(t,Mo(e,3))}function Ka(t,e){return(bs(t)?Qe:Ur)(t,Mo(e,3))}var Ja=so(function(t,e,n){fe.call(t,n)?t[n].push(e):Lr(t,n,[e])});var Ga=$i(function(t,e,n){var i=-1,o="function"==typeof e,a=ws(t)?r(t.length):[];return Hr(t,function(t){a[++i]=o?Ke(e,t,n):ai(t,e,n)}),a}),Qa=so(function(t,e,n){Lr(t,n,e)});function Ya(t,e){return(bs(t)?nn:vi)(t,Mo(e,3))}var Za=so(function(t,e,n){t[n?0:1].push(e)},function(){return[[],[]]});var ts=$i(function(t,e){if(null==t)return[];var n=e.length;return n>1&&Go(t,e[0],e[1])?e=[]:n>2&&Go(e[0],e[1],e[2])&&(e=[e[0]]),_i(t,Xr(e,1),[])}),es=Mn||function(){return Ie.Date.now()};function ns(t,e,n){return e=n?o:e,e=t&&null==e?t.length:e,So(t,T,o,o,o,o,e)}function rs(t,e){var n;if("function"!=typeof e)throw new oe(u);return t=Us(t),function(){return--t>0&&(n=e.apply(this,arguments)),t<=1&&(e=o),n}}var is=$i(function(t,e,n){var r=g;if(n.length){var i=jn(n,Fo(is));r|=x}return So(t,r,e,n,i)}),os=$i(function(t,e,n){var r=g|y;if(n.length){var i=jn(n,Fo(os));r|=x}return So(e,r,t,n,i)});function as(t,e,n){var r,i,a,s,c,l,f=0,p=!1,d=!1,h=!0;if("function"!=typeof t)throw new oe(u);function v(e){var n=r,a=i;return r=i=o,f=e,s=t.apply(a,n)}function m(t){var n=t-l;return l===o||n>=e||n<0||d&&t-f>=a}function g(){var t=es();if(m(t))return y(t);c=aa(g,function(t){var n=e-(t-l);return d?Jn(n,a-(t-f)):n}(t))}function y(t){return c=o,h&&r?v(t):(r=i=o,s)}function b(){var t=es(),n=m(t);if(r=arguments,i=this,l=t,n){if(c===o)return function(t){return f=t,c=aa(g,e),p?v(t):s}(l);if(d)return c=aa(g,e),v(l)}return c===o&&(c=aa(g,e)),s}return e=zs(e)||0,Es(n)&&(p=!!n.leading,a=(d="maxWait"in n)?Kn(zs(n.maxWait)||0,e):a,h="trailing"in n?!!n.trailing:h),b.cancel=function(){c!==o&&Yi(c),f=0,r=l=i=c=o},b.flush=function(){return c===o?s:y(es())},b}var ss=$i(function(t,e){return qr(t,1,e)}),us=$i(function(t,e,n){return qr(t,zs(e)||0,n)});function cs(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new oe(u);var n=function(){var r=arguments,i=e?e.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var a=t.apply(this,r);return n.cache=o.set(i,a)||o,a};return n.cache=new(cs.Cache||Cr),n}function ls(t){if("function"!=typeof t)throw new oe(u);return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}cs.Cache=Cr;var fs=Gi(function(t,e){var n=(e=1==e.length&&bs(e[0])?nn(e[0],_n(Mo())):nn(Xr(e,1),_n(Mo()))).length;return $i(function(r){for(var i=-1,o=Jn(r.length,n);++i=e}),ys=si(function(){return arguments}())?si:function(t){return Os(t)&&fe.call(t,"callee")&&!Le.call(t,"callee")},bs=r.isArray,_s=qe?_n(qe):function(t){return Os(t)&&ei(t)==ut};function ws(t){return null!=t&&Ss(t.length)&&!$s(t)}function xs(t){return Os(t)&&ws(t)}var Cs=Wn||zu,Ts=Be?_n(Be):function(t){return Os(t)&&ei(t)==z};function ks(t){if(!Os(t))return!1;var e=ei(t);return e==X||e==V||"string"==typeof t.message&&"string"==typeof t.name&&!Ds(t)}function $s(t){if(!Es(t))return!1;var e=ei(t);return e==K||e==J||e==U||e==tt}function As(t){return"number"==typeof t&&t==Us(t)}function Ss(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=I}function Es(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function Os(t){return null!=t&&"object"==typeof t}var js=He?_n(He):function(t){return Os(t)&&zo(t)==G};function Ns(t){return"number"==typeof t||Os(t)&&ei(t)==Q}function Ds(t){if(!Os(t)||ei(t)!=Z)return!1;var e=Ne(t);if(null===e)return!0;var n=fe.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&le.call(n)==ve}var Is=Ue?_n(Ue):function(t){return Os(t)&&ei(t)==et};var Ls=We?_n(We):function(t){return Os(t)&&zo(t)==nt};function Rs(t){return"string"==typeof t||!bs(t)&&Os(t)&&ei(t)==rt}function Ps(t){return"symbol"==typeof t||Os(t)&&ei(t)==it}var Fs=ze?_n(ze):function(t){return Os(t)&&Ss(t.length)&&!!Ae[ei(t)]};var Ms=Co(hi),qs=Co(function(t,e){return t<=e});function Bs(t){if(!t)return[];if(ws(t))return Rs(t)?Ln(t):oo(t);if(Me&&t[Me])return function(t){for(var e,n=[];!(e=t.next()).done;)n.push(e.value);return n}(t[Me]());var e=zo(t);return(e==G?En:e==nt?Nn:vu)(t)}function Hs(t){return t?(t=zs(t))===D||t===-D?(t<0?-1:1)*L:t==t?t:0:0===t?t:0}function Us(t){var e=Hs(t),n=e%1;return e==e?n?e-n:e:0}function Ws(t){return t?Pr(Us(t),0,P):0}function zs(t){if("number"==typeof t)return t;if(Ps(t))return R;if(Es(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=Es(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(Lt,"");var n=Vt.test(t);return n||Kt.test(t)?je(t.slice(2),n?2:8):zt.test(t)?R:+t}function Vs(t){return ao(t,su(t))}function Xs(t){return null==t?"":Mi(t)}var Ks=uo(function(t,e){if(ta(e)||ws(e))ao(e,au(e),t);else for(var n in e)fe.call(e,n)&&jr(t,n,e[n])}),Js=uo(function(t,e){ao(e,su(e),t)}),Gs=uo(function(t,e,n,r){ao(e,su(e),t,r)}),Qs=uo(function(t,e,n,r){ao(e,au(e),t,r)}),Ys=Do(Rr);var Zs=$i(function(t){return t.push(o,Eo),Ke(Gs,o,t)}),tu=$i(function(t){return t.push(o,Oo),Ke(cu,o,t)});function eu(t,e,n){var r=null==t?o:Zr(t,e);return r===o?n:r}function nu(t,e){return null!=t&&Vo(t,e,ii)}var ru=yo(function(t,e,n){t[e]=n},Eu(Nu)),iu=yo(function(t,e,n){fe.call(t,e)?t[e].push(n):t[e]=[n]},Mo),ou=$i(ai);function au(t){return ws(t)?$r(t):pi(t)}function su(t){return ws(t)?$r(t,!0):di(t)}var uu=uo(function(t,e,n){yi(t,e,n)}),cu=uo(function(t,e,n,r){yi(t,e,n,r)}),lu=Do(function(t,e){var n={};if(null==t)return n;var r=!1;e=nn(e,function(e){return e=Ji(e,t),r||(r=e.length>1),e}),ao(t,Lo(t),n),r&&(n=Fr(n,p|d|h,jo));for(var i=e.length;i--;)Bi(n,e[i]);return n});var fu=Do(function(t,e){return null==t?{}:function(t,e){return wi(t,e,function(e,n){return nu(t,n)})}(t,e)});function pu(t,e){if(null==t)return{};var n=nn(Lo(t),function(t){return[t]});return e=Mo(e),wi(t,n,function(t,n){return e(t,n[0])})}var du=Ao(au),hu=Ao(su);function vu(t){return null==t?[]:wn(t,au(t))}var mu=po(function(t,e,n){return e=e.toLowerCase(),t+(n?gu(e):e)});function gu(t){return ku(Xs(t).toLowerCase())}function yu(t){return(t=Xs(t))&&t.replace(Gt,kn).replace(_e,"")}var bu=po(function(t,e,n){return t+(n?"-":"")+e.toLowerCase()}),_u=po(function(t,e,n){return t+(n?" ":"")+e.toLowerCase()}),wu=fo("toLowerCase");var xu=po(function(t,e,n){return t+(n?"_":"")+e.toLowerCase()});var Cu=po(function(t,e,n){return t+(n?" ":"")+ku(e)});var Tu=po(function(t,e,n){return t+(n?" ":"")+e.toUpperCase()}),ku=fo("toUpperCase");function $u(t,e,n){return t=Xs(t),(e=n?o:e)===o?function(t){return Te.test(t)}(t)?function(t){return t.match(xe)||[]}(t):function(t){return t.match(Bt)||[]}(t):t.match(e)||[]}var Au=$i(function(t,e){try{return Ke(t,o,e)}catch(t){return ks(t)?t:new Zt(t)}}),Su=Do(function(t,e){return Ge(e,function(e){e=pa(e),Lr(t,e,is(t[e],t))}),t});function Eu(t){return function(){return t}}var Ou=mo(),ju=mo(!0);function Nu(t){return t}function Du(t){return fi("function"==typeof t?t:Fr(t,p))}var Iu=$i(function(t,e){return function(n){return ai(n,t,e)}}),Lu=$i(function(t,e){return function(n){return ai(t,n,e)}});function Ru(t,e,n){var r=au(e),i=Yr(e,r);null!=n||Es(e)&&(i.length||!r.length)||(n=e,e=t,t=this,i=Yr(e,au(e)));var o=!(Es(n)&&"chain"in n&&!n.chain),a=$s(t);return Ge(i,function(n){var r=e[n];t[n]=r,a&&(t.prototype[n]=function(){var e=this.__chain__;if(o||e){var n=t(this.__wrapped__);return(n.__actions__=oo(this.__actions__)).push({func:r,args:arguments,thisArg:t}),n.__chain__=e,n}return r.apply(t,rn([this.value()],arguments))})}),t}function Pu(){}var Fu=_o(nn),Mu=_o(Ye),qu=_o(sn);function Bu(t){return Qo(t)?vn(pa(t)):function(t){return function(e){return Zr(e,t)}}(t)}var Hu=xo(),Uu=xo(!0);function Wu(){return[]}function zu(){return!1}var Vu=bo(function(t,e){return t+e},0),Xu=ko("ceil"),Ku=bo(function(t,e){return t/e},1),Ju=ko("floor");var Gu,Qu=bo(function(t,e){return t*e},1),Yu=ko("round"),Zu=bo(function(t,e){return t-e},0);return mr.after=function(t,e){if("function"!=typeof e)throw new oe(u);return t=Us(t),function(){if(--t<1)return e.apply(this,arguments)}},mr.ary=ns,mr.assign=Ks,mr.assignIn=Js,mr.assignInWith=Gs,mr.assignWith=Qs,mr.at=Ys,mr.before=rs,mr.bind=is,mr.bindAll=Su,mr.bindKey=os,mr.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return bs(t)?t:[t]},mr.chain=Ba,mr.chunk=function(t,e,n){e=(n?Go(t,e,n):e===o)?1:Kn(Us(e),0);var i=null==t?0:t.length;if(!i||e<1)return[];for(var a=0,s=0,u=r(Bn(i/e));ai?0:i+n),(r=r===o||r>i?i:Us(r))<0&&(r+=i),r=n>r?0:Ws(r);n>>0)?(t=Xs(t))&&("string"==typeof e||null!=e&&!Is(e))&&!(e=Mi(e))&&Sn(t)?Qi(Ln(t),0,n):t.split(e,n):[]},mr.spread=function(t,e){if("function"!=typeof t)throw new oe(u);return e=null==e?0:Kn(Us(e),0),$i(function(n){var r=n[e],i=Qi(n,0,e);return r&&rn(i,r),Ke(t,this,i)})},mr.tail=function(t){var e=null==t?0:t.length;return e?Di(t,1,e):[]},mr.take=function(t,e,n){return t&&t.length?Di(t,0,(e=n||e===o?1:Us(e))<0?0:e):[]},mr.takeRight=function(t,e,n){var r=null==t?0:t.length;return r?Di(t,(e=r-(e=n||e===o?1:Us(e)))<0?0:e,r):[]},mr.takeRightWhile=function(t,e){return t&&t.length?Ui(t,Mo(e,3),!1,!0):[]},mr.takeWhile=function(t,e){return t&&t.length?Ui(t,Mo(e,3)):[]},mr.tap=function(t,e){return e(t),t},mr.throttle=function(t,e,n){var r=!0,i=!0;if("function"!=typeof t)throw new oe(u);return Es(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),as(t,e,{leading:r,maxWait:e,trailing:i})},mr.thru=Ha,mr.toArray=Bs,mr.toPairs=du,mr.toPairsIn=hu,mr.toPath=function(t){return bs(t)?nn(t,pa):Ps(t)?[t]:oo(fa(Xs(t)))},mr.toPlainObject=Vs,mr.transform=function(t,e,n){var r=bs(t),i=r||Cs(t)||Fs(t);if(e=Mo(e,4),null==n){var o=t&&t.constructor;n=i?r?new o:[]:Es(t)&&$s(o)?gr(Ne(t)):{}}return(i?Ge:Gr)(t,function(t,r,i){return e(n,t,r,i)}),n},mr.unary=function(t){return ns(t,1)},mr.union=Oa,mr.unionBy=ja,mr.unionWith=Na,mr.uniq=function(t){return t&&t.length?qi(t):[]},mr.uniqBy=function(t,e){return t&&t.length?qi(t,Mo(e,2)):[]},mr.uniqWith=function(t,e){return e="function"==typeof e?e:o,t&&t.length?qi(t,o,e):[]},mr.unset=function(t,e){return null==t||Bi(t,e)},mr.unzip=Da,mr.unzipWith=Ia,mr.update=function(t,e,n){return null==t?t:Hi(t,e,Ki(n))},mr.updateWith=function(t,e,n,r){return r="function"==typeof r?r:o,null==t?t:Hi(t,e,Ki(n),r)},mr.values=vu,mr.valuesIn=function(t){return null==t?[]:wn(t,su(t))},mr.without=La,mr.words=$u,mr.wrap=function(t,e){return ps(Ki(e),t)},mr.xor=Ra,mr.xorBy=Pa,mr.xorWith=Fa,mr.zip=Ma,mr.zipObject=function(t,e){return Vi(t||[],e||[],jr)},mr.zipObjectDeep=function(t,e){return Vi(t||[],e||[],Ei)},mr.zipWith=qa,mr.entries=du,mr.entriesIn=hu,mr.extend=Js,mr.extendWith=Gs,Ru(mr,mr),mr.add=Vu,mr.attempt=Au,mr.camelCase=mu,mr.capitalize=gu,mr.ceil=Xu,mr.clamp=function(t,e,n){return n===o&&(n=e,e=o),n!==o&&(n=(n=zs(n))==n?n:0),e!==o&&(e=(e=zs(e))==e?e:0),Pr(zs(t),e,n)},mr.clone=function(t){return Fr(t,h)},mr.cloneDeep=function(t){return Fr(t,p|h)},mr.cloneDeepWith=function(t,e){return Fr(t,p|h,e="function"==typeof e?e:o)},mr.cloneWith=function(t,e){return Fr(t,h,e="function"==typeof e?e:o)},mr.conformsTo=function(t,e){return null==e||Mr(t,e,au(e))},mr.deburr=yu,mr.defaultTo=function(t,e){return null==t||t!=t?e:t},mr.divide=Ku,mr.endsWith=function(t,e,n){t=Xs(t),e=Mi(e);var r=t.length,i=n=n===o?r:Pr(Us(n),0,r);return(n-=e.length)>=0&&t.slice(n,i)==e},mr.eq=vs,mr.escape=function(t){return(t=Xs(t))&&kt.test(t)?t.replace(Ct,$n):t},mr.escapeRegExp=function(t){return(t=Xs(t))&&It.test(t)?t.replace(Dt,"\\$&"):t},mr.every=function(t,e,n){var r=bs(t)?Ye:Wr;return n&&Go(t,e,n)&&(e=o),r(t,Mo(e,3))},mr.find=za,mr.findIndex=ya,mr.findKey=function(t,e){return cn(t,Mo(e,3),Gr)},mr.findLast=Va,mr.findLastIndex=ba,mr.findLastKey=function(t,e){return cn(t,Mo(e,3),Qr)},mr.floor=Ju,mr.forEach=Xa,mr.forEachRight=Ka,mr.forIn=function(t,e){return null==t?t:Kr(t,Mo(e,3),su)},mr.forInRight=function(t,e){return null==t?t:Jr(t,Mo(e,3),su)},mr.forOwn=function(t,e){return t&&Gr(t,Mo(e,3))},mr.forOwnRight=function(t,e){return t&&Qr(t,Mo(e,3))},mr.get=eu,mr.gt=ms,mr.gte=gs,mr.has=function(t,e){return null!=t&&Vo(t,e,ri)},mr.hasIn=nu,mr.head=wa,mr.identity=Nu,mr.includes=function(t,e,n,r){t=ws(t)?t:vu(t),n=n&&!r?Us(n):0;var i=t.length;return n<0&&(n=Kn(i+n,0)),Rs(t)?n<=i&&t.indexOf(e,n)>-1:!!i&&fn(t,e,n)>-1},mr.indexOf=function(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var i=null==n?0:Us(n);return i<0&&(i=Kn(r+i,0)),fn(t,e,i)},mr.inRange=function(t,e,n){return e=Hs(e),n===o?(n=e,e=0):n=Hs(n),function(t,e,n){return t>=Jn(e,n)&&t=-I&&t<=I},mr.isSet=Ls,mr.isString=Rs,mr.isSymbol=Ps,mr.isTypedArray=Fs,mr.isUndefined=function(t){return t===o},mr.isWeakMap=function(t){return Os(t)&&zo(t)==at},mr.isWeakSet=function(t){return Os(t)&&ei(t)==st},mr.join=function(t,e){return null==t?"":Vn.call(t,e)},mr.kebabCase=bu,mr.last=ka,mr.lastIndexOf=function(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var i=r;return n!==o&&(i=(i=Us(n))<0?Kn(r+i,0):Jn(i,r-1)),e==e?function(t,e,n){for(var r=n+1;r--;)if(t[r]===e)return r;return r}(t,e,i):ln(t,dn,i,!0)},mr.lowerCase=_u,mr.lowerFirst=wu,mr.lt=Ms,mr.lte=qs,mr.max=function(t){return t&&t.length?zr(t,Nu,ni):o},mr.maxBy=function(t,e){return t&&t.length?zr(t,Mo(e,2),ni):o},mr.mean=function(t){return hn(t,Nu)},mr.meanBy=function(t,e){return hn(t,Mo(e,2))},mr.min=function(t){return t&&t.length?zr(t,Nu,hi):o},mr.minBy=function(t,e){return t&&t.length?zr(t,Mo(e,2),hi):o},mr.stubArray=Wu,mr.stubFalse=zu,mr.stubObject=function(){return{}},mr.stubString=function(){return""},mr.stubTrue=function(){return!0},mr.multiply=Qu,mr.nth=function(t,e){return t&&t.length?bi(t,Us(e)):o},mr.noConflict=function(){return Ie._===this&&(Ie._=me),this},mr.noop=Pu,mr.now=es,mr.pad=function(t,e,n){t=Xs(t);var r=(e=Us(e))?In(t):0;if(!e||r>=e)return t;var i=(e-r)/2;return wo(Hn(i),n)+t+wo(Bn(i),n)},mr.padEnd=function(t,e,n){t=Xs(t);var r=(e=Us(e))?In(t):0;return e&&re){var r=t;t=e,e=r}if(n||t%1||e%1){var i=Yn();return Jn(t+i*(e-t+Oe("1e-"+((i+"").length-1))),e)}return Ti(t,e)},mr.reduce=function(t,e,n){var r=bs(t)?on:gn,i=arguments.length<3;return r(t,Mo(e,4),n,i,Hr)},mr.reduceRight=function(t,e,n){var r=bs(t)?an:gn,i=arguments.length<3;return r(t,Mo(e,4),n,i,Ur)},mr.repeat=function(t,e,n){return e=(n?Go(t,e,n):e===o)?1:Us(e),ki(Xs(t),e)},mr.replace=function(){var t=arguments,e=Xs(t[0]);return t.length<3?e:e.replace(t[1],t[2])},mr.result=function(t,e,n){var r=-1,i=(e=Ji(e,t)).length;for(i||(i=1,t=o);++rI)return[];var n=P,r=Jn(t,P);e=Mo(e),t-=P;for(var i=bn(r,e);++n=a)return t;var u=n-In(r);if(u<1)return r;var c=s?Qi(s,0,u).join(""):t.slice(0,u);if(i===o)return c+r;if(s&&(u+=c.length-u),Is(i)){if(t.slice(u).search(i)){var l,f=c;for(i.global||(i=re(i.source,Xs(Wt.exec(i))+"g")),i.lastIndex=0;l=i.exec(f);)var p=l.index;c=c.slice(0,p===o?u:p)}}else if(t.indexOf(Mi(i),u)!=u){var d=c.lastIndexOf(i);d>-1&&(c=c.slice(0,d))}return c+r},mr.unescape=function(t){return(t=Xs(t))&&Tt.test(t)?t.replace(xt,Rn):t},mr.uniqueId=function(t){var e=++pe;return Xs(t)+e},mr.upperCase=Tu,mr.upperFirst=ku,mr.each=Xa,mr.eachRight=Ka,mr.first=wa,Ru(mr,(Gu={},Gr(mr,function(t,e){fe.call(mr.prototype,e)||(Gu[e]=t)}),Gu),{chain:!1}),mr.VERSION="4.17.4",Ge(["bind","bindKey","curry","curryRight","partial","partialRight"],function(t){mr[t].placeholder=mr}),Ge(["drop","take"],function(t,e){_r.prototype[t]=function(n){n=n===o?1:Kn(Us(n),0);var r=this.__filtered__&&!e?new _r(this):this.clone();return r.__filtered__?r.__takeCount__=Jn(n,r.__takeCount__):r.__views__.push({size:Jn(n,P),type:t+(r.__dir__<0?"Right":"")}),r},_r.prototype[t+"Right"]=function(e){return this.reverse()[t](e).reverse()}}),Ge(["filter","map","takeWhile"],function(t,e){var n=e+1,r=n==j||3==n;_r.prototype[t]=function(t){var e=this.clone();return e.__iteratees__.push({iteratee:Mo(t,3),type:n}),e.__filtered__=e.__filtered__||r,e}}),Ge(["head","last"],function(t,e){var n="take"+(e?"Right":"");_r.prototype[t]=function(){return this[n](1).value()[0]}}),Ge(["initial","tail"],function(t,e){var n="drop"+(e?"":"Right");_r.prototype[t]=function(){return this.__filtered__?new _r(this):this[n](1)}}),_r.prototype.compact=function(){return this.filter(Nu)},_r.prototype.find=function(t){return this.filter(t).head()},_r.prototype.findLast=function(t){return this.reverse().find(t)},_r.prototype.invokeMap=$i(function(t,e){return"function"==typeof t?new _r(this):this.map(function(n){return ai(n,t,e)})}),_r.prototype.reject=function(t){return this.filter(ls(Mo(t)))},_r.prototype.slice=function(t,e){t=Us(t);var n=this;return n.__filtered__&&(t>0||e<0)?new _r(n):(t<0?n=n.takeRight(-t):t&&(n=n.drop(t)),e!==o&&(n=(e=Us(e))<0?n.dropRight(-e):n.take(e-t)),n)},_r.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},_r.prototype.toArray=function(){return this.take(P)},Gr(_r.prototype,function(t,e){var n=/^(?:filter|find|map|reject)|While$/.test(e),r=/^(?:head|last)$/.test(e),i=mr[r?"take"+("last"==e?"Right":""):e],a=r||/^find/.test(e);i&&(mr.prototype[e]=function(){var e=this.__wrapped__,s=r?[1]:arguments,u=e instanceof _r,c=s[0],l=u||bs(e),f=function(t){var e=i.apply(mr,rn([t],s));return r&&p?e[0]:e};l&&n&&"function"==typeof c&&1!=c.length&&(u=l=!1);var p=this.__chain__,d=!!this.__actions__.length,h=a&&!p,v=u&&!d;if(!a&&l){e=v?e:new _r(this);var m=t.apply(e,s);return m.__actions__.push({func:Ha,args:[f],thisArg:o}),new br(m,p)}return h&&v?t.apply(this,s):(m=this.thru(f),h?r?m.value()[0]:m.value():m)})}),Ge(["pop","push","shift","sort","splice","unshift"],function(t){var e=ae[t],n=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",r=/^(?:pop|shift)$/.test(t);mr.prototype[t]=function(){var t=arguments;if(r&&!this.__chain__){var i=this.value();return e.apply(bs(i)?i:[],t)}return this[n](function(n){return e.apply(bs(n)?n:[],t)})}}),Gr(_r.prototype,function(t,e){var n=mr[e];if(n){var r=n.name+"";(sr[r]||(sr[r]=[])).push({name:e,func:n})}}),sr[go(o,y).name]=[{name:"wrapper",func:o}],_r.prototype.clone=function(){var t=new _r(this.__wrapped__);return t.__actions__=oo(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=oo(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=oo(this.__views__),t},_r.prototype.reverse=function(){if(this.__filtered__){var t=new _r(this);t.__dir__=-1,t.__filtered__=!0}else(t=this.clone()).__dir__*=-1;return t},_r.prototype.value=function(){var t=this.__wrapped__.value(),e=this.__dir__,n=bs(t),r=e<0,i=n?t.length:0,o=function(t,e,n){for(var r=-1,i=n.length;++r=this.__values__.length;return{done:t,value:t?o:this.__values__[this.__index__++]}},mr.prototype.plant=function(t){for(var e,n=this;n instanceof yr;){var r=ha(n);r.__index__=0,r.__values__=o,e?i.__wrapped__=r:e=r;var i=r;n=n.__wrapped__}return i.__wrapped__=t,e},mr.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof _r){var e=t;return this.__actions__.length&&(e=new _r(this)),(e=e.reverse()).__actions__.push({func:Ha,args:[Ea],thisArg:o}),new br(e,this.__chain__)}return this.thru(Ea)},mr.prototype.toJSON=mr.prototype.valueOf=mr.prototype.value=function(){return Wi(this.__wrapped__,this.__actions__)},mr.prototype.first=mr.prototype.head,Me&&(mr.prototype[Me]=function(){return this}),mr}();Ie._=Pn,(i=function(){return Pn}.call(e,n,e,r))===o||(r.exports=i)}).call(this)}).call(e,n(1),n(16)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){var r;!function(e,n){"use strict";"object"==typeof t&&"object"==typeof t.exports?t.exports=e.document?n(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return n(t)}:n(e)}("undefined"!=typeof window?window:this,function(n,i){"use strict";var o=[],a=n.document,s=Object.getPrototypeOf,u=o.slice,c=o.concat,l=o.push,f=o.indexOf,p={},d=p.toString,h=p.hasOwnProperty,v=h.toString,m=v.call(Object),g={},y=function(t){return"function"==typeof t&&"number"!=typeof t.nodeType},b=function(t){return null!=t&&t===t.window},_={type:!0,src:!0,noModule:!0};function w(t,e,n){var r,i=(e=e||a).createElement("script");if(i.text=t,n)for(r in _)n[r]&&(i[r]=n[r]);e.head.appendChild(i).parentNode.removeChild(i)}function x(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?p[d.call(t)]||"object":typeof t}var C=function(t,e){return new C.fn.init(t,e)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function k(t){var e=!!t&&"length"in t&&t.length,n=x(t);return!y(t)&&!b(t)&&("array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t)}C.fn=C.prototype={jquery:"3.3.1",constructor:C,length:0,toArray:function(){return u.call(this)},get:function(t){return null==t?u.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=C.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return C.each(this,t)},map:function(t){return this.pushStack(C.map(this,function(e,n){return t.call(e,n,e)}))},slice:function(){return this.pushStack(u.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(t){var e=this.length,n=+t+(t<0?e:0);return this.pushStack(n>=0&&n+~]|"+R+")"+R+"*"),W=new RegExp("="+R+"*([^\\]'\"]*?)"+R+"*\\]","g"),z=new RegExp(M),V=new RegExp("^"+P+"$"),X={ID:new RegExp("^#("+P+")"),CLASS:new RegExp("^\\.("+P+")"),TAG:new RegExp("^("+P+"|[*])"),ATTR:new RegExp("^"+F),PSEUDO:new RegExp("^"+M),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},K=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,G=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Y=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+R+"?|("+R+")|.)","ig"),tt=function(t,e,n){var r="0x"+e-65536;return r!=r||n?e:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},et=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,nt=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},rt=function(){p()},it=yt(function(t){return!0===t.disabled&&("form"in t||"label"in t)},{dir:"parentNode",next:"legend"});try{N.apply(E=D.call(w.childNodes),w.childNodes),E[w.childNodes.length].nodeType}catch(t){N={apply:E.length?function(t,e){j.apply(t,D.call(e))}:function(t,e){for(var n=t.length,r=0;t[n++]=e[r++];);t.length=n-1}}}function ot(t,e,r,i){var o,s,c,l,f,h,g,y=e&&e.ownerDocument,x=e?e.nodeType:9;if(r=r||[],"string"!=typeof t||!t||1!==x&&9!==x&&11!==x)return r;if(!i&&((e?e.ownerDocument||e:w)!==d&&p(e),e=e||d,v)){if(11!==x&&(f=Q.exec(t)))if(o=f[1]){if(9===x){if(!(c=e.getElementById(o)))return r;if(c.id===o)return r.push(c),r}else if(y&&(c=y.getElementById(o))&&b(e,c)&&c.id===o)return r.push(c),r}else{if(f[2])return N.apply(r,e.getElementsByTagName(t)),r;if((o=f[3])&&n.getElementsByClassName&&e.getElementsByClassName)return N.apply(r,e.getElementsByClassName(o)),r}if(n.qsa&&!$[t+" "]&&(!m||!m.test(t))){if(1!==x)y=e,g=t;else if("object"!==e.nodeName.toLowerCase()){for((l=e.getAttribute("id"))?l=l.replace(et,nt):e.setAttribute("id",l=_),s=(h=a(t)).length;s--;)h[s]="#"+l+" "+gt(h[s]);g=h.join(","),y=Y.test(t)&&vt(e.parentNode)||e}if(g)try{return N.apply(r,y.querySelectorAll(g)),r}catch(t){}finally{l===_&&e.removeAttribute("id")}}}return u(t.replace(B,"$1"),e,r,i)}function at(){var t=[];return function e(n,i){return t.push(n+" ")>r.cacheLength&&delete e[t.shift()],e[n+" "]=i}}function st(t){return t[_]=!0,t}function ut(t){var e=d.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function ct(t,e){for(var n=t.split("|"),i=n.length;i--;)r.attrHandle[n[i]]=e}function lt(t,e){var n=e&&t,r=n&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function ft(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function pt(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function dt(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&it(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ht(t){return st(function(e){return e=+e,st(function(n,r){for(var i,o=t([],n.length,e),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function vt(t){return t&&void 0!==t.getElementsByTagName&&t}for(e in n=ot.support={},o=ot.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},p=ot.setDocument=function(t){var e,i,a=t?t.ownerDocument||t:w;return a!==d&&9===a.nodeType&&a.documentElement?(h=(d=a).documentElement,v=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",rt,!1):i.attachEvent&&i.attachEvent("onunload",rt)),n.attributes=ut(function(t){return t.className="i",!t.getAttribute("className")}),n.getElementsByTagName=ut(function(t){return t.appendChild(d.createComment("")),!t.getElementsByTagName("*").length}),n.getElementsByClassName=G.test(d.getElementsByClassName),n.getById=ut(function(t){return h.appendChild(t).id=_,!d.getElementsByName||!d.getElementsByName(_).length}),n.getById?(r.filter.ID=function(t){var e=t.replace(Z,tt);return function(t){return t.getAttribute("id")===e}},r.find.ID=function(t,e){if(void 0!==e.getElementById&&v){var n=e.getElementById(t);return n?[n]:[]}}):(r.filter.ID=function(t){var e=t.replace(Z,tt);return function(t){var n=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}},r.find.ID=function(t,e){if(void 0!==e.getElementById&&v){var n,r,i,o=e.getElementById(t);if(o){if((n=o.getAttributeNode("id"))&&n.value===t)return[o];for(i=e.getElementsByName(t),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===t)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):n.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,r=[],i=0,o=e.getElementsByTagName(t);if("*"===t){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&v)return e.getElementsByClassName(t)},g=[],m=[],(n.qsa=G.test(d.querySelectorAll))&&(ut(function(t){h.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+R+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||m.push("\\["+R+"*(?:value|"+L+")"),t.querySelectorAll("[id~="+_+"-]").length||m.push("~="),t.querySelectorAll(":checked").length||m.push(":checked"),t.querySelectorAll("a#"+_+"+*").length||m.push(".#.+[+~]")}),ut(function(t){t.innerHTML="";var e=d.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&m.push("name"+R+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&m.push(":enabled",":disabled"),h.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&m.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),m.push(",.*:")})),(n.matchesSelector=G.test(y=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ut(function(t){n.disconnectedMatch=y.call(t,"*"),y.call(t,"[s!='']:x"),g.push("!=",M)}),m=m.length&&new RegExp(m.join("|")),g=g.length&&new RegExp(g.join("|")),e=G.test(h.compareDocumentPosition),b=e||G.test(h.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,r=e&&e.parentNode;return t===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):t.compareDocumentPosition&&16&t.compareDocumentPosition(r)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},A=e?function(t,e){if(t===e)return f=!0,0;var r=!t.compareDocumentPosition-!e.compareDocumentPosition;return r||(1&(r=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!n.sortDetached&&e.compareDocumentPosition(t)===r?t===d||t.ownerDocument===w&&b(w,t)?-1:e===d||e.ownerDocument===w&&b(w,e)?1:l?I(l,t)-I(l,e):0:4&r?-1:1)}:function(t,e){if(t===e)return f=!0,0;var n,r=0,i=t.parentNode,o=e.parentNode,a=[t],s=[e];if(!i||!o)return t===d?-1:e===d?1:i?-1:o?1:l?I(l,t)-I(l,e):0;if(i===o)return lt(t,e);for(n=t;n=n.parentNode;)a.unshift(n);for(n=e;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?lt(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},ot.matches=function(t,e){return ot(t,null,null,e)},ot.matchesSelector=function(t,e){if((t.ownerDocument||t)!==d&&p(t),e=e.replace(W,"='$1']"),n.matchesSelector&&v&&!$[e+" "]&&(!g||!g.test(e))&&(!m||!m.test(e)))try{var r=y.call(t,e);if(r||n.disconnectedMatch||t.document&&11!==t.document.nodeType)return r}catch(t){}return ot(e,d,null,[t]).length>0},ot.contains=function(t,e){return(t.ownerDocument||t)!==d&&p(t),b(t,e)},ot.attr=function(t,e){(t.ownerDocument||t)!==d&&p(t);var i=r.attrHandle[e.toLowerCase()],o=i&&S.call(r.attrHandle,e.toLowerCase())?i(t,e,!v):void 0;return void 0!==o?o:n.attributes||!v?t.getAttribute(e):(o=t.getAttributeNode(e))&&o.specified?o.value:null},ot.escape=function(t){return(t+"").replace(et,nt)},ot.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},ot.uniqueSort=function(t){var e,r=[],i=0,o=0;if(f=!n.detectDuplicates,l=!n.sortStable&&t.slice(0),t.sort(A),f){for(;e=t[o++];)e===t[o]&&(i=r.push(o));for(;i--;)t.splice(r[i],1)}return l=null,t},i=ot.getText=function(t){var e,n="",r=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=i(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[r++];)n+=i(e);return n},(r=ot.selectors={cacheLength:50,createPseudo:st,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(Z,tt),t[3]=(t[3]||t[4]||t[5]||"").replace(Z,tt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||ot.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&ot.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return X.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&z.test(n)&&(e=a(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(Z,tt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=T[t+" "];return e||(e=new RegExp("(^|"+R+")"+t+"("+R+"|$)"))&&T(t,function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,e,n){return function(r){var i=ot.attr(r,t);return null==i?"!="===e:!e||(i+="","="===e?i===n:"!="===e?i!==n:"^="===e?n&&0===i.indexOf(n):"*="===e?n&&i.indexOf(n)>-1:"$="===e?n&&i.slice(-n.length)===n:"~="===e?(" "+i.replace(q," ")+" ").indexOf(n)>-1:"|="===e&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,r,i){var o="nth"!==t.slice(0,3),a="last"!==t.slice(-4),s="of-type"===e;return 1===r&&0===i?function(t){return!!t.parentNode}:function(e,n,u){var c,l,f,p,d,h,v=o!==a?"nextSibling":"previousSibling",m=e.parentNode,g=s&&e.nodeName.toLowerCase(),y=!u&&!s,b=!1;if(m){if(o){for(;v;){for(p=e;p=p[v];)if(s?p.nodeName.toLowerCase()===g:1===p.nodeType)return!1;h=v="only"===t&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&y){for(b=(d=(c=(l=(f=(p=m)[_]||(p[_]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]||[])[0]===x&&c[1])&&c[2],p=d&&m.childNodes[d];p=++d&&p&&p[v]||(b=d=0)||h.pop();)if(1===p.nodeType&&++b&&p===e){l[t]=[x,d,b];break}}else if(y&&(b=d=(c=(l=(f=(p=e)[_]||(p[_]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]||[])[0]===x&&c[1]),!1===b)for(;(p=++d&&p&&p[v]||(b=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==g:1!==p.nodeType)||!++b||(y&&((l=(f=p[_]||(p[_]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]=[x,b]),p!==e)););return(b-=i)===r||b%r==0&&b/r>=0}}},PSEUDO:function(t,e){var n,i=r.pseudos[t]||r.setFilters[t.toLowerCase()]||ot.error("unsupported pseudo: "+t);return i[_]?i(e):i.length>1?(n=[t,t,"",e],r.setFilters.hasOwnProperty(t.toLowerCase())?st(function(t,n){for(var r,o=i(t,e),a=o.length;a--;)t[r=I(t,o[a])]=!(n[r]=o[a])}):function(t){return i(t,0,n)}):i}},pseudos:{not:st(function(t){var e=[],n=[],r=s(t.replace(B,"$1"));return r[_]?st(function(t,e,n,i){for(var o,a=r(t,null,i,[]),s=t.length;s--;)(o=a[s])&&(t[s]=!(e[s]=o))}):function(t,i,o){return e[0]=t,r(e,null,o,n),e[0]=null,!n.pop()}}),has:st(function(t){return function(e){return ot(t,e).length>0}}),contains:st(function(t){return t=t.replace(Z,tt),function(e){return(e.textContent||e.innerText||i(e)).indexOf(t)>-1}}),lang:st(function(t){return V.test(t||"")||ot.error("unsupported lang: "+t),t=t.replace(Z,tt).toLowerCase(),function(e){var n;do{if(n=v?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===h},focus:function(t){return t===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:dt(!1),disabled:dt(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!r.pseudos.empty(t)},header:function(t){return J.test(t.nodeName)},input:function(t){return K.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:ht(function(){return[0]}),last:ht(function(t,e){return[e-1]}),eq:ht(function(t,e,n){return[n<0?n+e:n]}),even:ht(function(t,e){for(var n=0;n=0;)t.push(r);return t}),gt:ht(function(t,e,n){for(var r=n<0?n+e:n;++r1?function(e,n,r){for(var i=t.length;i--;)if(!t[i](e,n,r))return!1;return!0}:t[0]}function _t(t,e,n,r,i){for(var o,a=[],s=0,u=t.length,c=null!=e;s-1&&(o[c]=!(a[c]=f))}}else g=_t(g===a?g.splice(h,g.length):g),i?i(null,a,g,u):N.apply(a,g)})}function xt(t){for(var e,n,i,o=t.length,a=r.relative[t[0].type],s=a||r.relative[" "],u=a?1:0,l=yt(function(t){return t===e},s,!0),f=yt(function(t){return I(e,t)>-1},s,!0),p=[function(t,n,r){var i=!a&&(r||n!==c)||((e=n).nodeType?l(t,n,r):f(t,n,r));return e=null,i}];u1&&bt(p),u>1&>(t.slice(0,u-1).concat({value:" "===t[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=t.length>0,o=function(o,a,s,u,l){var f,h,m,g=0,y="0",b=o&&[],_=[],w=c,C=o||i&&r.find.TAG("*",l),T=x+=null==w?1:Math.random()||.1,k=C.length;for(l&&(c=a===d||a||l);y!==k&&null!=(f=C[y]);y++){if(i&&f){for(h=0,a||f.ownerDocument===d||(p(f),s=!v);m=t[h++];)if(m(f,a||d,s)){u.push(f);break}l&&(x=T)}n&&((f=!m&&f)&&g--,o&&b.push(f))}if(g+=y,n&&y!==g){for(h=0;m=e[h++];)m(b,_,a,s);if(o){if(g>0)for(;y--;)b[y]||_[y]||(_[y]=O.call(u));_=_t(_)}N.apply(u,_),l&&!o&&_.length>0&&g+e.length>1&&ot.uniqueSort(u)}return l&&(x=T,c=w),b};return n?st(o):o}(o,i))).selector=t}return s},u=ot.select=function(t,e,n,i){var o,u,c,l,f,p="function"==typeof t&&t,d=!i&&a(t=p.selector||t);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(c=u[0]).type&&9===e.nodeType&&v&&r.relative[u[1].type]){if(!(e=(r.find.ID(c.matches[0].replace(Z,tt),e)||[])[0]))return n;p&&(e=e.parentNode),t=t.slice(u.shift().value.length)}for(o=X.needsContext.test(t)?0:u.length;o--&&(c=u[o],!r.relative[l=c.type]);)if((f=r.find[l])&&(i=f(c.matches[0].replace(Z,tt),Y.test(u[0].type)&&vt(e.parentNode)||e))){if(u.splice(o,1),!(t=i.length&>(u)))return N.apply(n,i),n;break}}return(p||s(t,d))(i,e,!v,n,!e||Y.test(t)&&vt(e.parentNode)||e),n},n.sortStable=_.split("").sort(A).join("")===_,n.detectDuplicates=!!f,p(),n.sortDetached=ut(function(t){return 1&t.compareDocumentPosition(d.createElement("fieldset"))}),ut(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||ct("type|href|height|width",function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),n.attributes&&ut(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||ct("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),ut(function(t){return null==t.getAttribute("disabled")})||ct(L,function(t,e,n){var r;if(!n)return!0===t[e]?e.toLowerCase():(r=t.getAttributeNode(e))&&r.specified?r.value:null}),ot}(n);C.find=$,C.expr=$.selectors,C.expr[":"]=C.expr.pseudos,C.uniqueSort=C.unique=$.uniqueSort,C.text=$.getText,C.isXMLDoc=$.isXML,C.contains=$.contains,C.escapeSelector=$.escape;var A=function(t,e,n){for(var r=[],i=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(i&&C(t).is(n))break;r.push(t)}return r},S=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},E=C.expr.match.needsContext;function O(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var j=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function N(t,e,n){return y(e)?C.grep(t,function(t,r){return!!e.call(t,r,t)!==n}):e.nodeType?C.grep(t,function(t){return t===e!==n}):"string"!=typeof e?C.grep(t,function(t){return f.call(e,t)>-1!==n}):C.filter(e,t,n)}C.filter=function(t,e,n){var r=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===r.nodeType?C.find.matchesSelector(r,t)?[r]:[]:C.find.matches(t,C.grep(e,function(t){return 1===t.nodeType}))},C.fn.extend({find:function(t){var e,n,r=this.length,i=this;if("string"!=typeof t)return this.pushStack(C(t).filter(function(){for(e=0;e1?C.uniqueSort(n):n},filter:function(t){return this.pushStack(N(this,t||[],!1))},not:function(t){return this.pushStack(N(this,t||[],!0))},is:function(t){return!!N(this,"string"==typeof t&&E.test(t)?C(t):t||[],!1).length}});var D,I=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(C.fn.init=function(t,e,n){var r,i;if(!t)return this;if(n=n||D,"string"==typeof t){if(!(r="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:I.exec(t))||!r[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(r[1]){if(e=e instanceof C?e[0]:e,C.merge(this,C.parseHTML(r[1],e&&e.nodeType?e.ownerDocument||e:a,!0)),j.test(r[1])&&C.isPlainObject(e))for(r in e)y(this[r])?this[r](e[r]):this.attr(r,e[r]);return this}return(i=a.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):y(t)?void 0!==n.ready?n.ready(t):t(C):C.makeArray(t,this)}).prototype=C.fn,D=C(a);var L=/^(?:parents|prev(?:Until|All))/,R={children:!0,contents:!0,next:!0,prev:!0};function P(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}C.fn.extend({has:function(t){var e=C(t,this),n=e.length;return this.filter(function(){for(var t=0;t-1:1===n.nodeType&&C.find.matchesSelector(n,t))){o.push(n);break}return this.pushStack(o.length>1?C.uniqueSort(o):o)},index:function(t){return t?"string"==typeof t?f.call(C(t),this[0]):f.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(C.uniqueSort(C.merge(this.get(),C(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),C.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return A(t,"parentNode")},parentsUntil:function(t,e,n){return A(t,"parentNode",n)},next:function(t){return P(t,"nextSibling")},prev:function(t){return P(t,"previousSibling")},nextAll:function(t){return A(t,"nextSibling")},prevAll:function(t){return A(t,"previousSibling")},nextUntil:function(t,e,n){return A(t,"nextSibling",n)},prevUntil:function(t,e,n){return A(t,"previousSibling",n)},siblings:function(t){return S((t.parentNode||{}).firstChild,t)},children:function(t){return S(t.firstChild)},contents:function(t){return O(t,"iframe")?t.contentDocument:(O(t,"template")&&(t=t.content||t),C.merge([],t.childNodes))}},function(t,e){C.fn[t]=function(n,r){var i=C.map(this,e,n);return"Until"!==t.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=C.filter(r,i)),this.length>1&&(R[t]||C.uniqueSort(i),L.test(t)&&i.reverse()),this.pushStack(i)}});var F=/[^\x20\t\r\n\f]+/g;function M(t){return t}function q(t){throw t}function B(t,e,n,r){var i;try{t&&y(i=t.promise)?i.call(t).done(e).fail(n):t&&y(i=t.then)?i.call(t,e,n):e.apply(void 0,[t].slice(r))}catch(t){n.apply(void 0,[t])}}C.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return C.each(t.match(F)||[],function(t,n){e[n]=!0}),e}(t):C.extend({},t);var e,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||t.once,r=e=!0;a.length;s=-1)for(n=a.shift();++s-1;)o.splice(n,1),n<=s&&s--}),this},has:function(t){return t?C.inArray(t,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||e||(o=n=""),this},locked:function(){return!!i},fireWith:function(t,n){return i||(n=[t,(n=n||[]).slice?n.slice():n],a.push(n),e||u()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},C.extend({Deferred:function(t){var e=[["notify","progress",C.Callbacks("memory"),C.Callbacks("memory"),2],["resolve","done",C.Callbacks("once memory"),C.Callbacks("once memory"),0,"resolved"],["reject","fail",C.Callbacks("once memory"),C.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(t){return i.then(null,t)},pipe:function(){var t=arguments;return C.Deferred(function(n){C.each(e,function(e,r){var i=y(t[r[4]])&&t[r[4]];o[r[1]](function(){var t=i&&i.apply(this,arguments);t&&y(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[r[0]+"With"](this,i?[t]:arguments)})}),t=null}).promise()},then:function(t,r,i){var o=0;function a(t,e,r,i){return function(){var s=this,u=arguments,c=function(){var n,c;if(!(t=o&&(r!==q&&(s=void 0,u=[n]),e.rejectWith(s,u))}};t?l():(C.Deferred.getStackHook&&(l.stackTrace=C.Deferred.getStackHook()),n.setTimeout(l))}}return C.Deferred(function(n){e[0][3].add(a(0,n,y(i)?i:M,n.notifyWith)),e[1][3].add(a(0,n,y(t)?t:M)),e[2][3].add(a(0,n,y(r)?r:q))}).promise()},promise:function(t){return null!=t?C.extend(t,i):i}},o={};return C.each(e,function(t,n){var a=n[2],s=n[5];i[n[1]]=a.add,s&&a.add(function(){r=s},e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),a.add(n[3].fire),o[n[0]]=function(){return o[n[0]+"With"](this===o?void 0:this,arguments),this},o[n[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(t){var e=arguments.length,n=e,r=Array(n),i=u.call(arguments),o=C.Deferred(),a=function(t){return function(n){r[t]=this,i[t]=arguments.length>1?u.call(arguments):n,--e||o.resolveWith(r,i)}};if(e<=1&&(B(t,o.done(a(n)).resolve,o.reject,!e),"pending"===o.state()||y(i[n]&&i[n].then)))return o.then();for(;n--;)B(i[n],a(n),o.reject);return o.promise()}});var H=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;C.Deferred.exceptionHook=function(t,e){n.console&&n.console.warn&&t&&H.test(t.name)&&n.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},C.readyException=function(t){n.setTimeout(function(){throw t})};var U=C.Deferred();function W(){a.removeEventListener("DOMContentLoaded",W),n.removeEventListener("load",W),C.ready()}C.fn.ready=function(t){return U.then(t).catch(function(t){C.readyException(t)}),this},C.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--C.readyWait:C.isReady)||(C.isReady=!0,!0!==t&&--C.readyWait>0||U.resolveWith(a,[C]))}}),C.ready.then=U.then,"complete"===a.readyState||"loading"!==a.readyState&&!a.documentElement.doScroll?n.setTimeout(C.ready):(a.addEventListener("DOMContentLoaded",W),n.addEventListener("load",W));var z=function(t,e,n,r,i,o,a){var s=0,u=t.length,c=null==n;if("object"===x(n))for(s in i=!0,n)z(t,e,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,y(r)||(a=!0),c&&(a?(e.call(t,r),e=null):(c=e,e=function(t,e,n){return c.call(C(t),n)})),e))for(;s1,null,!0)},removeData:function(t){return this.each(function(){Z.remove(this,t)})}}),C.extend({queue:function(t,e,n){var r;if(t)return e=(e||"fx")+"queue",r=Y.get(t,e),n&&(!r||Array.isArray(n)?r=Y.access(t,e,C.makeArray(n)):r.push(n)),r||[]},dequeue:function(t,e){e=e||"fx";var n=C.queue(t,e),r=n.length,i=n.shift(),o=C._queueHooks(t,e);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===e&&n.unshift("inprogress"),delete o.stop,i.call(t,function(){C.dequeue(t,e)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return Y.get(t,n)||Y.access(t,n,{empty:C.Callbacks("once memory").add(function(){Y.remove(t,[e+"queue",n])})})}}),C.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]+)/i,ht=/^$|^module$|\/(?:java|ecma)script/i,vt={option:[1,""],thead:[1,"
    {{ 'submitted_start_balance'|_ }} (date){{ 'submitted_start_balance'|_ }} ({{ start.formatLocalized(monthAndDayFormat) }}) {{ formatAmountByAccount(account, startBalance) }}
    {{ formatAmountByAccount(account, clearedAmount) }}
    {{ 'submitted_end_balance'|_ }} (date){{ 'submitted_end_balance'|_ }} ({{ end.formatLocalized(monthAndDayFormat) }}) {{ formatAmountByAccount(account, endBalance) }}
    - {{ linkType.name }} + {{ journalLinkTranslation('name', linkType.name) }} - {{ linkType.inward }} + {{ journalLinkTranslation('inward', linkType.inward) }} - {{ linkType.outward }} + {{ journalLinkTranslation('outward', linkType.outward) }} {{ linkType.journalCount }} diff --git a/resources/views/admin/link/show.twig b/resources/views/admin/link/show.twig index b9639b981c..b14813fded 100644 --- a/resources/views/admin/link/show.twig +++ b/resources/views/admin/link/show.twig @@ -8,7 +8,7 @@
    -

    {{ trans('firefly.overview_for_link', {name: linkType.name}) }}

    +

    {{ trans('firefly.overview_for_link', {name: journalLinkTranslation('name', linkType.name)}) }}

    @@ -36,7 +36,7 @@ {{ link.source.description }} - + From bf3c57d26bc3e5a8151aeb5f55232c8c04582440 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 4 Aug 2018 14:14:08 +0200 Subject: [PATCH 026/166] Remove translations. --- resources/lang/de_DE/firefly.php | 4 ---- resources/lang/es_ES/firefly.php | 4 ---- resources/lang/fr_FR/firefly.php | 4 ---- resources/lang/id_ID/firefly.php | 4 ---- resources/lang/it_IT/firefly.php | 4 ---- resources/lang/nl_NL/firefly.php | 4 ---- resources/lang/pl_PL/firefly.php | 4 ---- resources/lang/pt_BR/firefly.php | 4 ---- resources/lang/ru_RU/firefly.php | 4 ---- resources/lang/tr_TR/firefly.php | 4 ---- 10 files changed, 40 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 5ac8855ae3..b947fd5730 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -1133,10 +1133,6 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'deleted_link' => 'Verknüpfung löschen', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'bezieht sich auf', 'is (partially) refunded by_inward' => 'wird (teilweise) erstattet durch', 'is (partially) paid for by_inward' => 'wird (teilweise) bezahlt von', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index bc327f15ea..eeecbbfb2a 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -1133,10 +1133,6 @@ return [ 'deleted_link' => 'Enlace borrado', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'relacionado con', 'is (partially) refunded by_inward' => 'es (parcialmente) es devuelto por', 'is (partially) paid for by_inward' => 'es(parcialmente) pagado por', diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index e84db72fb6..9fbab65594 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Lien supprimé', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'se rapporte à', 'is (partially) refunded by_inward' => 'est (partiellement) remboursé par', 'is (partially) paid for by_inward' => 'est (partiellement) payé par', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 17ecbc86e5..77dea623aa 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Tautan dihapus', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'berhubungan dengan', 'is (partially) refunded by_inward' => '(sebagian) dikembalikan oleh', 'is (partially) paid for by_inward' => 'adalah (sebagian) dibayar oleh', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index 80f5ed6e7f..b402a9674e 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Elimina collegamento', // link translations: - 'Paid_name' => 'Pagata', - 'Refund_name' => 'Rimborso', - 'Reimbursement_name' => 'Rimborso', - 'Related_name' => 'Inerente', 'relates to_inward' => 'inerente a', 'is (partially) refunded by_inward' => 'è (parzialmente) rimborsata da', 'is (partially) paid for by_inward' => 'è (parzialmente) pagata da', diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 1958b6f73a..500f0f75a9 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Koppeling verwijderd', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'gerelateerd aan', 'is (partially) refunded by_inward' => 'wordt (deels) terugbetaald door', 'is (partially) paid for by_inward' => 'wordt (deels) betaald door', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 76c5c7fb80..7c5687f66e 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Usunięto powiązanie', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'odnosi się do', 'is (partially) refunded by_inward' => 'jest (częściowo) zwracane przez', 'is (partially) paid for by_inward' => 'jest (częściowo) opłacane przez', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index cc4c4dc6c4..96b0815825 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Excluir ligação', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'relacionado a', 'is (partially) refunded by_inward' => 'é (parcialmente) devolvido por', 'is (partially) paid for by_inward' => 'é (parcialmente) pago por', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index d437830797..f5292f8f7d 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -1132,10 +1132,6 @@ return [ 'deleted_link' => 'Связь удалена', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'связано с', 'is (partially) refunded by_inward' => '(частично) возвращён', 'is (partially) paid for by_inward' => '(частично) оплачен', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index ddd1dff69e..6e26744d38 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -1135,10 +1135,6 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'deleted_link' => 'Bağlantı silindi', // link translations: - 'Paid_name' => 'Paid', - 'Refund_name' => 'Refund', - 'Reimbursement_name' => 'Reimbursement', - 'Related_name' => 'Related', 'relates to_inward' => 'ile ilişkili', 'is (partially) refunded by_inward' => 'tarafından (kısmen) geri ödendi', 'is (partially) paid for by_inward' => 'tarafından (kısmen) ödendi', From 5af026674f78e8ea7149841d1ad9d24f2a49b5c3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 4 Aug 2018 14:17:03 +0200 Subject: [PATCH 027/166] Updated translations. --- resources/lang/en_US/firefly.php | 21 +++++++++++++-------- resources/lang/en_US/form.php | 2 ++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 43632689a8..eaca551c4c 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'New asset account', 'new_expense_account' => 'New expense account', 'new_revenue_account' => 'New revenue account', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'New budget', 'new_bill' => 'New bill', 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', @@ -589,6 +590,8 @@ return [ 'invalid_convert_selection' => 'The account you have selected is already used in this transaction or does not exist.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + + // create new stuff: 'create_new_withdrawal' => 'Create new withdrawal', 'create_new_deposit' => 'Create new deposit', @@ -695,6 +698,7 @@ return [ 'make_new_asset_account' => 'Create a new asset account', 'make_new_expense_account' => 'Create a new expense account', 'make_new_revenue_account' => 'Create a new revenue account', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Asset accounts', 'expense_accounts' => 'Expense accounts', 'revenue_accounts' => 'Revenue accounts', @@ -734,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Please select more than one category', 'select_more_than_one_budget' => 'Please select more than one budget', 'select_more_than_one_tag' => 'Please select more than one tag', - 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Your Firefly III ledger has more money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', 'reconcile_has_less' => 'Your Firefly III ledger has less money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', 'reconcile_is_equal' => 'Your Firefly III ledger and your bank statements match. There is nothing to do. Please press "Confirm reconciliation" to confirm your input.', @@ -854,6 +858,10 @@ return [ 'Expense account' => 'Expense account', 'Revenue account' => 'Revenue account', 'Initial balance account' => 'Initial balance account', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Budgets', 'tags' => 'Tags', 'reports' => 'Reports', @@ -883,6 +891,10 @@ return [ 'monthly' => 'Monthly', 'profile' => 'Profile', 'errors' => 'Errors', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Default financial report between :start and :end', @@ -1132,13 +1144,6 @@ return [ 'destination_transaction' => 'Destination transaction', 'delete_journal_link' => 'Delete the link between :source and :destination', 'deleted_link' => 'Deleted link', - 'link_is (partially) paid for by' => 'is (partially) paid for by', - 'link_(partially) pays for' => '(partially) pays for', - 'link_is (partially) refunded by' => 'is (partially) refunded by', - 'link_(partially) refunds' => '(partially) refunds', - 'link_is (partially) reimbursed by' => 'is (partially) reimbursed by', - 'link_(partially) reimburses' => '(partially) reimburses', - 'link_relates to' => 'relates to', // link translations: 'Paid_name' => 'Paid', diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index c3d02d9cee..c2dfc104eb 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -84,6 +84,8 @@ return [ 'verification' => 'Verification', 'api_key' => 'API key', 'remember_me' => 'Remember me', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', 'source_account_asset' => 'Source account (asset account)', 'destination_account_expense' => 'Destination account (expense account)', From f0d3ca5d537da9f6b6464e9e65e17030df62907a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 4 Aug 2018 17:30:06 +0200 Subject: [PATCH 028/166] Various code cleanup. --- app/Api/V1/Requests/RecurrenceRequest.php | 2 +- .../Report/Account/MonthReportGenerator.php | 17 ++++-- .../Report/Audit/MonthReportGenerator.php | 15 ++++-- .../Report/Budget/MonthReportGenerator.php | 10 +++- .../Report/Category/MonthReportGenerator.php | 35 ++++++------ .../Report/Standard/MonthReportGenerator.php | 19 ++++--- .../Standard/MultiYearReportGenerator.php | 19 ++++--- .../Report/Standard/YearReportGenerator.php | 20 ++++--- .../Report/Tag/MonthReportGenerator.php | 26 +++++---- .../Controllers/Budget/ShowController.php | 4 +- .../Controllers/Category/ShowController.php | 4 +- app/Http/Controllers/DebugController.php | 2 +- app/Http/Controllers/ExportController.php | 2 +- .../Controllers/Import/CallbackController.php | 2 + .../Controllers/Import/IndexController.php | 2 +- .../Controllers/Json/FrontpageController.php | 10 +++- app/Http/Controllers/JsonController.php | 18 +++++-- .../Controllers/Popup/ReportController.php | 4 +- .../Report/OperationsController.php | 2 +- app/Http/Controllers/ReportController.php | 53 ++++++++++++++----- .../Controllers/Rule/SelectController.php | 2 +- app/Http/Controllers/RuleGroupController.php | 2 +- app/Http/Controllers/SearchController.php | 11 ++-- .../Controllers/TransactionController.php | 2 +- app/Http/Requests/AccountFormRequest.php | 23 +++++++- app/Http/Requests/ExportFormRequest.php | 2 +- app/Http/Requests/RecurrenceFormRequest.php | 2 +- .../Requests/SelectTransactionsRequest.php | 2 +- app/Import/Storage/ImportArrayStorage.php | 4 ++ app/Models/Account.php | 8 +-- app/Models/AccountMeta.php | 6 +-- app/Models/AccountType.php | 20 ++++++- app/Models/Attachment.php | 6 ++- app/Models/AvailableBudget.php | 8 +-- app/Models/Bill.php | 13 +++-- app/Models/Budget.php | 22 ++++---- app/Models/BudgetLimit.php | 6 ++- app/Models/Category.php | 18 ++++--- app/Models/Configuration.php | 5 +- app/Models/CurrencyExchangeRate.php | 12 ++++- app/Models/ExportJob.php | 21 ++++++-- app/Models/ImportJob.php | 13 +++-- app/Models/LinkType.php | 9 ++-- app/Models/Note.php | 2 +- app/Models/PiggyBank.php | 21 ++++---- app/Models/PiggyBankEvent.php | 19 +++---- app/Models/PiggyBankRepetition.php | 10 ++-- app/Models/Preference.php | 11 ++-- app/Models/Recurrence.php | 8 +-- app/Models/RecurrenceMeta.php | 10 ++-- app/Models/RecurrenceRepetition.php | 9 +++- app/Models/RecurrenceTransaction.php | 14 +++-- app/Models/RecurrenceTransactionMeta.php | 9 +++- app/Models/Role.php | 6 +-- app/Models/Rule.php | 6 ++- app/Models/RuleAction.php | 7 +-- app/Models/RuleGroup.php | 18 ++++--- app/Models/RuleTrigger.php | 7 +-- app/Models/Tag.php | 18 ++++--- app/Models/Transaction.php | 43 +++++++++------ app/Models/TransactionCurrency.php | 13 ++--- app/Models/TransactionJournal.php | 35 ++++++------ app/Models/TransactionJournalLink.php | 23 +++++--- app/Models/TransactionJournalMeta.php | 6 +-- app/Models/TransactionType.php | 11 ++-- .../ExportJob/ExportJobRepository.php | 14 +++-- .../Recurring/RecurringRepository.php | 2 +- .../File/ConfigureMappingHandler.php | 1 + .../File/NewFileJobHandler.php | 3 +- .../Ynab/NewYnabJobHandler.php | 6 +++ .../Ynab/SelectAccountsHandler.php | 2 +- .../Routine/Bunq/StageImportDataHandler.php | 1 + .../Import/Routine/Fake/StageFinalHandler.php | 2 +- .../Import/Routine/File/CurrencyMapper.php | 2 +- .../Routine/File/ImportableConverter.php | 2 +- .../Import/Routine/Ynab/ImportDataHandler.php | 3 +- .../Routine/Ynab/StageGetAccessHandler.php | 2 + app/Support/Preferences.php | 5 +- app/Support/Search/Search.php | 2 +- app/Support/Twig/AmountFormat.php | 1 + .../Twig/Extension/TransactionJournal.php | 2 +- app/Support/Twig/General.php | 4 +- app/User.php | 2 +- app/Validation/TransactionValidation.php | 10 ++-- resources/lang/en_US/validation.php | 2 +- resources/views/form/amount-no-currency.twig | 1 + resources/views/form/number.twig | 1 + resources/views/transactions/show.twig | 4 +- 88 files changed, 552 insertions(+), 311 deletions(-) diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php index fd348c733d..77adb8d242 100644 --- a/app/Api/V1/Requests/RecurrenceRequest.php +++ b/app/Api/V1/Requests/RecurrenceRequest.php @@ -84,7 +84,7 @@ class RecurrenceRequest extends Request */ public function rules(): array { - $today = Carbon::create()->addDay(); + $today = Carbon::now()->addDay(); return [ 'type' => 'required|in:withdrawal,transfer,deposit', diff --git a/app/Generator/Report/Account/MonthReportGenerator.php b/app/Generator/Report/Account/MonthReportGenerator.php index 49f0cbfbd7..ab135909f7 100644 --- a/app/Generator/Report/Account/MonthReportGenerator.php +++ b/app/Generator/Report/Account/MonthReportGenerator.php @@ -25,6 +25,8 @@ namespace FireflyIII\Generator\Report\Account; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use Illuminate\Support\Collection; +use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -44,7 +46,6 @@ class MonthReportGenerator implements ReportGeneratorInterface * Generate the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -52,11 +53,17 @@ class MonthReportGenerator implements ReportGeneratorInterface $expenseIds = implode(',', $this->expense->pluck('id')->toArray()); $reportType = 'account'; $preferredPeriod = $this->preferredPeriod(); + try { + $result = view( + 'reports.account.report', + compact('accountIds', 'reportType', 'expenseIds', 'preferredPeriod') + )->with('start', $this->start)->with('end', $this->end)->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } - return view( - 'reports.account.report', - compact('accountIds', 'reportType', 'expenseIds', 'preferredPeriod') - )->with('start', $this->start)->with('end', $this->end)->render(); + return $result; } /** diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index 386f27ded2..3dcaa08952 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -34,6 +34,8 @@ use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; +use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -52,7 +54,6 @@ class MonthReportGenerator implements ReportGeneratorInterface * * @return string * @throws FireflyException - * @throws \Throwable */ public function generate(): string { @@ -78,10 +79,16 @@ class MonthReportGenerator implements ReportGeneratorInterface 'internal_reference', 'notes', 'create_date', 'update_date', ]; + try { + $result = view('reports.audit.report', compact('reportType', 'accountIds', 'auditData', 'hideable', 'defaultShow')) + ->with('start', $this->start)->with('end', $this->end)->with('accounts', $this->accounts) + ->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.audit.report: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } - return view('reports.audit.report', compact('reportType', 'accountIds', 'auditData', 'hideable', 'defaultShow')) - ->with('start', $this->start)->with('end', $this->end)->with('accounts', $this->accounts) - ->render(); + return $result; } /** diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index ff4229ab26..39aaf1696e 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -35,6 +35,7 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -64,7 +65,6 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface * Generates the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -77,11 +77,17 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $topExpenses = $this->getTopExpenses(); // render! - return view('reports.budget.month', compact('accountIds', 'budgetIds', 'accountSummary', 'budgetSummary', 'averageExpenses', 'topExpenses')) + try { + $result= view('reports.budget.month', compact('accountIds', 'budgetIds', 'accountSummary', 'budgetSummary', 'averageExpenses', 'topExpenses')) ->with('start', $this->start)->with('end', $this->end) ->with('budgets', $this->budgets) ->with('accounts', $this->accounts) ->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } + return $result; } /** diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index ef187a575b..d454d85dd8 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -36,6 +36,7 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -68,7 +69,6 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface * Generates the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -85,24 +85,23 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $topIncome = $this->getTopIncome(); // render! - return view( - 'reports.category.month', - compact( - 'accountIds', - 'categoryIds', - 'topIncome', - 'reportType', - 'accountSummary', - 'categorySummary', - 'averageExpenses', - 'averageIncome', - 'topExpenses' + try { + return view( + 'reports.category.month', compact( + 'accountIds', 'categoryIds', 'topIncome', 'reportType', 'accountSummary', 'categorySummary', 'averageExpenses', + 'averageIncome', 'topExpenses' + ) ) - ) - ->with('start', $this->start)->with('end', $this->end) - ->with('categories', $this->categories) - ->with('accounts', $this->accounts) - ->render(); + ->with('start', $this->start)->with('end', $this->end) + ->with('categories', $this->categories) + ->with('accounts', $this->accounts) + ->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.category.month: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } + + return $result; } /** diff --git a/app/Generator/Report/Standard/MonthReportGenerator.php b/app/Generator/Report/Standard/MonthReportGenerator.php index e27f4d79ed..ad0a9c0b99 100644 --- a/app/Generator/Report/Standard/MonthReportGenerator.php +++ b/app/Generator/Report/Standard/MonthReportGenerator.php @@ -26,6 +26,8 @@ use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Helpers\Report\ReportHelperInterface; use Illuminate\Support\Collection; +use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -43,7 +45,6 @@ class MonthReportGenerator implements ReportGeneratorInterface * Generates the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -53,11 +54,17 @@ class MonthReportGenerator implements ReportGeneratorInterface $accountIds = implode(',', $this->accounts->pluck('id')->toArray()); $reportType = 'default'; - // continue! - return view( - 'reports.default.month', - compact('bills', 'accountIds', 'reportType') - )->with('start', $this->start)->with('end', $this->end)->render(); + try { + return view( + 'reports.default.month', + compact('bills', 'accountIds', 'reportType') + )->with('start', $this->start)->with('end', $this->end)->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.default.month: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } + + return $result; } /** diff --git a/app/Generator/Report/Standard/MultiYearReportGenerator.php b/app/Generator/Report/Standard/MultiYearReportGenerator.php index ec507b9386..9310554b33 100644 --- a/app/Generator/Report/Standard/MultiYearReportGenerator.php +++ b/app/Generator/Report/Standard/MultiYearReportGenerator.php @@ -25,6 +25,8 @@ namespace FireflyIII\Generator\Report\Standard; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use Illuminate\Support\Collection; +use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -42,7 +44,6 @@ class MultiYearReportGenerator implements ReportGeneratorInterface * Generates the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -50,11 +51,17 @@ class MultiYearReportGenerator implements ReportGeneratorInterface $accountIds = implode(',', $this->accounts->pluck('id')->toArray()); $reportType = 'default'; - // continue! - return view( - 'reports.default.multi-year', - compact('accountIds', 'reportType') - )->with('start', $this->start)->with('end', $this->end)->render(); + try { + return view( + 'reports.default.multi-year', + compact('accountIds', 'reportType') + )->with('start', $this->start)->with('end', $this->end)->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.default.multi-year: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } + + return $result; } /** diff --git a/app/Generator/Report/Standard/YearReportGenerator.php b/app/Generator/Report/Standard/YearReportGenerator.php index 47f92f47ad..bec85e462a 100644 --- a/app/Generator/Report/Standard/YearReportGenerator.php +++ b/app/Generator/Report/Standard/YearReportGenerator.php @@ -25,6 +25,8 @@ namespace FireflyIII\Generator\Report\Standard; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use Illuminate\Support\Collection; +use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -42,7 +44,6 @@ class YearReportGenerator implements ReportGeneratorInterface * Generates the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -50,13 +51,20 @@ class YearReportGenerator implements ReportGeneratorInterface $accountIds = implode(',', $this->accounts->pluck('id')->toArray()); $reportType = 'default'; - // continue! - return view( - 'reports.default.year', - compact('accountIds', 'reportType') - )->with('start', $this->start)->with('end', $this->end)->render(); + try { + $result = view( + 'reports.default.year', + compact('accountIds', 'reportType') + )->with('start', $this->start)->with('end', $this->end)->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } + + return $result; } + /** * Set the accounts. * diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php index e367283624..84532e19f1 100644 --- a/app/Generator/Report/Tag/MonthReportGenerator.php +++ b/app/Generator/Report/Tag/MonthReportGenerator.php @@ -38,6 +38,7 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; +use Throwable; /** * Class MonthReportGenerator. @@ -70,7 +71,6 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface * Generate the report. * * @return string - * @throws \Throwable */ public function generate(): string { @@ -87,20 +87,18 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $topIncome = $this->getTopIncome(); // render! - return view( - 'reports.tag.month', - compact( - 'accountIds', - 'tagTags', - 'reportType', - 'accountSummary', - 'tagSummary', - 'averageExpenses', - 'averageIncome', - 'topIncome', - 'topExpenses' + try { + $result = view( + 'reports.tag.month', compact( + 'accountIds', 'tagTags', 'reportType', 'accountSummary', 'tagSummary', 'averageExpenses', 'averageIncome', 'topIncome', 'topExpenses' ) - )->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render(); + )->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.tag.month: %s', $e->getMessage())); + $result = 'Could not render report view.'; + } + + return $result; } /** diff --git a/app/Http/Controllers/Budget/ShowController.php b/app/Http/Controllers/Budget/ShowController.php index 0b48280422..3e81b43390 100644 --- a/app/Http/Controllers/Budget/ShowController.php +++ b/app/Http/Controllers/Budget/ShowController.php @@ -142,7 +142,7 @@ class ShowController extends Controller public function show(Request $request, Budget $budget) { /** @var Carbon $start */ - $start = session('first', Carbon::create()->startOfYear()); + $start = session('first', Carbon::now()->startOfYear()); $end = new Carbon; $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; @@ -196,7 +196,7 @@ class ShowController extends Controller $transactions = $collector->getPaginatedJournals(); $transactions->setPath(route('budgets.show', [$budget->id, $budgetLimit->id])); /** @var Carbon $start */ - $start = session('first', Carbon::create()->startOfYear()); + $start = session('first', Carbon::now()->startOfYear()); $end = new Carbon; $limits = $this->getLimits($budget, $start, $end); diff --git a/app/Http/Controllers/Category/ShowController.php b/app/Http/Controllers/Category/ShowController.php index eb0e54edfd..623cdaab9d 100644 --- a/app/Http/Controllers/Category/ShowController.php +++ b/app/Http/Controllers/Category/ShowController.php @@ -90,9 +90,9 @@ class ShowController extends Controller { Log::debug('Now in show()'); /** @var Carbon $start */ - $start = $start ?? session('start', Carbon::create()->startOfMonth()); + $start = $start ?? session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ - $end = $end ?? session('end', Carbon::create()->startOfMonth()); + $end = $end ?? session('end', Carbon::now()->startOfMonth()); $subTitleIcon = 'fa-bar-chart'; $moment = ''; $page = (int)$request->get('page'); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 958c51f017..0735d60db9 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -120,7 +120,7 @@ class DebugController extends Controller $phpVersion = str_replace($search, $replace, PHP_VERSION); $phpOs = str_replace($search, $replace, PHP_OS); $interface = PHP_SAPI; - $now = Carbon::create()->format('Y-m-d H:i:s e'); + $now = Carbon::now()->format('Y-m-d H:i:s e'); $extensions = implode(', ', get_loaded_extensions()); $drivers = implode(', ', DB::availableDrivers()); $currentDriver = DB::getDriverName(); diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 8426d5b2f7..102a10dbb3 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -125,7 +125,7 @@ class ExportController extends Controller $formats = array_keys(config('firefly.export_formats')); $defaultFormat = app('preferences')->get('export_format', config('firefly.default_export_format'))->data; $first = session('first')->format('Y-m-d'); - $today = Carbon::create()->format('Y-m-d'); + $today = Carbon::now()->format('Y-m-d'); return view('export.index', compact('job', 'formats', 'defaultFormat', 'first', 'today')); } diff --git a/app/Http/Controllers/Import/CallbackController.php b/app/Http/Controllers/Import/CallbackController.php index 9f7f7c62f6..2fb1e0b612 100644 --- a/app/Http/Controllers/Import/CallbackController.php +++ b/app/Http/Controllers/Import/CallbackController.php @@ -36,6 +36,8 @@ class CallbackController extends Controller { /** + * Callback specifically for YNAB logins. + * * @param Request $request * * @param ImportJobRepositoryInterface $repository diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index bf45a8c6c3..b24ab51fbb 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -37,7 +37,7 @@ use Log; */ class IndexController extends Controller { - /** @var array */ + /** @var array All available providers */ public $providers; /** @var ImportJobRepositoryInterface The import job repository */ public $repository; diff --git a/app/Http/Controllers/Json/FrontpageController.php b/app/Http/Controllers/Json/FrontpageController.php index b27dec9585..7a8247b79e 100644 --- a/app/Http/Controllers/Json/FrontpageController.php +++ b/app/Http/Controllers/Json/FrontpageController.php @@ -26,6 +26,8 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\PiggyBank; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Http\JsonResponse; +use Log; +use Throwable; /** * Class FrontpageController. @@ -38,7 +40,6 @@ class FrontpageController extends Controller * @param PiggyBankRepositoryInterface $repository * * @return JsonResponse - * @throws \Throwable */ public function piggyBanks(PiggyBankRepositoryInterface $repository): JsonResponse { @@ -64,7 +65,12 @@ class FrontpageController extends Controller } $html = ''; if (\count($info) > 0) { - $html = view('json.piggy-banks', compact('info'))->render(); + try { + $html = view('json.piggy-banks', compact('info'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render json.piggy-banks: %s', $e->getMessage())); + $html = 'Could not render view.'; + } } return response()->json(['html' => $html]); diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index a18561b929..a270d98a8b 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -24,6 +24,8 @@ namespace FireflyIII\Http\Controllers; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Log; +use Throwable; /** * Class JsonController. @@ -36,7 +38,6 @@ class JsonController extends Controller * @param Request $request * * @return JsonResponse - * @throws \Throwable */ public function action(Request $request): JsonResponse { @@ -46,7 +47,12 @@ class JsonController extends Controller foreach ($keys as $key) { $actions[$key] = (string)trans('firefly.rule_action_' . $key . '_choice'); } - $view = view('rules.partials.action', compact('actions', 'count'))->render(); + try { + $view = view('rules.partials.action', compact('actions', 'count'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render rules.partials.action: %s', $e->getMessage())); + $view = 'Could not render view.'; + } return response()->json(['html' => $view]); } @@ -57,7 +63,6 @@ class JsonController extends Controller * @param Request $request * * @return JsonResponse - * @throws \Throwable */ public function trigger(Request $request): JsonResponse { @@ -71,7 +76,12 @@ class JsonController extends Controller } asort($triggers); - $view = view('rules.partials.trigger', compact('triggers', 'count'))->render(); + try { + $view = view('rules.partials.trigger', compact('triggers', 'count'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render rules.partials.trigger: %s', $e->getMessage())); + $view = 'Could not render view.'; + } return response()->json(['html' => $view]); } diff --git a/app/Http/Controllers/Popup/ReportController.php b/app/Http/Controllers/Popup/ReportController.php index d07a09ab63..8e7d3e3cb6 100644 --- a/app/Http/Controllers/Popup/ReportController.php +++ b/app/Http/Controllers/Popup/ReportController.php @@ -274,7 +274,7 @@ class ReportController extends Controller $attributes['startDate'] = Carbon::createFromFormat('Ymd', $attributes['startDate']); } catch (InvalidArgumentException $e) { Log::debug(sprintf('Not important error message: %s', $e->getMessage())); - $date = Carbon::create()->startOfMonth(); + $date = Carbon::now()->startOfMonth(); $attributes['startDate'] = $date; } @@ -282,7 +282,7 @@ class ReportController extends Controller $attributes['endDate'] = Carbon::createFromFormat('Ymd', $attributes['endDate']); } catch (InvalidArgumentException $e) { Log::debug(sprintf('Not important error message: %s', $e->getMessage())); - $date = Carbon::create()->startOfMonth(); + $date = Carbon::now()->startOfMonth(); $attributes['endDate'] = $date; } diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php index 04a5307c0b..b5223dd5b7 100644 --- a/app/Http/Controllers/Report/OperationsController.php +++ b/app/Http/Controllers/Report/OperationsController.php @@ -38,7 +38,7 @@ class OperationsController extends Controller private $tasker; /** - * + * OperationsController constructor. */ public function __construct() { diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index ff77965966..df5d3baa9c 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -36,6 +36,7 @@ use FireflyIII\Repositories\Tag\TagRepositoryInterface; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Collection; use Log; +use Throwable; /** * Class ReportController. @@ -277,7 +278,6 @@ class ReportController extends Controller * * @return mixed * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @throws \Throwable */ public function options(string $reportType) { @@ -391,7 +391,6 @@ class ReportController extends Controller * @param Carbon $end * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string - * * @throws \FireflyIII\Exceptions\FireflyException */ public function tagReport(Collection $accounts, Collection $tags, Carbon $start, Carbon $end) @@ -423,7 +422,6 @@ class ReportController extends Controller * Get options for account report. * * @return string - * @throws \Throwable */ private function accountReportOptions(): string { @@ -438,52 +436,77 @@ class ReportController extends Controller $set->push($exp); } } + try { + $result = view('reports.options.account', compact('set'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.options.tag: %s', $e->getMessage())); + $result = 'Could not render view.'; + } - return view('reports.options.account', compact('set'))->render(); + return $result; } /** * Get options for budget report. + * * @return string - * @throws \Throwable */ private function budgetReportOptions(): string { /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); $budgets = $repository->getBudgets(); + try { + $result = view('reports.options.budget', compact('budgets'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.options.tag: %s', $e->getMessage())); + $result = 'Could not render view.'; + } - return view('reports.options.budget', compact('budgets'))->render(); + return $result; } /** * Get options for category report. + * * @return string - * @throws \Throwable */ private function categoryReportOptions(): string { /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); $categories = $repository->getCategories(); + try { + $result = view('reports.options.category', compact('categories'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.options.category: %s', $e->getMessage())); + $result = 'Could not render view.'; + } - return view('reports.options.category', compact('categories'))->render(); + return $result; } /** * Get options for default report. + * * @return string - * @throws \Throwable */ private function noReportOptions(): string { - return view('reports.options.no-options')->render(); + try { + $result = view('reports.options.no-options')->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.options.no-options: %s', $e->getMessage())); + $result = 'Could not render view.'; + } + + return $result; } /** * Get options for tag report. + * * @return string - * @throws \Throwable */ private function tagReportOptions(): string { @@ -494,7 +517,13 @@ class ReportController extends Controller return $tag->tag; } ); + try { + $result = view('reports.options.tag', compact('tags'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render reports.options.tag: %s', $e->getMessage())); + $result = 'Could not render view.'; + } - return view('reports.options.tag', compact('tags'))->render(); + return $result; } } diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index 6381fbf21c..b8c30229f2 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -118,7 +118,7 @@ class SelectController extends Controller { // does the user have shared accounts? $first = session('first')->format('Y-m-d'); - $today = Carbon::create()->format('Y-m-d'); + $today = Carbon::now()->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')); diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php index c814776eb8..8a99cd2ab7 100644 --- a/app/Http/Controllers/RuleGroupController.php +++ b/app/Http/Controllers/RuleGroupController.php @@ -208,7 +208,7 @@ class RuleGroupController extends Controller public function selectTransactions(RuleGroup $ruleGroup) { $first = session('first')->format('Y-m-d'); - $today = Carbon::create()->format('Y-m-d'); + $today = Carbon::now()->format('Y-m-d'); $subTitle = (string)trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); return view('rules.rule-group.select-transactions', compact('first', 'today', 'ruleGroup', 'subTitle')); diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 6308bd1770..57e1b9e949 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -27,6 +27,8 @@ use FireflyIII\Support\Search\SearchInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Collection; +use Log; +use Throwable; /** * Class SearchController. @@ -77,7 +79,6 @@ class SearchController extends Controller * @param SearchInterface $searcher * * @return \Illuminate\Http\JsonResponse - * @throws \Throwable */ public function search(Request $request, SearchInterface $searcher): JsonResponse { @@ -99,8 +100,12 @@ class SearchController extends Controller $transactions = $searcher->searchTransactions(); $cache->store($transactions); } - - $html = view('search.search', compact('transactions'))->render(); + try { + $html = view('search.search', compact('transactions'))->render(); + } catch (Throwable $e) { + Log::error(sprintf('Cannot render search.search: %s', $e->getMessage())); + $html = 'Could not render view.'; + } return response()->json(['count' => $transactions->count(), 'html' => $html]); } diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index c9685012a6..a989dda208 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -262,7 +262,7 @@ class TransactionController extends Controller { $range = app('preferences')->get('viewRange', '1M')->data; $first = $this->repository->firstNull(); - $start = Carbon::create()->subYear(); + $start = Carbon::now()->subYear(); $types = config('firefly.transactionTypesByWhat.' . $what); $entries = new Collection; if (null !== $first) { diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index f79500515a..d347aa7a9e 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -48,7 +48,7 @@ class AccountFormRequest extends Request */ public function getAccountData(): array { - return [ + $data = [ 'name' => $this->string('name'), 'active' => $this->boolean('active'), 'accountType' => $this->string('what'), @@ -64,7 +64,22 @@ class AccountFormRequest extends Request 'ccType' => $this->string('ccType'), 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'), 'notes' => $this->string('notes'), + 'interest' => $this->string('interest'), + 'interest_period' => $this->string('interest_period'), ]; + + // if the account type is "liabilities" there are actually four types of liability + // that could have been selected. + if ($data['accountType'] === 'liabilities') { + $data['accountType'] = null; + $data['account_type_id'] = $this->integer('liability_type_id'); + // also reverse the opening balance: + if ('' !== $data['openingBalance']) { + $data['openingBalance'] = bcmul($data['openingBalance'], '-1'); + } + } + + return $data; } /** @@ -93,8 +108,14 @@ class AccountFormRequest extends Request 'amount_currency_id_openingBalance' => 'exists:transaction_currencies,id', 'amount_currency_id_virtualBalance' => 'exists:transaction_currencies,id', 'what' => 'in:' . $types, + 'interest_period' => 'in:daily,monthly,yearly', ]; + if ('liabilities' === $this->get('what')) { + $rules['openingBalance'] = 'numeric|required|more:0'; + $rules['openingBalanceDate'] = 'date|required'; + } + /** @var Account $account */ $account = $this->route()->parameter('account'); if (null !== $account) { diff --git a/app/Http/Requests/ExportFormRequest.php b/app/Http/Requests/ExportFormRequest.php index 942aca6870..d19441209c 100644 --- a/app/Http/Requests/ExportFormRequest.php +++ b/app/Http/Requests/ExportFormRequest.php @@ -50,7 +50,7 @@ class ExportFormRequest extends Request /** @var Carbon $sessionFirst */ $sessionFirst = clone session('first'); $first = $sessionFirst->subDay()->format('Y-m-d'); - $today = Carbon::create()->addDay()->format('Y-m-d'); + $today = Carbon::now()->addDay()->format('Y-m-d'); $formats = implode(',', array_keys(config('firefly.export_formats'))); // fixed diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index 47c389922e..7f2fba4f03 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -149,7 +149,7 @@ class RecurrenceFormRequest extends Request public function rules(): array { $today = new Carbon; - $tomorrow = Carbon::create()->addDay(); + $tomorrow = Carbon::now()->addDay(); $rules = [ // mandatory info for recurrence. 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', diff --git a/app/Http/Requests/SelectTransactionsRequest.php b/app/Http/Requests/SelectTransactionsRequest.php index 9207a51159..5697fdfb62 100644 --- a/app/Http/Requests/SelectTransactionsRequest.php +++ b/app/Http/Requests/SelectTransactionsRequest.php @@ -53,7 +53,7 @@ class SelectTransactionsRequest extends Request /** @var Carbon $sessionFirst */ $sessionFirst = clone session('first'); $first = $sessionFirst->subDay()->format('Y-m-d'); - $today = Carbon::create()->addDay()->format('Y-m-d'); + $today = Carbon::now()->addDay()->format('Y-m-d'); return [ 'start_date' => 'required|date|after:' . $first, diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 1a736c5acc..ebe5eeb859 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -26,6 +26,7 @@ namespace FireflyIII\Import\Storage; use Carbon\Carbon; use DB; +use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; @@ -114,6 +115,9 @@ class ImportArrayStorage app('preferences')->mark(); + // email about this: + event(new RequestedReportOnJournals($this->importJob->user_id, $collection)); + return $collection; } diff --git a/app/Models/Account.php b/app/Models/Account.php index ba5aef5da0..b4d40cc1af 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -75,18 +75,20 @@ class Account extends Model 'active' => 'boolean', 'encrypted' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban']; - /** @var array */ + /** @var array Hidden from view */ protected $hidden = ['encrypted']; /** @var bool */ private $joinedAccountTypes; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Account - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Account { diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index 4f6e68e52c..ff36c2ec93 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -44,11 +44,9 @@ class AccountMeta extends Model 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['account_id', 'name', 'data']; - /** - * @var string - */ + /** @var string The table to store the data in */ protected $table = 'account_meta'; /** diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index 1446d0d6e3..2faad2e91a 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -35,25 +35,43 @@ use Illuminate\Database\Eloquent\Relations\HasMany; */ class AccountType extends Model { + /** @var string */ public const DEFAULT = 'Default account'; + /** @var string */ public const CASH = 'Cash account'; + /** @var string */ public const ASSET = 'Asset account'; + /** @var string */ public const EXPENSE = 'Expense account'; + /** @var string */ public const REVENUE = 'Revenue account'; + /** @var string */ public const INITIAL_BALANCE = 'Initial balance account'; + /** @var string */ public const BENEFICIARY = 'Beneficiary account'; + /** @var string */ public const IMPORT = 'Import account'; + /** @var string */ public const RECONCILIATION = 'Reconciliation account'; + /** @var string */ public const LOAN = 'Loan'; + /** @var string */ public const DEBT = 'Debt'; + /** @var string */ public const MORTGAGE = 'Mortgage'; + /** @var string */ public const CREDITCARD = 'Credit card'; + /** + * The attributes that should be casted to native types. + * + * @var array + */ protected $casts = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['type']; /** diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index 591f2dfa71..967316202b 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -66,14 +66,16 @@ class Attachment extends Model 'deleted_at' => 'datetime', 'uploaded' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['attachable_id', 'attachable_type', 'user_id', 'md5', 'filename', 'mime', 'title', 'description', 'size', 'uploaded']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Attachment - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Attachment { diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 4950cb7661..0e41aaf521 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -58,14 +58,16 @@ class AvailableBudget extends Model 'start_date' => 'date', 'end_date' => 'date', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return AvailableBudget - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): AvailableBudget { @@ -84,7 +86,7 @@ class AvailableBudget extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function transactionCurrency(): BelongsTo { diff --git a/app/Models/Bill.php b/app/Models/Bill.php index f0b0ce6711..2354c460f0 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -77,22 +77,21 @@ class Bill extends Model 'name_encrypted' => 'boolean', 'match_encrypted' => 'boolean', ]; - /** - * @var array - */ + + /** @var array Fields that can be filled */ protected $fillable = ['name', 'match', 'amount_min', 'match_encrypted', 'name_encrypted', 'user_id', 'amount_max', 'date', 'repeat_freq', 'skip', 'automatch', 'active', 'transaction_currency_id']; - /** - * @var array - */ + /** @var array Hidden from view */ protected $hidden = ['amount_min_encrypted', 'amount_max_encrypted', 'name_encrypted', 'match_encrypted']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Bill - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Bill { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index ab87f02080..106739fcec 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -26,6 +26,8 @@ use Crypt; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -58,16 +60,18 @@ class Budget extends Model 'active' => 'boolean', 'encrypted' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'name', 'active']; - /** @var array */ + /** @var array Hidden from view */ protected $hidden = ['encrypted']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Budget - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Budget { @@ -86,9 +90,9 @@ class Budget extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function budgetlimits(): \Illuminate\Database\Eloquent\Relations\HasMany + public function budgetlimits(): HasMany { return $this->hasMany(BudgetLimit::class); } @@ -126,18 +130,18 @@ class Budget extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function transactionJournals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function transactionJournals(): BelongsToMany { return $this->belongsToMany(TransactionJournal::class, 'budget_transaction_journal', 'budget_id'); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function transactions(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function transactions(): BelongsToMany { return $this->belongsToMany(Transaction::class, 'budget_transaction', 'budget_id'); } diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index 15f11689a2..2306e375de 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -54,13 +54,17 @@ class BudgetLimit extends Model 'start_date' => 'date', 'end_date' => 'date', ]; + + /** @var array Fields that can be filled */ protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return mixed - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): BudgetLimit { diff --git a/app/Models/Category.php b/app/Models/Category.php index 359dd039e3..92e657d78f 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -18,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with Firefly III. If not, see . */ + declare(strict_types=1); namespace FireflyIII\Models; @@ -27,6 +28,7 @@ use Crypt; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -55,16 +57,18 @@ class Category extends Model 'deleted_at' => 'datetime', 'encrypted' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'name']; - /** @var array */ + /** @var array Hidden from view */ protected $hidden = ['encrypted']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Category - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Category { @@ -114,18 +118,18 @@ class Category extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function transactionJournals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function transactionJournals(): BelongsToMany { return $this->belongsToMany(TransactionJournal::class, 'category_transaction_journal', 'category_id'); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function transactions(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function transactions(): BelongsToMany { return $this->belongsToMany(Transaction::class, 'category_transaction', 'category_id'); } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 76eae0f9ed..771f9a86b2 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -44,10 +44,9 @@ class Configuration extends Model = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', ]; - /** - * @var string - */ + /** @var string The table to store the data in */ protected $table = 'configuration'; /** diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index e77bb2484c..203b4d6bd2 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -43,8 +43,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; */ class CurrencyExchangeRate extends Model { - /** @var array */ - protected $dates = ['date']; + /** @var array Convert these fields to other data types */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'user_id' => 'int', + 'from_currency_id' => 'int', + 'to_currency_id' => 'int', + 'date' => 'datetime', + ]; /** * @codeCoverageIgnore diff --git a/app/Models/ExportJob.php b/app/Models/ExportJob.php index 593b3ebc64..474d29f9da 100644 --- a/app/Models/ExportJob.php +++ b/app/Models/ExportJob.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -37,7 +38,11 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class ExportJob extends Model { - /** @var array */ + /** + * The attributes that should be casted to native types. + * + * @var array + */ protected $casts = [ 'created_at' => 'datetime', @@ -45,6 +50,8 @@ class ExportJob extends Model ]; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return ExportJob @@ -67,9 +74,12 @@ class ExportJob extends Model } /** - * @codeCoverageIgnore + * Change the status of this export job. * * @param $status + * + * @deprecated + * @codeCoverageIgnore */ public function change($status): void { @@ -78,10 +88,13 @@ class ExportJob extends Model } /** + * Returns the user this objects belongs to. + * + * + * @return BelongsTo * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index 6957faaf2e..ab8392a1f7 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -61,10 +62,12 @@ class ImportJob extends Model 'transactions' => 'array', 'errors' => 'array', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['key', 'user_id', 'file_type', 'provider', 'status', 'stage', 'configuration', 'extended_status', 'transactions', 'errors']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param $value * * @return mixed @@ -97,18 +100,18 @@ class ImportJob extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function tag(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function tag(): BelongsTo { return $this->belongsTo(Tag::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index f5f1bd9e86..0e44074d61 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\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -55,10 +56,12 @@ class LinkType extends Model 'editable' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['name', 'inward', 'outward', 'editable']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param $value * * @return LinkType @@ -79,9 +82,9 @@ class LinkType extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function transactionJournalLinks(): \Illuminate\Database\Eloquent\Relations\HasMany + public function transactionJournalLinks(): HasMany { return $this->hasMany(TransactionJournalLink::class); } diff --git a/app/Models/Note.php b/app/Models/Note.php index ee4accbce0..d67b00d82b 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -51,7 +51,7 @@ class Note extends Model 'updated_at' => 'datetime', 'deleted_at' => 'datetime', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['title', 'text', 'noteable_id', 'noteable_type']; /** diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 7436b5c35c..360d4b7c01 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -26,6 +26,7 @@ use Carbon\Carbon; use Crypt; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -67,18 +68,18 @@ class PiggyBank extends Model 'active' => 'boolean', 'encrypted' => 'boolean', ]; - /** @var array */ - protected $dates = ['startdate', 'targetdate']; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['name', 'account_id', 'order', 'targetamount', 'startdate', 'targetdate', 'active']; - /** @var array */ + /** @var array Hidden from view */ protected $hidden = ['targetamount_encrypted', 'encrypted']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return PiggyBank - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): PiggyBank { @@ -96,7 +97,7 @@ class PiggyBank extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function account(): BelongsTo { @@ -131,18 +132,18 @@ class PiggyBank extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function piggyBankEvents(): \Illuminate\Database\Eloquent\Relations\HasMany + public function piggyBankEvents(): HasMany { return $this->hasMany(PiggyBankEvent::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function piggyBankRepetitions(): \Illuminate\Database\Eloquent\Relations\HasMany + public function piggyBankRepetitions(): HasMany { return $this->hasMany(PiggyBankRepetition::class); } diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index e01ed0a677..3cb7541896 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class PiggyBankEvent. @@ -47,22 +48,16 @@ class PiggyBankEvent extends Model 'updated_at' => 'datetime', 'date' => 'date', ]; - /** @var array */ - protected $dates = ['date']; - /** - * @var array - */ + /** @var array Fields that can be filled */ protected $fillable = ['piggy_bank_id', 'transaction_journal_id', 'date', 'amount']; - /** - * @var array - */ + /** @var array Hidden from view */ protected $hidden = ['amount_encrypted']; /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function piggyBank(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function piggyBank(): BelongsTo { return $this->belongsTo(PiggyBank::class); } @@ -79,9 +74,9 @@ class PiggyBankEvent extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionJournal(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function transactionJournal(): BelongsTo { return $this->belongsTo(TransactionJournal::class); } diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index a021ce61b6..450b91addc 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -25,6 +25,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class PiggyBankRepetition. @@ -44,20 +45,17 @@ class PiggyBankRepetition extends Model = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', 'startdate' => 'date', 'targetdate' => 'date', ]; - /** @var array */ - protected $dates = ['startdate', 'targetdate']; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['piggy_bank_id', 'startdate', 'targetdate', 'currentamount']; /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function piggyBank(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function piggyBank(): BelongsTo { return $this->belongsTo(PiggyBank::class); } diff --git a/app/Models/Preference.php b/app/Models/Preference.php index cc9ccc0b15..a7e28d2bad 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Log; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -55,14 +56,16 @@ class Preference extends Model 'updated_at' => 'datetime', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'data', 'name']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Preference - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Preference { @@ -129,9 +132,9 @@ class Preference extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php index 243eece7b6..0c4ac5a9c0 100644 --- a/app/Models/Recurrence.php +++ b/app/Models/Recurrence.php @@ -82,17 +82,19 @@ class Recurrence extends Model 'active' => 'bool', 'apply_rules' => 'bool', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active']; - /** @var string */ + /** @var string The table to store the data in */ protected $table = 'recurrences'; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Recurrence - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Recurrence { diff --git a/app/Models/RecurrenceMeta.php b/app/Models/RecurrenceMeta.php index dbd7424917..2dd414c907 100644 --- a/app/Models/RecurrenceMeta.php +++ b/app/Models/RecurrenceMeta.php @@ -37,7 +37,11 @@ use Illuminate\Database\Eloquent\SoftDeletes; class RecurrenceMeta extends Model { use SoftDeletes; - /** @var array */ + /** + * The attributes that should be casted to native types. + * + * @var array + */ protected $casts = [ 'created_at' => 'datetime', @@ -46,9 +50,9 @@ class RecurrenceMeta extends Model 'name' => 'string', 'value' => 'string', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['recurrence_id', 'name', 'value']; - /** @var string */ + /** @var string The table to store the data in */ protected $table = 'recurrences_meta'; /** diff --git a/app/Models/RecurrenceRepetition.php b/app/Models/RecurrenceRepetition.php index 89c54def54..cc26a97378 100644 --- a/app/Models/RecurrenceRepetition.php +++ b/app/Models/RecurrenceRepetition.php @@ -51,7 +51,11 @@ class RecurrenceRepetition extends Model /** @var int */ public const WEEKEND_TO_MONDAY = 4; use SoftDeletes; - /** @var array */ + /** + * The attributes that should be casted to native types. + * + * @var array + */ protected $casts = [ 'created_at' => 'datetime', @@ -62,8 +66,9 @@ class RecurrenceRepetition extends Model 'repetition_skip' => 'int', 'weekend' => 'int', ]; + /** @var array Fields that can be filled */ protected $fillable = ['recurrence_id', 'weekend', 'repetition_type', 'repetition_moment', 'repetition_skip']; - /** @var string */ + /** @var string The table to store the data in */ protected $table = 'recurrences_repetitions'; /** diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php index 62ffd49d10..14812b9068 100644 --- a/app/Models/RecurrenceTransaction.php +++ b/app/Models/RecurrenceTransaction.php @@ -51,7 +51,11 @@ use Illuminate\Database\Eloquent\SoftDeletes; class RecurrenceTransaction extends Model { use SoftDeletes; - /** @var array */ + /** + * The attributes that should be casted to native types. + * + * @var array + */ protected $casts = [ 'created_at' => 'datetime', @@ -61,16 +65,16 @@ class RecurrenceTransaction extends Model 'foreign_amount' => 'string', 'description' => 'string', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_id', 'destination_id', 'amount', 'foreign_amount', 'description']; - /** @var string */ + /** @var string The table to store the data in */ protected $table = 'recurrences_transactions'; /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function destinationAccount(): BelongsTo { @@ -106,7 +110,7 @@ class RecurrenceTransaction extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function sourceAccount(): BelongsTo { diff --git a/app/Models/RecurrenceTransactionMeta.php b/app/Models/RecurrenceTransactionMeta.php index e39aff977e..1f283ad0db 100644 --- a/app/Models/RecurrenceTransactionMeta.php +++ b/app/Models/RecurrenceTransactionMeta.php @@ -37,7 +37,11 @@ use Illuminate\Database\Eloquent\SoftDeletes; class RecurrenceTransactionMeta extends Model { use SoftDeletes; - /** @var array */ + /** + * The attributes that should be casted to native types. + * + * @var array + */ protected $casts = [ 'created_at' => 'datetime', @@ -46,8 +50,9 @@ class RecurrenceTransactionMeta extends Model 'name' => 'string', 'value' => 'string', ]; + /** @var array Fields that can be filled */ protected $fillable = ['rt_id', 'name', 'value']; - /** @var string */ + /** @var string The table to store the data in */ protected $table = 'rt_meta'; /** diff --git a/app/Models/Role.php b/app/Models/Role.php index c262631e60..319629e02f 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -45,14 +45,12 @@ class Role extends Model 'updated_at' => 'datetime', ]; - /** - * @var array - */ + /** @var array Fields that can be filled */ protected $fillable = ['name', 'display_name', 'description']; /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ public function users(): BelongsToMany { diff --git a/app/Models/Rule.php b/app/Models/Rule.php index d7fbc0ba00..328ac6039d 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -70,14 +70,16 @@ class Rule extends Model 'id' => 'int', 'strict' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['rule_group_id', 'order', 'active', 'title', 'description', 'user_id', 'strict']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Rule - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Rule { diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index b0b7e9dcd3..0488e15806 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class RuleAction. @@ -54,14 +55,14 @@ class RuleAction extends Model 'stop_processing' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['rule_id', 'action_type', 'action_value', 'order', 'active', 'stop_processing']; /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function rule(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function rule(): BelongsTo { return $this->belongsTo(Rule::class); } diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index bc2840175b..233ef34165 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -25,6 +25,8 @@ namespace FireflyIII\Models; use Carbon\Carbon; 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; @@ -60,16 +62,16 @@ class RuleGroup extends Model 'order' => 'int', ]; - /** - * @var array - */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'order', 'title', 'description', 'active']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return RuleGroup - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): RuleGroup { @@ -88,18 +90,18 @@ class RuleGroup extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function rules(): \Illuminate\Database\Eloquent\Relations\HasMany + public function rules(): HasMany { return $this->hasMany(Rule::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index 7c8da2f6e2..e2b8422251 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -24,6 +24,7 @@ namespace FireflyIII\Models; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class RuleTrigger. @@ -53,14 +54,14 @@ class RuleTrigger extends Model 'stop_processing' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['rule_id', 'trigger_type', 'trigger_value', 'order', 'active', 'stop_processing']; /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function rule(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function rule(): BelongsTo { return $this->belongsTo(Rule::class); } diff --git a/app/Models/Tag.php b/app/Models/Tag.php index f0e5c900dc..699c8991ee 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -25,6 +25,8 @@ namespace FireflyIII\Models; use Crypt; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -60,16 +62,16 @@ class Tag extends Model 'date' => 'date', 'zoomLevel' => 'int', ]; - /** @var array */ - protected $dates = ['date']; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'tag', 'date', 'description', 'longitude', 'latitude', 'zoomLevel', 'tagMode']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Tag - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Tag { @@ -146,18 +148,18 @@ class Tag extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function transactionJournals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function transactionJournals(): BelongsToMany { return $this->belongsToMany(TransactionJournal::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 2098c6788b..d20fbf8778 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -27,6 +27,7 @@ use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -100,6 +101,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class Transaction extends Model { + use SoftDeletes; /** * The attributes that should be casted to native types. * @@ -115,24 +117,23 @@ class Transaction extends Model 'bill_name_encrypted' => 'boolean', 'reconciled' => 'boolean', ]; - /** - * @var array - */ + /** @var array Fields that can be filled */ protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier', 'transaction_currency_id', 'foreign_currency_id', 'foreign_amount', 'reconciled']; - /** - * @var array - */ + /** @var array Hidden from view */ protected $hidden = ['encrypted']; /** - * @codeCoverageIgnore + * Check if a table is joined. + * + * * * @param Builder $query * @param string $table * * @return bool + * @codeCoverageIgnore */ public static function isJoined(Builder $query, string $table): bool { @@ -150,10 +151,12 @@ class Transaction extends Model } /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return Transaction - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): Transaction { @@ -171,11 +174,12 @@ class Transaction extends Model throw new NotFoundHttpException; } - use SoftDeletes; /** + * Get the account this object belongs to. + * * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function account(): BelongsTo { @@ -183,26 +187,32 @@ class Transaction extends Model } /** + * Get the budget(s) this object belongs to. + * * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function budgets(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function budgets(): BelongsToMany { return $this->belongsToMany(Budget::class); } /** + * Get the category(ies) this object belongs to. + * * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ - public function categories(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + public function categories(): BelongsToMany { return $this->belongsToMany(Category::class); } /** + * Get the currency this object belongs to. + * * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function foreignCurrency(): BelongsTo { @@ -210,6 +220,8 @@ class Transaction extends Model } /** + * Check for transactions AFTER a specified date. + * * @codeCoverageIgnore * * @param Builder $query @@ -224,6 +236,7 @@ class Transaction extends Model } /** + * Check for transactions BEFORE the specified date. * @codeCoverageIgnore * * @param Builder $query diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index c44d6d350b..08358b492d 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -52,16 +53,16 @@ class TransactionCurrency extends Model 'deleted_at' => 'datetime', 'decimal_places' => 'int', ]; - /** @var array */ - protected $dates = ['date']; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['name', 'code', 'symbol', 'decimal_places']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return TransactionCurrency - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): TransactionCurrency { @@ -77,9 +78,9 @@ class TransactionCurrency extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function transactionJournals(): \Illuminate\Database\Eloquent\Relations\HasMany + public function transactionJournals(): HasMany { return $this->hasMany(TransactionJournal::class); } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 7eae36c4d3..ccdb49fa4c 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -28,6 +28,7 @@ use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -91,15 +92,17 @@ class TransactionJournal extends Model 'completed' => 'boolean', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'transaction_type_id', 'bill_id', 'interest_date', 'book_date', 'process_date', 'transaction_currency_id', 'description', 'completed', 'date', 'rent_date', 'encrypted', 'tag_count',]; - /** @var array */ + /** @var array Hidden from view */ protected $hidden = ['encrypted']; /** + * Checks if tables are joined. + * * @param Builder $query * @param string $table * @@ -121,10 +124,12 @@ class TransactionJournal extends Model } /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return TransactionJournal - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $value): TransactionJournal { @@ -154,16 +159,16 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function bill(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function bill(): BelongsTo { return $this->belongsTo(Bill::class); } /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ public function budgets(): BelongsToMany { @@ -172,7 +177,7 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ public function categories(): BelongsToMany { @@ -259,7 +264,7 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ public function piggyBankEvents(): HasMany { @@ -333,7 +338,7 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * @return BelongsToMany */ public function tags(): BelongsToMany { @@ -342,9 +347,9 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionCurrency(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function transactionCurrency(): BelongsTo { return $this->belongsTo(TransactionCurrency::class); } @@ -360,9 +365,9 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function transactionType(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function transactionType(): BelongsTo { return $this->belongsTo(TransactionType::class); } @@ -378,9 +383,9 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php index 38de517ea4..92e5920f7a 100644 --- a/app/Models/TransactionJournalLink.php +++ b/app/Models/TransactionJournalLink.php @@ -45,12 +45,23 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; */ class TransactionJournalLink extends Model { - /** - * @var string - */ + /** @var string The table to store the data in */ protected $table = 'journal_links'; /** + * The attributes that should be casted to native types. + * + * @var array + */ + protected $casts + = [ + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $value * * @return mixed @@ -76,7 +87,7 @@ class TransactionJournalLink extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function destination(): BelongsTo { @@ -101,7 +112,7 @@ class TransactionJournalLink extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function linkType(): BelongsTo { @@ -136,7 +147,7 @@ class TransactionJournalLink extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function source(): BelongsTo { diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index d27a7a5996..0eb222f6c3 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -49,9 +49,9 @@ class TransactionJournalMeta extends Model 'updated_at' => 'datetime', 'deleted_at' => 'datetime', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['transaction_journal_id', 'name', 'data', 'hash']; - /** @var string */ + /** @var string The table to store the data in */ protected $table = 'journal_meta'; /** @@ -80,7 +80,7 @@ class TransactionJournalMeta extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * @return BelongsTo */ public function transactionJournal(): BelongsTo { diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index f82f24769a..75c0f35b3d 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -67,14 +68,16 @@ class TransactionType extends Model 'updated_at' => 'datetime', 'deleted_at' => 'datetime', ]; - /** @var array */ + /** @var array Fields that can be filled */ protected $fillable = ['type']; /** + * Route binder. Converts the key in the URL to the specified object (or throw 404). + * * @param string $type * * @return Model|null|static - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + * @throws NotFoundHttpException */ public static function routeBinder(string $type): TransactionType { @@ -126,9 +129,9 @@ class TransactionType extends Model /** * @codeCoverageIgnore - * @return \Illuminate\Database\Eloquent\Relations\HasMany + * @return HasMany */ - public function transactionJournals(): \Illuminate\Database\Eloquent\Relations\HasMany + public function transactionJournals(): HasMany { return $this->hasMany(TransactionJournal::class); } diff --git a/app/Repositories/ExportJob/ExportJobRepository.php b/app/Repositories/ExportJob/ExportJobRepository.php index c8d50516a5..a74f7f6628 100644 --- a/app/Repositories/ExportJob/ExportJobRepository.php +++ b/app/Repositories/ExportJob/ExportJobRepository.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\ExportJob; use Carbon\Carbon; +use Exception; use FireflyIII\Models\ExportJob; use FireflyIII\User; use Illuminate\Contracts\Filesystem\FileNotFoundException; @@ -54,11 +55,10 @@ class ExportJobRepository implements ExportJobRepositoryInterface /** * @return bool - * @throws \Exception */ public function cleanup(): bool { - $dayAgo = Carbon::create()->subDay(); + $dayAgo = Carbon::now()->subDay(); $set = ExportJob::where('created_at', '<', $dayAgo->format('Y-m-d H:i:s')) ->whereIn('status', ['never_started', 'export_status_finished', 'export_downloaded']) ->get(); @@ -74,7 +74,11 @@ class ExportJobRepository implements ExportJobRepositoryInterface unlink(storage_path('export') . DIRECTORY_SEPARATOR . $file); } } - $entry->delete(); + try { + $entry->delete(); + } catch (Exception $e) { + Log::debug(sprintf('Could not delete object: %s', $e->getMessage())); + } } return true; @@ -142,8 +146,8 @@ class ExportJobRepository implements ExportJobRepositoryInterface */ public function getContent(ExportJob $job): string { - $disk = Storage::disk('export'); - $file = $job->key . '.zip'; + $disk = Storage::disk('export'); + $file = $job->key . '.zip'; try { $content = $disk->get($file); diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 61ccbd4a79..62967a1d12 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -375,7 +375,7 @@ class RecurringRepository implements RecurringRepositoryInterface } if ('yearly' === $repetition->repetition_type) { // - $today = Carbon::create()->endOfYear(); + $today = Carbon::now()->endOfYear(); $repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment); $diffInYears = $today->diffInYears($repDate); $repDate->addYears($diffInYears); // technically not necessary. diff --git a/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php b/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php index 568ff9d384..e8803b31b0 100644 --- a/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php +++ b/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php @@ -267,6 +267,7 @@ class ConfigureMappingHandler implements FileConfigurationInterface * - Add the value to the list of "values" that the user must map. * * @param Reader $reader + * @param array $config * @param array $columnConfig * * @return array diff --git a/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php b/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php index 047f8c5d39..6ce648d5aa 100644 --- a/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php +++ b/app/Support/Import/JobConfiguration/File/NewFileJobHandler.php @@ -101,7 +101,7 @@ class NewFileJobHandler implements FileConfigurationInterface } /** - * @param ImportJob $job + * @param ImportJob $importJob */ public function setImportJob(ImportJob $importJob): void { @@ -114,7 +114,6 @@ class NewFileJobHandler implements FileConfigurationInterface /** * Store config from job. * - * @throws FireflyException */ public function storeConfiguration(): void { diff --git a/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php b/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php index 725bd38766..5a9ddaccda 100644 --- a/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php +++ b/app/Support/Import/JobConfiguration/Ynab/NewYnabJobHandler.php @@ -87,6 +87,8 @@ class NewYnabJobHandler implements YnabJobConfigurationInterface * Get data for config view. * * @return array + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface */ public function getNextData(): array { @@ -134,6 +136,8 @@ class NewYnabJobHandler implements YnabJobConfigurationInterface /** + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface * @throws FireflyException */ private function getAccessToken(): void @@ -231,6 +235,8 @@ class NewYnabJobHandler implements YnabJobConfigurationInterface /** * @return bool + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface */ private function hasRefreshToken(): bool { diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php index 411eb78301..4dbf8fa49b 100644 --- a/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php @@ -218,7 +218,7 @@ class SelectAccountsHandler implements YnabJobConfigurationInterface } /** - * @param int $accountId + * @param string $accountId * * @return string */ diff --git a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php index f0067b57dd..cb3f2e2989 100644 --- a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php +++ b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php @@ -111,6 +111,7 @@ class StageImportDataHandler * @param LocalAccount $source * * @return array + * @throws FireflyException */ private function convertPayment(BunqPayment $payment, LocalAccount $source): array { diff --git a/app/Support/Import/Routine/Fake/StageFinalHandler.php b/app/Support/Import/Routine/Fake/StageFinalHandler.php index 23d02676ca..bb93636f90 100644 --- a/app/Support/Import/Routine/Fake/StageFinalHandler.php +++ b/app/Support/Import/Routine/Fake/StageFinalHandler.php @@ -47,7 +47,7 @@ class StageFinalHandler for ($i = 0; $i < 5; $i++) { $transaction = [ 'type' => 'withdrawal', - 'date' => Carbon::create()->format('Y-m-d'), + 'date' => Carbon::now()->format('Y-m-d'), 'tags' => '', 'user' => $this->importJob->user_id, diff --git a/app/Support/Import/Routine/File/CurrencyMapper.php b/app/Support/Import/Routine/File/CurrencyMapper.php index 21362912b5..83821c019f 100644 --- a/app/Support/Import/Routine/File/CurrencyMapper.php +++ b/app/Support/Import/Routine/File/CurrencyMapper.php @@ -71,7 +71,7 @@ class CurrencyMapper return $result; } } - if (!isset($data['code']) || null === $data['code']) { + if (!isset($data['code'])) { return null; } diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 43445e96e6..d4a90fc9d8 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -212,7 +212,7 @@ class ImportableConverter return [ 'type' => $transactionType, - 'date' => $this->convertDateValue($importable->date) ?? Carbon::create()->format('Y-m-d'), + 'date' => $this->convertDateValue($importable->date) ?? Carbon::now()->format('Y-m-d'), 'tags' => $importable->tags, 'user' => $this->importJob->user_id, 'notes' => $importable->note, diff --git a/app/Support/Import/Routine/Ynab/ImportDataHandler.php b/app/Support/Import/Routine/Ynab/ImportDataHandler.php index f8edcac2e2..c6e4c0976c 100644 --- a/app/Support/Import/Routine/Ynab/ImportDataHandler.php +++ b/app/Support/Import/Routine/Ynab/ImportDataHandler.php @@ -64,7 +64,7 @@ class ImportDataHandler /** * @var string $ynabId - * @var int $localId + * @var string $localId */ foreach ($mapping as $ynabId => $localId) { $localAccount = $this->getLocalAccount((int)$localId); @@ -252,7 +252,6 @@ class ImportDataHandler /** * @param string $token - * @param string $budget * @param string $account * * @return array diff --git a/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php b/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php index 489a75766d..4c91d1abac 100644 --- a/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php +++ b/app/Support/Import/Routine/Ynab/StageGetAccessHandler.php @@ -45,6 +45,8 @@ class StageGetAccessHandler /** * Send a token request to YNAB. Return with access token (if all goes well). * + * @throws \Psr\Container\NotFoundExceptionInterface + * @throws \Psr\Container\ContainerExceptionInterface * @throws FireflyException */ public function run(): void diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index a7f3690562..b94901dcd1 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -171,10 +171,7 @@ class Preferences 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 $hash; + return md5($lastActivity); } /** diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index cc1e884954..85fb5ccf48 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -246,7 +246,7 @@ class Search implements SearchInterface private function extractModifier(string $string): void { $parts = explode(':', $string); - if (2 === \count($parts) && \strlen(trim((string)$parts[0])) > 0 && '' !== trim((string)$parts[1])) { + if (2 === \count($parts) && '' !== trim((string)$parts[1]) && \strlen(trim((string)$parts[0])) > 0) { $type = trim((string)$parts[0]); $value = trim((string)$parts[1]); if (\in_array($type, $this->validModifiers, true)) { diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index 52a7554885..9992740d8e 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -132,6 +132,7 @@ class AmountFormat extends Twig_Extension { return new Twig_SimpleFunction( 'formatAmountBySymbol', + /** @noinspection MoreThanThreeArgumentsInspection */ function (string $amount, string $symbol, int $decimalPlaces = null, bool $coloured = null): string { $decimalPlaces = $decimalPlaces ?? 2; $coloured = $coloured ?? true; diff --git a/app/Support/Twig/Extension/TransactionJournal.php b/app/Support/Twig/Extension/TransactionJournal.php index 32bfa25f3a..4c8f69ff96 100644 --- a/app/Support/Twig/Extension/TransactionJournal.php +++ b/app/Support/Twig/Extension/TransactionJournal.php @@ -136,7 +136,7 @@ class TransactionJournal extends Twig_Extension /** * @param JournalModel $journal * - * @return string + * @return array */ private function getTotalAmount(JournalModel $journal): array { diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index a92de4be5c..804378ffe2 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -103,9 +103,7 @@ class General extends Twig_Extension return new Twig_SimpleFunction( 'activeRoutePartialWhat', function ($context): string { - $args = \func_get_args(); - $route = $args[1]; // name of the route. - $what = $args[2]; // name of the route. + [, $route, $what] = \func_get_args(); $activeWhat = $context['what'] ?? false; if ($what === $activeWhat && !(false === stripos(Route::getCurrentRoute()->getName(), $route))) { diff --git a/app/User.php b/app/User.php index c8ff6c43c6..442c9fb290 100644 --- a/app/User.php +++ b/app/User.php @@ -302,7 +302,7 @@ class User extends Authenticatable * * @param string $token */ - public function sendPasswordResetNotification($token) + public function sendPasswordResetNotification($token): void { $ipAddress = Request::ip(); diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 0c874afd03..1d42ead1bf 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -112,10 +112,10 @@ trait TransactionValidation { $data = $validator->getData(); $transactions = $data['transactions'] ?? []; - $journalDescription = (string)($data['description'] ?? ''); + $journalDescription = (string)($data['description'] ?? null); $validDescriptions = 0; foreach ($transactions as $index => $transaction) { - if (\strlen((string)($transaction['description'] ?? '')) > 0) { + if (\strlen((string)($transaction['description'] ?? null)) > 0) { $validDescriptions++; } } @@ -157,9 +157,9 @@ trait TransactionValidation { $data = $validator->getData(); $transactions = $data['transactions'] ?? []; - $journalDescription = (string)($data['description'] ?? ''); + $journalDescription = (string)($data['description'] ?? null); foreach ($transactions as $index => $transaction) { - $description = (string)($transaction['description'] ?? ''); + $description = (string)($transaction['description'] ?? null); // description cannot be equal to journal description. if ($description === $journalDescription) { $validator->errors()->add('transactions.' . $index . '.description', (string)trans('validation.equal_description')); @@ -249,7 +249,7 @@ trait TransactionValidation $data = $validator->getData(); $transactions = $data['transactions'] ?? []; foreach ($transactions as $index => $transaction) { - $description = (string)($transaction['description'] ?? ''); + $description = (string)($transaction['description'] ?? null); // filled description is mandatory for split transactions. if ('' === $description && \count($transactions) > 1) { $validator->errors()->add( diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 58266f14c9..ad42c3d7ae 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -50,7 +50,7 @@ return [ '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 :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', diff --git a/resources/views/form/amount-no-currency.twig b/resources/views/form/amount-no-currency.twig index 005be0f54a..a5b2d39ba9 100644 --- a/resources/views/form/amount-no-currency.twig +++ b/resources/views/form/amount-no-currency.twig @@ -2,6 +2,7 @@
    {{ Form.input('number', name, value, options) }} + {% include 'form/help' %} {% include 'form/feedback' %}
    diff --git a/resources/views/form/number.twig b/resources/views/form/number.twig index 6951b19328..54be537886 100644 --- a/resources/views/form/number.twig +++ b/resources/views/form/number.twig @@ -3,6 +3,7 @@
    {{ Form.input('number', name, value, options) }} + {% include 'form/help' %} {% include 'form/feedback' %}
    diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index f63a467f28..f13da933a1 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -426,8 +426,8 @@
    From 8dbc84631467c7095472ad23f0445ad8c5f1c881 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 4 Aug 2018 17:30:47 +0200 Subject: [PATCH 029/166] Basic code for tracking liabilities. --- app/Factory/AccountFactory.php | 17 ++++++---- .../Controllers/Account/CreateController.php | 23 ++++++++++++- .../Controllers/Account/DeleteController.php | 3 +- .../Account/AccountRepository.php | 13 +++++++ .../Account/AccountRepositoryInterface.php | 15 ++++++-- .../Internal/Support/AccountServiceTrait.php | 2 +- app/Support/ExpandedForm.php | 29 ++++++++++++++++ app/Support/Twig/Translation.php | 2 ++ app/Validation/FireflyValidator.php | 34 ++++++++++--------- app/Validation/RecurrenceValidation.php | 2 ++ config/firefly.php | 29 ++++++++++------ config/twigbridge.php | 2 +- resources/lang/en_US/firefly.php | 5 ++- resources/lang/en_US/form.php | 1 + resources/views/accounts/create.twig | 13 +++++-- resources/views/accounts/delete.twig | 2 +- resources/views/form/percentage.twig | 12 +++++++ 17 files changed, 161 insertions(+), 43 deletions(-) create mode 100644 resources/views/form/percentage.twig diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php index aeb9b62b3b..8c06e1e63c 100644 --- a/app/Factory/AccountFactory.php +++ b/app/Factory/AccountFactory.php @@ -32,6 +32,7 @@ use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Services\Internal\Support\AccountServiceTrait; use FireflyIII\User; +use Log; /** * Factory to create or return accounts. @@ -80,8 +81,9 @@ class AccountFactory 'iban' => $data['iban'], ]; - // remove virtual balance when not an asset account: - if ($type->type !== AccountType::ASSET) { + // remove virtual balance when not an asset account or a liability + $canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; + if (!\in_array($type->type, $canHaveVirtual, true)) { $databaseData['virtual_balance'] = '0'; } @@ -93,7 +95,7 @@ class AccountFactory $return = Account::create($databaseData); $this->updateMetaData($return, $data); - if ($type->type === AccountType::ASSET) { + if (\in_array($type->type, $canHaveVirtual, true)) { if ($this->validIBData($data)) { $this->updateIB($return, $data); } @@ -188,9 +190,12 @@ class AccountFactory $result = AccountType::find($accountTypeId); } if (null === $result) { - /** @var string $type */ - $type = (string)config('firefly.accountTypeByIdentifier.' . $accountType); - $result = AccountType::whereType($type)->first(); + Log::debug(sprintf('No account type found by ID, continue search for "%s".', $accountType)); + /** @var array $types */ + $types = config('firefly.accountTypeByIdentifier.' . $accountType) ?? []; + if (\count($types) > 0) { + $result = AccountType::whereIn('types', $types)->first(); + } if (null === $result && null !== $accountType) { // try as full name: $result = AccountType::whereType($accountType)->first(); diff --git a/app/Http/Controllers/Account/CreateController.php b/app/Http/Controllers/Account/CreateController.php index 10da6e9c81..f78ac9e166 100644 --- a/app/Http/Controllers/Account/CreateController.php +++ b/app/Http/Controllers/Account/CreateController.php @@ -78,6 +78,26 @@ class CreateController extends Controller $roles[$role] = (string)trans('firefly.account_role_' . $role); } + // types of liability: + $debt = $this->repository->getAccountTypeByType(AccountType::DEBT); + $loan = $this->repository->getAccountTypeByType(AccountType::LOAN); + $mortgage = $this->repository->getAccountTypeByType(AccountType::MORTGAGE); + $creditCard = $this->repository->getAccountTypeByType(AccountType::CREDITCARD); + $liabilityTypes = [ + $debt->id => (string)trans('firefly.account_type_' . AccountType::DEBT), + $loan->id => (string)trans('firefly.account_type_' . AccountType::LOAN), + $mortgage->id => (string)trans('firefly.account_type_' . AccountType::MORTGAGE), + $creditCard->id => (string)trans('firefly.account_type_' . AccountType::CREDITCARD), + ]; + asort($liabilityTypes); + + // interest calculation periods: + $interestPeriods = [ + 'daily' => (string)trans('firefly.interest_calc_daily'), + 'monthly' => (string)trans('firefly.interest_calc_monthly'), + 'yearly' => (string)trans('firefly.interest_calc_yearly'), + ]; + // pre fill some data $request->session()->flash('preFilled', ['currency_id' => $defaultCurrency->id]); @@ -87,7 +107,7 @@ class CreateController extends Controller } $request->session()->forget('accounts.create.fromStore'); - return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'roles')); + return view('accounts.create', compact('subTitleIcon', 'what','interestPeriods', 'subTitle', 'roles', 'liabilityTypes')); } @@ -100,6 +120,7 @@ class CreateController extends Controller */ public function store(AccountFormRequest $request) { + $data = $request->getAccountData(); $account = $this->repository->store($data); $request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name])); diff --git a/app/Http/Controllers/Account/DeleteController.php b/app/Http/Controllers/Account/DeleteController.php index 700a0660c7..3ec08fe917 100644 --- a/app/Http/Controllers/Account/DeleteController.php +++ b/app/Http/Controllers/Account/DeleteController.php @@ -69,12 +69,13 @@ class DeleteController extends Controller $typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type); $subTitle = (string)trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]); $accountList = app('expandedform')->makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type])); + $what = $typeName; unset($accountList[$account->id]); // put previous url in session $this->rememberPreviousUri('accounts.delete.uri'); - return view('accounts.delete', compact('account', 'subTitle', 'accountList')); + return view('accounts.delete', compact('account', 'subTitle', 'accountList', 'what')); } /** diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index e865b10dbc..29616566f1 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -168,6 +168,18 @@ class AccountRepository implements AccountRepositoryInterface return $this->user->accounts()->find($accountId); } + /** + * Return account type or null if not found. + * + * @param string $type + * + * @return AccountType|null + */ + public function getAccountTypeByType(string $type): ?AccountType + { + return AccountType::whereType($type)->first(); + } + /** * @param array $accountIds * @@ -373,6 +385,7 @@ class AccountRepository implements AccountRepositoryInterface * Returns the date of the very first transaction in this account. * * @param Account $account + * * @return TransactionJournal|null */ public function oldestJournal(Account $account): ?TransactionJournal diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 09a0981209..8f88884e1a 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -24,17 +24,18 @@ namespace FireflyIII\Repositories\Account; use Carbon\Carbon; use FireflyIII\Models\Account; - - +use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; + /** * Interface AccountRepositoryInterface. */ interface AccountRepositoryInterface { + /** * Moved here from account CRUD. * @@ -87,6 +88,15 @@ interface AccountRepositoryInterface */ public function findNull(int $accountId): ?Account; + /** + * Return account type or null if not found. + * + * @param string $type + * + * @return AccountType|null + */ + public function getAccountTypeByType(string $type): ?AccountType; + /** * @param array $accountIds * @@ -164,6 +174,7 @@ interface AccountRepositoryInterface * Returns the date of the very first transaction in this account. * * @param Account $account + * * @return TransactionJournal|null */ public function oldestJournal(Account $account): ?TransactionJournal; diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index a5bf55b646..6877ba13bb 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -49,7 +49,7 @@ trait AccountServiceTrait /** @var array */ public $validCCFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType', 'accountNumber', 'currency_id', 'BIC']; /** @var array */ - public $validFields = ['accountNumber', 'currency_id', 'BIC']; + public $validFields = ['accountNumber', 'currency_id', 'BIC','interest','interest_period']; /** * @param Account $account diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 0370df5f54..51609ca6c1 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -528,6 +528,34 @@ class ExpandedForm return $html; } + /** + * Function to render a percentage. + * + * @param string $name + * @param mixed $value + * @param array $options + * + * @return string + * + */ + public function percentage(string $name, $value = null, array $options = null): string + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $value = $this->fillFieldValue($name, $value); + $options['step'] = 'any'; + unset($options['placeholder']); + try { + $html = view('form.percentage', compact('classes', 'name', 'label', 'value', 'options'))->render(); + } catch (Throwable $e) { + Log::debug(sprintf('Could not render percentage(): %s', $e->getMessage())); + $html = 'Could not render percentage.'; + } + + return $html; + } + /** * @param string $type * @param string $name @@ -772,6 +800,7 @@ class ExpandedForm return $html; } + /** @noinspection MoreThanThreeArgumentsInspection */ /** * @param string $name * @param string $view diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php index 74198da564..e00093a7c9 100644 --- a/app/Support/Twig/Translation.php +++ b/app/Support/Twig/Translation.php @@ -69,7 +69,9 @@ class Translation extends Twig_Extension 'journalLinkTranslation', function (string $direction, string $original) { $key = sprintf('firefly.%s_%s', $original, $direction); + return $key; $translation = trans($key); + if ($key === $translation) { return $original; } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 1034b1ef63..c44ac55640 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -38,7 +38,9 @@ use FireflyIII\TransactionRules\Triggers\TriggerInterface; use FireflyIII\User; use Google2FA; use Illuminate\Contracts\Encryption\DecryptException; +use Illuminate\Support\Collection; use Illuminate\Validation\Validator; +use Log; /** * Class FireflyValidator. @@ -195,12 +197,12 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateMore($attribute, $value, $parameters): bool + public function validateLess($attribute, $value, $parameters): bool { /** @var mixed $compare */ $compare = $parameters[0] ?? '0'; - return bccomp((string)$value, (string)$compare) > 0; + return bccomp((string)$value, (string)$compare) < 0; } /** @@ -211,15 +213,14 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateLess($attribute, $value, $parameters): bool + public function validateMore($attribute, $value, $parameters): bool { /** @var mixed $compare */ $compare = $parameters[0] ?? '0'; - return bccomp((string)$value, (string)$compare) < 0; + return bccomp((string)$value, (string)$compare) > 0; } - /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @@ -291,7 +292,7 @@ class FireflyValidator extends Validator case 'link_to_bill': /** @var BillRepositoryInterface $repository */ $repository = app(BillRepositoryInterface::class); - $bill = $repository->findByName((string)$value); + $bill = $repository->findByName($value); return null !== $bill; case 'invalid': @@ -468,7 +469,7 @@ class FireflyValidator extends Validator if ((int)$accountId > 0) { // exclude current account from check. - $query->where('account_meta.account_id', '!=', (int)$accountId); + $query->where('account_meta.account_id', '!=', $accountId); } $set = $query->get(['account_meta.*']); @@ -499,9 +500,7 @@ class FireflyValidator extends Validator public function validateUniqueObjectForUser($attribute, $value, $parameters): bool { $value = $this->tryDecrypt($value); - // exclude? - $table = $parameters[0]; - $field = $parameters[1]; + [$table, $field] = $parameters; $exclude = (int)($parameters[2] ?? 0.0); /* @@ -630,7 +629,7 @@ class FireflyValidator extends Validator try { $value = Crypt::decrypt($value); } catch (DecryptException $e) { - // do not care. + Log::debug(sprintf('Could not decrypt. %s', $e->getMessage())); } return $value; @@ -717,11 +716,14 @@ class FireflyValidator extends Validator */ private function validateByAccountTypeString(string $value, array $parameters, string $type): bool { - $search = Config::get('firefly.accountTypeByIdentifier.' . $type); - $accountType = AccountType::whereType($search)->first(); - $ignore = (int)($parameters[0] ?? 0.0); - - $set = auth()->user()->accounts()->where('account_type_id', $accountType->id)->where('id', '!=', $ignore)->get(); + /** @var array $search */ + $search = Config::get('firefly.accountTypeByIdentifier.' . $type); + /** @var Collection $accountTypes */ + $accountTypes = AccountType::whereIn('type', $search)->get(); + $ignore = (int)($parameters[0] ?? 0.0); + $accountTypeIds = $accountTypes->pluck('id')->toArray(); + /** @var Collection $set */ + $set = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore)->get(); /** @var Account $entry */ foreach ($set as $entry) { if ($entry->name === $value) { diff --git a/app/Validation/RecurrenceValidation.php b/app/Validation/RecurrenceValidation.php index 98ad0d0342..ad6614a0db 100644 --- a/app/Validation/RecurrenceValidation.php +++ b/app/Validation/RecurrenceValidation.php @@ -27,6 +27,7 @@ use Carbon\Carbon; use Exception; use Illuminate\Validation\Validator; use InvalidArgumentException; +use Log; /** * Trait RecurrenceValidation @@ -187,6 +188,7 @@ trait RecurrenceValidation try { Carbon::createFromFormat('Y-m-d', $moment); } catch (InvalidArgumentException|Exception $e) { + Log::debug(sprintf('Invalid argument for Carbon: %s', $e->getMessage())); $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); } } diff --git a/config/firefly.php b/config/firefly.php index c996c32c76..69f9fd000c 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -176,10 +176,11 @@ return [ ], 'subTitlesByIdentifier' => [ - 'asset' => 'Asset accounts', - 'expense' => 'Expense accounts', - 'revenue' => 'Revenue accounts', - 'cash' => 'Cash accounts', + 'asset' => 'Asset accounts', + 'expense' => 'Expense accounts', + 'revenue' => 'Revenue accounts', + 'cash' => 'Cash accounts', + 'liabilities' => 'Liabilities', ], 'subIconsByIdentifier' => [ @@ -194,6 +195,7 @@ return [ 'Revenue account' => 'fa-download', 'import' => 'fa-download', 'Import account' => 'fa-download', + 'liabilities' => 'fa-ticket', ], 'accountTypesByIdentifier' => [ @@ -205,13 +207,14 @@ return [ ], 'accountTypeByIdentifier' => [ - 'asset' => 'Asset account', - 'expense' => 'Expense account', - 'revenue' => 'Revenue account', - 'opening' => 'Initial balance account', - 'initial' => 'Initial balance account', - 'import' => 'Import account', - 'reconcile' => 'Reconciliation account', + 'asset' => ['Asset account'], + 'expense' => ['Expense account'], + 'revenue' => ['Revenue account'], + 'opening' => ['Initial balance account'], + 'initial' => ['Initial balance account'], + 'import' => ['Import account'], + 'reconcile' => ['Reconciliation account'], + 'liabilities' => ['Loan', 'Debt', 'Mortgage', 'Credit card'], ], 'shortNamesByFullName' => [ @@ -222,6 +225,10 @@ return [ 'Beneficiary account' => 'expense', 'Revenue account' => 'revenue', 'Cash account' => 'cash', + 'Credit card' => 'liabilities', + 'Loan' => 'liabilities', + 'Debt' => 'liabilities', + 'Mortgage' => 'liabilities', ], 'languages' => [ // completed languages diff --git a/config/twigbridge.php b/config/twigbridge.php index fa0557b7c9..7d3c3f0091 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', 'file', 'staticText', 'password', 'nonSelectableAmount', 'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList','ruleGroupListWithEmpty', - 'piggyBankList','currencyListEmpty','activeAssetAccountList' + 'piggyBankList','currencyListEmpty','activeAssetAccountList','percentage' ], ], 'Form' => [ diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index eaca551c4c..72edf342b2 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -591,7 +591,6 @@ return [ 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', - // create new stuff: 'create_new_withdrawal' => 'Create new withdrawal', 'create_new_deposit' => 'Create new deposit', @@ -689,6 +688,7 @@ return [ 'delete_asset_account' => 'Delete asset account ":name"', 'delete_expense_account' => 'Delete expense account ":name"', 'delete_revenue_account' => 'Delete revenue account ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Successfully deleted asset account ":name"', 'expense_deleted' => 'Successfully deleted expense account ":name"', 'revenue_deleted' => 'Successfully deleted revenue account ":name"', @@ -756,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Already cleared transactions (:count)', 'submitted_end_balance' => 'Submitted end balance', 'initial_balance_description' => 'Initial balance for ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'New category', diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index c2dfc104eb..2007f077a9 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -86,6 +86,7 @@ return [ 'remember_me' => 'Remember me', 'liability_type_id' => 'Liability type', 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Source account (asset account)', 'destination_account_expense' => 'Destination account (expense account)', diff --git a/resources/views/accounts/create.twig b/resources/views/accounts/create.twig index 0fe42b070c..dc32ab6e77 100644 --- a/resources/views/accounts/create.twig +++ b/resources/views/accounts/create.twig @@ -17,9 +17,17 @@
    {{ ExpandedForm.text('name') }} - {% if what == 'asset' %} + {% if what == 'asset' or what=='liabilities' %} {{ ExpandedForm.currencyList('currency_id', null, {helpText:'account_default_currency'|_}) }} {% endif %} + {% if what == 'liabilities' %} + {{ ExpandedForm.select('liability_type_id', liabilityTypes) }} + {{ ExpandedForm.amountNoCurrency('openingBalance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }} + {{ ExpandedForm.date('openingBalanceDate', null, {label:'debt_start_date'|_}) }} + {{ ExpandedForm.percentage('interest') }} + {{ ExpandedForm.select('interest_period', interestPeriods) }} + + {% endif %}
    @@ -40,9 +48,10 @@ {{ ExpandedForm.amountNoCurrency('openingBalance') }} {{ ExpandedForm.date('openingBalanceDate') }} - {{ ExpandedForm.select('accountRole', roles,null,{'helpText' : 'asset_account_role_help'|_}) }} + {{ ExpandedForm.select('accountRole', roles,null,{helpText : 'asset_account_role_help'|_}) }} {{ ExpandedForm.amountNoCurrency('virtualBalance') }} {% endif %} + {{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }} diff --git a/resources/views/accounts/delete.twig b/resources/views/accounts/delete.twig index 9e8dcb0868..0466bab036 100644 --- a/resources/views/accounts/delete.twig +++ b/resources/views/accounts/delete.twig @@ -33,7 +33,7 @@ {% endif %}

    {% endif %} - {% if account.transactions.count > 0 %} + {% if account.transactions.count > 0 and account.accountType.type == 'Asset account' %}

    {{ 'save_transactions_by_moving'|_ }}

    diff --git a/resources/views/form/percentage.twig b/resources/views/form/percentage.twig new file mode 100644 index 0000000000..b5e73b7b91 --- /dev/null +++ b/resources/views/form/percentage.twig @@ -0,0 +1,12 @@ +
    + + +
    +
    + {{ Form.input('number', name, value, options) }} +
    %
    +
    + {% include 'form/help' %} + {% include 'form/feedback' %} +
    +
    From 5449879a7d5d0fe9422c6f6982dbbaa1d0477613 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 5 Aug 2018 07:36:33 +0200 Subject: [PATCH 030/166] Fix for #1594 --- routes/breadcrumbs.php | 285 ++++++++++++++++++++++------------------- 1 file changed, 152 insertions(+), 133 deletions(-) diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 3c8cfa7263..42e37b0914 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -44,18 +44,37 @@ use FireflyIII\Models\TransactionType; use FireflyIII\User; use Illuminate\Support\Collection; +/** + * Cuts away the middle of a string when it's very long. + * + * @param string $string + * + * @return string + */ +function limitStringLength(string $string): string +{ + $maxChars = 75; + $length = \strlen($string); + $result = $string; + if ($length > $maxChars) { + $result = substr_replace($string, ' ... ', $maxChars / 2, $length - $maxChars); + } + + return $result; +} + try { // HOME Breadcrumbs::register( 'home', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->push(trans('breadcrumbs.home'), route('index')); } ); Breadcrumbs::register( 'index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->push(trans('breadcrumbs.home'), route('index')); } ); @@ -63,7 +82,7 @@ try { // ACCOUNTS Breadcrumbs::register( 'accounts.index', - function (BreadCrumbsGenerator $breadcrumbs, string $what) { + function (BreadcrumbsGenerator $breadcrumbs, string $what) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.' . strtolower(e($what)) . '_accounts'), route('accounts.index', [$what])); } @@ -71,7 +90,7 @@ try { Breadcrumbs::register( 'accounts.create', - function (BreadCrumbsGenerator $breadcrumbs, string $what) { + function (BreadcrumbsGenerator $breadcrumbs, string $what) { $breadcrumbs->parent('accounts.index', $what); $breadcrumbs->push(trans('firefly.new_' . strtolower(e($what)) . '_account'), route('accounts.create', [$what])); } @@ -79,11 +98,11 @@ try { Breadcrumbs::register( 'accounts.show', - function (BreadCrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { + function (BreadcrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $breadcrumbs->parent('accounts.index', $what); - $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); + $breadcrumbs->push(limitStringLength($account->name), route('accounts.show', [$account->id])); if (null !== $start && null !== $end) { $title = trans( 'firefly.between_dates_breadcrumb', @@ -97,17 +116,17 @@ try { Breadcrumbs::register( 'accounts.show.all', - function (BreadCrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { + function (BreadcrumbsGenerator $breadcrumbs, Account $account, Carbon $start = null, Carbon $end = null) { $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); $breadcrumbs->parent('accounts.index', $what); - $breadcrumbs->push($account->name, route('accounts.show', [$account->id])); + $breadcrumbs->push(limitStringLength($account->name), route('accounts.show', [$account->id])); } ); Breadcrumbs::register( 'accounts.reconcile', - function (BreadCrumbsGenerator $breadcrumbs, Account $account) { + function (BreadcrumbsGenerator $breadcrumbs, Account $account) { $breadcrumbs->parent('accounts.show', $account); $breadcrumbs->push(trans('firefly.reconcile_account', ['account' => $account->name]), route('accounts.reconcile', [$account->id])); } @@ -115,7 +134,7 @@ try { Breadcrumbs::register( 'accounts.reconcile.show', - function (BreadCrumbsGenerator $breadcrumbs, Account $account, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, Account $account, TransactionJournal $journal) { $breadcrumbs->parent('accounts.show', $account); $title = trans('firefly.reconciliation') . ' "' . $journal->description . '"'; $breadcrumbs->push($title, route('accounts.reconcile.show', [$journal->id])); @@ -124,26 +143,26 @@ try { Breadcrumbs::register( 'accounts.delete', - function (BreadCrumbsGenerator $breadcrumbs, Account $account) { + function (BreadcrumbsGenerator $breadcrumbs, Account $account) { $breadcrumbs->parent('accounts.show', $account); - $breadcrumbs->push(trans('firefly.delete_account', ['name' => $account->name]), route('accounts.delete', [$account->id])); + $breadcrumbs->push(trans('firefly.delete_account', ['name' => limitStringLength($account->name)]), route('accounts.delete', [$account->id])); } ); Breadcrumbs::register( 'accounts.edit', - function (BreadCrumbsGenerator $breadcrumbs, Account $account) { + function (BreadcrumbsGenerator $breadcrumbs, Account $account) { $breadcrumbs->parent('accounts.show', $account); $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); - $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => $account->name]), route('accounts.edit', [$account->id])); + $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => limitStringLength($account->name)]), route('accounts.edit', [$account->id])); } ); // ADMIN Breadcrumbs::register( 'admin.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.administration'), route('admin.index')); } @@ -151,7 +170,7 @@ try { Breadcrumbs::register( 'admin.users', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('admin.index'); $breadcrumbs->push(trans('firefly.list_all_users'), route('admin.users')); } @@ -159,21 +178,21 @@ try { Breadcrumbs::register( 'admin.users.show', - function (BreadCrumbsGenerator $breadcrumbs, User $user) { + function (BreadcrumbsGenerator $breadcrumbs, User $user) { $breadcrumbs->parent('admin.users'); $breadcrumbs->push(trans('firefly.single_user_administration', ['email' => $user->email]), route('admin.users.show', [$user->id])); } ); Breadcrumbs::register( 'admin.users.edit', - function (BreadCrumbsGenerator $breadcrumbs, User $user) { + function (BreadcrumbsGenerator $breadcrumbs, User $user) { $breadcrumbs->parent('admin.users'); $breadcrumbs->push(trans('firefly.edit_user', ['email' => $user->email]), route('admin.users.edit', [$user->id])); } ); Breadcrumbs::register( 'admin.users.delete', - function (BreadCrumbsGenerator $breadcrumbs, User $user) { + function (BreadcrumbsGenerator $breadcrumbs, User $user) { $breadcrumbs->parent('admin.users'); $breadcrumbs->push(trans('firefly.delete_user', ['email' => $user->email]), route('admin.users.delete', [$user->id])); } @@ -181,7 +200,7 @@ try { Breadcrumbs::register( 'admin.users.domains', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('admin.index'); $breadcrumbs->push(trans('firefly.blocked_domains'), route('admin.users.domains')); } @@ -189,14 +208,14 @@ try { Breadcrumbs::register( 'admin.configuration.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('admin.index'); $breadcrumbs->push(trans('firefly.instance_configuration'), route('admin.configuration.index')); } ); Breadcrumbs::register( 'admin.update-check', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('admin.index'); $breadcrumbs->push(trans('firefly.update_check_title'), route('admin.update-check')); } @@ -204,7 +223,7 @@ try { Breadcrumbs::register( 'admin.links.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('admin.index'); $breadcrumbs->push(trans('firefly.journal_link_configuration'), route('admin.links.index')); } @@ -212,7 +231,7 @@ try { Breadcrumbs::register( 'admin.links.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('admin.links.index'); $breadcrumbs->push(trans('firefly.create_new_link_type'), route('admin.links.create')); } @@ -220,31 +239,31 @@ try { Breadcrumbs::register( 'admin.links.show', - function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { + function (BreadcrumbsGenerator $breadcrumbs, LinkType $linkType) { $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.overview_for_link', ['name' => $linkType->name]), route('admin.links.show', [$linkType->id])); + $breadcrumbs->push(trans('firefly.overview_for_link', ['name' => limitStringLength($linkType->name)]), route('admin.links.show', [$linkType->id])); } ); Breadcrumbs::register( 'admin.links.edit', - function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { + function (BreadcrumbsGenerator $breadcrumbs, LinkType $linkType) { $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.edit_link_type', ['name' => $linkType->name]), route('admin.links.edit', [$linkType->id])); + $breadcrumbs->push(trans('firefly.edit_link_type', ['name' => limitStringLength($linkType->name)]), route('admin.links.edit', [$linkType->id])); } ); Breadcrumbs::register( 'admin.links.delete', - function (BreadCrumbsGenerator $breadcrumbs, LinkType $linkType) { + function (BreadcrumbsGenerator $breadcrumbs, LinkType $linkType) { $breadcrumbs->parent('admin.links.index'); - $breadcrumbs->push(trans('firefly.delete_link_type', ['name' => $linkType->name]), route('admin.links.delete', [$linkType->id])); + $breadcrumbs->push(trans('firefly.delete_link_type', ['name' => limitStringLength($linkType->name)]), route('admin.links.delete', [$linkType->id])); } ); Breadcrumbs::register( 'transactions.link.delete', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournalLink $link) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionJournalLink $link) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.delete_journal_link'), route('transactions.link.delete', $link->id)); } @@ -253,7 +272,7 @@ try { // ATTACHMENTS Breadcrumbs::register( 'attachments.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.attachments'), route('attachments.index')); } @@ -261,23 +280,21 @@ try { Breadcrumbs::register( 'attachments.edit', - function (BreadCrumbsGenerator $breadcrumbs, Attachment $attachment) { + function (BreadcrumbsGenerator $breadcrumbs, Attachment $attachment) { $object = $attachment->attachable; if ($object instanceof TransactionJournal) { $breadcrumbs->parent('transactions.show', $object); - $breadcrumbs->push($attachment->filename, route('attachments.edit', [$attachment])); - } else { - throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); + $breadcrumbs->push(limitStringLength($attachment->filename), route('attachments.edit', [$attachment])); } } ); Breadcrumbs::register( 'attachments.delete', - function (BreadCrumbsGenerator $breadcrumbs, Attachment $attachment) { + function (BreadcrumbsGenerator $breadcrumbs, Attachment $attachment) { $object = $attachment->attachable; if ($object instanceof TransactionJournal) { $breadcrumbs->parent('transactions.show', $object); - $breadcrumbs->push(trans('firefly.delete_attachment', ['name' => $attachment->filename]), route('attachments.edit', [$attachment])); + $breadcrumbs->push(trans('firefly.delete_attachment', ['name' => limitStringLength($attachment->filename)]), route('attachments.edit', [$attachment])); } else { throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); } @@ -287,14 +304,14 @@ try { // BILLS Breadcrumbs::register( 'bills.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.bills'), route('bills.index')); } ); Breadcrumbs::register( 'bills.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('bills.index'); $breadcrumbs->push(trans('breadcrumbs.newBill'), route('bills.create')); } @@ -302,38 +319,38 @@ try { Breadcrumbs::register( 'bills.edit', - function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { + function (BreadcrumbsGenerator $breadcrumbs, Bill $bill) { $breadcrumbs->parent('bills.show', $bill); - $breadcrumbs->push(trans('breadcrumbs.edit_bill', ['name' => $bill->name]), route('bills.edit', [$bill->id])); + $breadcrumbs->push(trans('breadcrumbs.edit_bill', ['name' => limitStringLength($bill->name)]), route('bills.edit', [$bill->id])); } ); Breadcrumbs::register( 'bills.delete', - function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { + function (BreadcrumbsGenerator $breadcrumbs, Bill $bill) { $breadcrumbs->parent('bills.show', $bill); - $breadcrumbs->push(trans('breadcrumbs.delete_bill', ['name' => $bill->name]), route('bills.delete', [$bill->id])); + $breadcrumbs->push(trans('breadcrumbs.delete_bill', ['name' => limitStringLength($bill->name)]), route('bills.delete', [$bill->id])); } ); Breadcrumbs::register( 'bills.show', - function (BreadCrumbsGenerator $breadcrumbs, Bill $bill) { + function (BreadcrumbsGenerator $breadcrumbs, Bill $bill) { $breadcrumbs->parent('bills.index'); - $breadcrumbs->push($bill->name, route('bills.show', [$bill->id])); + $breadcrumbs->push(limitStringLength($bill->name), route('bills.show', [$bill->id])); } ); // BUDGETS Breadcrumbs::register( 'budgets.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.budgets'), route('budgets.index')); } ); Breadcrumbs::register( 'budgets.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('budgets.index'); $breadcrumbs->push(trans('firefly.create_new_budget'), route('budgets.create')); } @@ -341,22 +358,22 @@ try { Breadcrumbs::register( 'budgets.edit', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { + function (BreadcrumbsGenerator $breadcrumbs, Budget $budget) { $breadcrumbs->parent('budgets.show', $budget); - $breadcrumbs->push(trans('firefly.edit_budget', ['name' => $budget->name]), route('budgets.edit', [$budget->id])); + $breadcrumbs->push(trans('firefly.edit_budget', ['name' => limitStringLength($budget->name)]), route('budgets.edit', [$budget->id])); } ); Breadcrumbs::register( 'budgets.delete', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { + function (BreadcrumbsGenerator $breadcrumbs, Budget $budget) { $breadcrumbs->parent('budgets.show', $budget); - $breadcrumbs->push(trans('firefly.delete_budget', ['name' => $budget->name]), route('budgets.delete', [$budget->id])); + $breadcrumbs->push(trans('firefly.delete_budget', ['name' => limitStringLength($budget->name)]), route('budgets.delete', [$budget->id])); } ); Breadcrumbs::register( 'budgets.no-budget', - function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('budgets.index'); $breadcrumbs->push(trans('firefly.journals_without_budget'), route('budgets.no-budget')); $title = trans( @@ -370,7 +387,7 @@ try { Breadcrumbs::register( 'budgets.no-budget-all', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('budgets.index'); $breadcrumbs->push(trans('firefly.journals_without_budget'), route('budgets.no-budget')); $breadcrumbs->push(trans('firefly.everything'), route('budgets.no-budget-all')); @@ -379,18 +396,18 @@ try { Breadcrumbs::register( 'budgets.show', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget) { + function (BreadcrumbsGenerator $breadcrumbs, Budget $budget) { $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push($budget->name, route('budgets.show', [$budget->id])); + $breadcrumbs->push(limitStringLength($budget->name), route('budgets.show', [$budget->id])); $breadcrumbs->push(trans('firefly.everything'), route('budgets.show', [$budget->id])); } ); Breadcrumbs::register( 'budgets.show.limit', - function (BreadCrumbsGenerator $breadcrumbs, Budget $budget, BudgetLimit $budgetLimit) { + function (BreadcrumbsGenerator $breadcrumbs, Budget $budget, BudgetLimit $budgetLimit) { $breadcrumbs->parent('budgets.index'); - $breadcrumbs->push($budget->name, route('budgets.show', [$budget->id])); + $breadcrumbs->push(limitStringLength($budget->name), route('budgets.show', [$budget->id])); $title = trans( 'firefly.between_dates_breadcrumb', @@ -408,14 +425,14 @@ try { // CATEGORIES Breadcrumbs::register( 'categories.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.categories'), route('categories.index')); } ); Breadcrumbs::register( 'categories.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('categories.index'); $breadcrumbs->push(trans('firefly.new_category'), route('categories.create')); } @@ -423,24 +440,24 @@ try { Breadcrumbs::register( 'categories.edit', - function (BreadCrumbsGenerator $breadcrumbs, Category $category) { + function (BreadcrumbsGenerator $breadcrumbs, Category $category) { $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon); - $breadcrumbs->push(trans('firefly.edit_category', ['name' => $category->name]), route('categories.edit', [$category->id])); + $breadcrumbs->push(trans('firefly.edit_category', ['name' => limitStringLength($category->name)]), route('categories.edit', [$category->id])); } ); Breadcrumbs::register( 'categories.delete', - function (BreadCrumbsGenerator $breadcrumbs, Category $category) { + function (BreadcrumbsGenerator $breadcrumbs, Category $category) { $breadcrumbs->parent('categories.show', $category, '(nothing)', new Carbon, new Carbon); - $breadcrumbs->push(trans('firefly.delete_category', ['name' => $category->name]), route('categories.delete', [$category->id])); + $breadcrumbs->push(trans('firefly.delete_category', ['name' => limitStringLength($category->name)]), route('categories.delete', [$category->id])); } ); Breadcrumbs::register( 'categories.show', - function (BreadCrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); - $breadcrumbs->push($category->name, route('categories.show', [$category->id])); + $breadcrumbs->push(limitStringLength($category->name), route('categories.show', [$category->id])); $title = trans( 'firefly.between_dates_breadcrumb', ['start' => $start->formatLocalized((string)trans('config.month_and_day')), @@ -452,16 +469,16 @@ try { Breadcrumbs::register( 'categories.show-all', - function (BreadCrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, Category $category, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); - $breadcrumbs->push($category->name, route('categories.show', [$category->id])); + $breadcrumbs->push(limitStringLength($category->name), route('categories.show', [$category->id])); $breadcrumbs->push(trans('firefly.everything'), route('categories.show', [$category->id, 'all'])); } ); Breadcrumbs::register( 'categories.no-category', - function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); $title = trans( @@ -476,7 +493,7 @@ try { Breadcrumbs::register( 'categories.no-category-all', - function (BreadCrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('categories.index'); $breadcrumbs->push(trans('firefly.journals_without_category'), route('categories.no-category')); $breadcrumbs->push(trans('firefly.everything'), route('categories.no-category-all')); @@ -486,7 +503,7 @@ try { // CURRENCIES Breadcrumbs::register( 'currencies.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.currencies'), route('currencies.index')); } @@ -494,7 +511,7 @@ try { Breadcrumbs::register( 'currencies.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('currencies.index'); $breadcrumbs->push(trans('firefly.create_currency'), route('currencies.create')); } @@ -502,14 +519,14 @@ try { Breadcrumbs::register( 'currencies.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { $breadcrumbs->parent('currencies.index'); $breadcrumbs->push(trans('breadcrumbs.edit_currency', ['name' => $currency->name]), route('currencies.edit', [$currency->id])); } ); Breadcrumbs::register( 'currencies.delete', - function (BreadCrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionCurrency $currency) { $breadcrumbs->parent('currencies.index'); $breadcrumbs->push(trans('breadcrumbs.delete_currency', ['name' => $currency->name]), route('currencies.delete', [$currency->id])); } @@ -518,7 +535,7 @@ try { // EXPORT Breadcrumbs::register( 'export.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.export_data'), route('export.index')); } @@ -527,14 +544,14 @@ try { // PIGGY BANKS Breadcrumbs::register( 'piggy-banks.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.piggyBanks'), route('piggy-banks.index')); } ); Breadcrumbs::register( 'piggy-banks.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('piggy-banks.index'); $breadcrumbs->push(trans('breadcrumbs.newPiggyBank'), route('piggy-banks.create')); } @@ -542,14 +559,14 @@ try { Breadcrumbs::register( 'piggy-banks.edit', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + function (BreadcrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { $breadcrumbs->parent('piggy-banks.show', $piggyBank); $breadcrumbs->push(trans('breadcrumbs.edit_piggyBank', ['name' => $piggyBank->name]), route('piggy-banks.edit', [$piggyBank->id])); } ); Breadcrumbs::register( 'piggy-banks.delete', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + function (BreadcrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { $breadcrumbs->parent('piggy-banks.show', $piggyBank); $breadcrumbs->push(trans('firefly.delete_piggy_bank', ['name' => $piggyBank->name]), route('piggy-banks.delete', [$piggyBank->id])); } @@ -557,7 +574,7 @@ try { Breadcrumbs::register( 'piggy-banks.show', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + function (BreadcrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { $breadcrumbs->parent('piggy-banks.index'); $breadcrumbs->push($piggyBank->name, route('piggy-banks.show', [$piggyBank->id])); } @@ -565,7 +582,7 @@ try { Breadcrumbs::register( 'piggy-banks.add-money-mobile', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + function (BreadcrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { $breadcrumbs->parent('piggy-banks.show', $piggyBank); $breadcrumbs->push(trans('firefly.add_money_to_piggy', ['name' => $piggyBank->name]), route('piggy-banks.add-money-mobile', [$piggyBank->id])); } @@ -573,7 +590,7 @@ try { Breadcrumbs::register( 'piggy-banks.remove-money-mobile', - function (BreadCrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { + function (BreadcrumbsGenerator $breadcrumbs, PiggyBank $piggyBank) { $breadcrumbs->parent('piggy-banks.show', $piggyBank); $breadcrumbs->push( trans('firefly.remove_money_from_piggy_title', ['name' => $piggyBank->name]), @@ -585,7 +602,7 @@ try { // IMPORT Breadcrumbs::register( 'import.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.import_index_title'), route('import.index')); } @@ -593,7 +610,7 @@ try { Breadcrumbs::register( 'import.prerequisites.index', - function (BreadCrumbsGenerator $breadcrumbs, string $importProvider) { + function (BreadcrumbsGenerator $breadcrumbs, string $importProvider) { $breadcrumbs->parent('import.index'); $breadcrumbs->push(trans('import.prerequisites_breadcrumb_' . $importProvider), route('import.prerequisites.index', [$importProvider])); } @@ -601,7 +618,7 @@ try { Breadcrumbs::register( 'import.job.configuration.index', - function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { + function (BreadcrumbsGenerator $breadcrumbs, ImportJob $job) { $breadcrumbs->parent('import.index'); $breadcrumbs->push(trans('import.job_configuration_breadcrumb', ['key' => $job->key]), route('import.job.configuration.index', [$job->key])); } @@ -609,7 +626,7 @@ try { Breadcrumbs::register( 'import.job.status.index', - function (BreadCrumbsGenerator $breadcrumbs, ImportJob $job) { + function (BreadcrumbsGenerator $breadcrumbs, ImportJob $job) { $breadcrumbs->parent('import.index'); $breadcrumbs->push(trans('import.job_status_breadcrumb', ['key' => $job->key]), route('import.job.status.index', [$job->key])); } @@ -619,7 +636,7 @@ try { // PREFERENCES Breadcrumbs::register( 'preferences.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.preferences'), route('preferences.index')); } @@ -627,7 +644,7 @@ try { Breadcrumbs::register( 'profile.code', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index')); } @@ -636,14 +653,14 @@ try { // PROFILE Breadcrumbs::register( 'profile.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.profile'), route('profile.index')); } ); Breadcrumbs::register( 'profile.change-password', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('profile.index'); $breadcrumbs->push(trans('breadcrumbs.changePassword'), route('profile.change-password')); } @@ -651,7 +668,7 @@ try { Breadcrumbs::register( 'profile.change-email', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('profile.index'); $breadcrumbs->push(trans('breadcrumbs.change_email'), route('profile.change-email')); } @@ -659,7 +676,7 @@ try { Breadcrumbs::register( 'profile.delete-account', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('profile.index'); $breadcrumbs->push(trans('firefly.delete_account'), route('profile.delete-account')); } @@ -668,7 +685,7 @@ try { // REPORTS Breadcrumbs::register( 'reports.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.reports'), route('reports.index')); } @@ -676,7 +693,7 @@ try { Breadcrumbs::register( 'reports.report.audit', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); $monthFormat = (string)trans('config.month_and_day'); @@ -689,7 +706,7 @@ try { ); Breadcrumbs::register( 'reports.report.budget', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $budgetIds, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $accountIds, string $budgetIds, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); $monthFormat = (string)trans('config.month_and_day'); @@ -703,7 +720,7 @@ try { Breadcrumbs::register( 'reports.report.tag', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $tagTags, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $accountIds, string $tagTags, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); $monthFormat = (string)trans('config.month_and_day'); @@ -717,7 +734,7 @@ try { Breadcrumbs::register( 'reports.report.category', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $categoryIds, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $accountIds, string $categoryIds, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); $monthFormat = (string)trans('config.month_and_day'); @@ -731,7 +748,7 @@ try { Breadcrumbs::register( 'reports.report.account', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, string $expenseIds, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $accountIds, string $expenseIds, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); $monthFormat = (string)trans('config.month_and_day'); @@ -745,7 +762,7 @@ try { Breadcrumbs::register( 'reports.report.default', - function (BreadCrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, string $accountIds, Carbon $start, Carbon $end) { $breadcrumbs->parent('reports.index'); $monthFormat = (string)trans('config.month_and_day'); @@ -760,7 +777,7 @@ try { // New user Controller Breadcrumbs::register( 'new-user.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.getting_started'), route('new-user.index')); } @@ -769,14 +786,14 @@ try { // Recurring transactions controller: Breadcrumbs::register( 'recurring.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.recurrences'), route('recurring.index')); } ); Breadcrumbs::register( 'recurring.show', - function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) { + function (BreadcrumbsGenerator $breadcrumbs, Recurrence $recurrence) { $breadcrumbs->parent('recurring.index'); $breadcrumbs->push($recurrence->title, route('recurring.show', [$recurrence->id])); } @@ -784,7 +801,7 @@ try { Breadcrumbs::register( 'recurring.delete', - function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) { + function (BreadcrumbsGenerator $breadcrumbs, Recurrence $recurrence) { $breadcrumbs->parent('recurring.index'); $breadcrumbs->push(trans('firefly.delete_recurring', ['title' => $recurrence->title]), route('recurring.delete', [$recurrence->id])); } @@ -792,7 +809,7 @@ try { Breadcrumbs::register( 'recurring.edit', - function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) { + function (BreadcrumbsGenerator $breadcrumbs, Recurrence $recurrence) { $breadcrumbs->parent('recurring.index'); $breadcrumbs->push(trans('firefly.edit_recurrence', ['title' => $recurrence->title]), route('recurring.edit', [$recurrence->id])); } @@ -800,7 +817,7 @@ try { Breadcrumbs::register( 'recurring.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('recurring.index'); $breadcrumbs->push(trans('firefly.create_new_recurrence'), route('recurring.create')); } @@ -809,7 +826,7 @@ try { // Rules Breadcrumbs::register( 'rules.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('firefly.rules'), route('rules.index')); } @@ -817,42 +834,42 @@ try { Breadcrumbs::register( 'rules.create', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup])); } ); Breadcrumbs::register( 'rules.edit', - function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { + function (BreadcrumbsGenerator $breadcrumbs, Rule $rule) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push(trans('firefly.edit_rule', ['title' => $rule->title]), route('rules.edit', [$rule])); } ); Breadcrumbs::register( 'rules.delete', - function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { + function (BreadcrumbsGenerator $breadcrumbs, Rule $rule) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push(trans('firefly.delete_rule', ['title' => $rule->title]), route('rules.delete', [$rule])); } ); Breadcrumbs::register( 'rule-groups.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push(trans('firefly.make_new_rule_group'), route('rule-groups.create')); } ); Breadcrumbs::register( 'rule-groups.edit', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push(trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]), route('rule-groups.edit', [$ruleGroup])); } ); Breadcrumbs::register( 'rule-groups.delete', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push(trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]), route('rule-groups.delete', [$ruleGroup])); } @@ -860,7 +877,7 @@ try { Breadcrumbs::register( 'rules.select-transactions', - function (BreadCrumbsGenerator $breadcrumbs, Rule $rule) { + function (BreadcrumbsGenerator $breadcrumbs, Rule $rule) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push( trans('firefly.rule_select_transactions', ['title' => $rule->title]), route('rules.select-transactions', [$rule]) @@ -870,7 +887,7 @@ try { Breadcrumbs::register( 'rule-groups.select-transactions', - function (BreadCrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { $breadcrumbs->parent('rules.index'); $breadcrumbs->push( trans('firefly.rule_group_select_transactions', ['title' => $ruleGroup->title]), route('rule-groups.select-transactions', [$ruleGroup]) @@ -881,7 +898,7 @@ try { // SEARCH Breadcrumbs::register( 'search.index', - function (BreadCrumbsGenerator $breadcrumbs, $query) { + function (BreadcrumbsGenerator $breadcrumbs, $query) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.search_result', ['query' => $query]), route('search.index')); } @@ -890,7 +907,7 @@ try { // TAGS Breadcrumbs::register( 'tags.index', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.tags'), route('tags.index')); } @@ -898,7 +915,7 @@ try { Breadcrumbs::register( 'tags.create', - function (BreadCrumbsGenerator $breadcrumbs) { + function (BreadcrumbsGenerator $breadcrumbs) { $breadcrumbs->parent('tags.index'); $breadcrumbs->push(trans('breadcrumbs.createTag'), route('tags.create')); } @@ -906,7 +923,7 @@ try { Breadcrumbs::register( 'tags.edit', - function (BreadCrumbsGenerator $breadcrumbs, Tag $tag) { + function (BreadcrumbsGenerator $breadcrumbs, Tag $tag) { $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon); $breadcrumbs->push(trans('breadcrumbs.edit_tag', ['tag' => $tag->tag]), route('tags.edit', [$tag->id])); } @@ -914,7 +931,7 @@ try { Breadcrumbs::register( 'tags.delete', - function (BreadCrumbsGenerator $breadcrumbs, Tag $tag) { + function (BreadcrumbsGenerator $breadcrumbs, Tag $tag) { $breadcrumbs->parent('tags.show', $tag, '(nothing)', new Carbon, new Carbon); $breadcrumbs->push(trans('breadcrumbs.delete_tag', ['tag' => $tag->tag]), route('tags.delete', [$tag->id])); } @@ -922,7 +939,7 @@ try { Breadcrumbs::register( 'tags.show', - function (BreadCrumbsGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) { + function (BreadcrumbsGenerator $breadcrumbs, Tag $tag, string $moment, Carbon $start, Carbon $end) { $breadcrumbs->parent('tags.index'); $breadcrumbs->push($tag->tag, route('tags.show', [$tag->id, $moment])); if ('all' === $moment) { @@ -944,7 +961,7 @@ try { Breadcrumbs::register( 'transactions.index', - function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { + function (BreadcrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); @@ -962,7 +979,7 @@ try { Breadcrumbs::register( 'transactions.index.all', - function (BreadCrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { + function (BreadcrumbsGenerator $breadcrumbs, string $what, Carbon $start = null, Carbon $end = null) { $breadcrumbs->parent('home'); $breadcrumbs->push(trans('breadcrumbs.' . $what . '_list'), route('transactions.index', [$what])); } @@ -970,7 +987,7 @@ try { Breadcrumbs::register( 'transactions.create', - function (BreadCrumbsGenerator $breadcrumbs, string $what) { + function (BreadcrumbsGenerator $breadcrumbs, string $what) { $breadcrumbs->parent('transactions.index', $what); $breadcrumbs->push(trans('breadcrumbs.create_' . e($what)), route('transactions.create', [$what])); } @@ -978,7 +995,7 @@ try { Breadcrumbs::register( 'transactions.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { $breadcrumbs->parent('transactions.show', $journal); $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.edit', [$journal->id])); } @@ -987,7 +1004,7 @@ try { // also edit reconciliations: Breadcrumbs::register( 'accounts.reconcile.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { $breadcrumbs->parent('transactions.show', $journal); $breadcrumbs->push( trans('breadcrumbs.edit_reconciliation', ['description' => $journal->description]), route('accounts.reconcile.edit', [$journal->id]) @@ -997,7 +1014,7 @@ try { Breadcrumbs::register( 'transactions.delete', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { $breadcrumbs->parent('transactions.show', $journal); $breadcrumbs->push(trans('breadcrumbs.delete_journal', ['description' => $journal->description]), route('transactions.delete', [$journal->id])); } @@ -1005,16 +1022,18 @@ try { Breadcrumbs::register( 'transactions.show', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { $what = strtolower($journal->transactionType->type); + $title = limitStringLength($journal->description); + $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push($journal->description, route('transactions.show', [$journal->id])); + $breadcrumbs->push($title, route('transactions.show', [$journal->id])); } ); Breadcrumbs::register( 'transactions.convert.index', - function (BreadCrumbsGenerator $breadcrumbs, TransactionType $destinationType, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionType $destinationType, TransactionJournal $journal) { $breadcrumbs->parent('transactions.show', $journal); $breadcrumbs->push( trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]), @@ -1026,7 +1045,7 @@ try { // MASS TRANSACTION EDIT / DELETE Breadcrumbs::register( 'transactions.mass.edit', - function (BreadCrumbsGenerator $breadcrumbs, Collection $journals): void { + function (BreadcrumbsGenerator $breadcrumbs, Collection $journals): void { if (\count($journals) > 0) { $journalIds = $journals->pluck('id')->toArray(); $what = strtolower($journals->first()['type']); @@ -1041,7 +1060,7 @@ try { Breadcrumbs::register( 'transactions.mass.delete', - function (BreadCrumbsGenerator $breadcrumbs, Collection $journals) { + function (BreadcrumbsGenerator $breadcrumbs, Collection $journals) { $journalIds = $journals->pluck('id')->toArray(); $what = strtolower($journals->first()->transactionType->type); $breadcrumbs->parent('transactions.index', $what); @@ -1052,7 +1071,7 @@ try { // BULK EDIT Breadcrumbs::register( 'transactions.bulk.edit', - function (BreadCrumbsGenerator $breadcrumbs, Collection $journals): void { + function (BreadcrumbsGenerator $breadcrumbs, Collection $journals): void { if ($journals->count() > 0) { $journalIds = $journals->pluck('id')->toArray(); $what = strtolower($journals->first()->transactionType->type); @@ -1071,7 +1090,7 @@ try { // SPLIT Breadcrumbs::register( 'transactions.split.edit', - function (BreadCrumbsGenerator $breadcrumbs, TransactionJournal $journal) { + function (BreadcrumbsGenerator $breadcrumbs, TransactionJournal $journal) { $breadcrumbs->parent('transactions.show', $journal); $breadcrumbs->push(trans('breadcrumbs.edit_journal', ['description' => $journal->description]), route('transactions.split.edit', [$journal->id])); } From 07a8c69ba8eff639f129625716f88fe9c137f8d6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 5 Aug 2018 15:33:49 +0200 Subject: [PATCH 031/166] New translations [skip ci] --- resources/lang/de_DE/firefly.php | 31 +++++++++++-- resources/lang/de_DE/form.php | 5 +- resources/lang/de_DE/import.php | 62 +++++++++++++------------ resources/lang/de_DE/list.php | 1 + resources/lang/de_DE/validation.php | 2 +- resources/lang/en_US/firefly.php | 3 +- resources/lang/es_ES/config.php | 2 +- resources/lang/es_ES/firefly.php | 27 ++++++++++- resources/lang/es_ES/form.php | 3 ++ resources/lang/es_ES/import.php | 4 ++ resources/lang/es_ES/list.php | 1 + resources/lang/es_ES/validation.php | 2 +- resources/lang/fr_FR/firefly.php | 27 ++++++++++- resources/lang/fr_FR/form.php | 3 ++ resources/lang/fr_FR/import.php | 4 ++ resources/lang/fr_FR/list.php | 1 + resources/lang/fr_FR/validation.php | 2 +- resources/lang/id_ID/config.php | 2 +- resources/lang/id_ID/firefly.php | 27 ++++++++++- resources/lang/id_ID/form.php | 3 ++ resources/lang/id_ID/import.php | 4 ++ resources/lang/id_ID/list.php | 1 + resources/lang/id_ID/validation.php | 2 +- resources/lang/it_IT/firefly.php | 71 +++++++++++++++++++---------- resources/lang/it_IT/form.php | 19 ++++---- resources/lang/it_IT/import.php | 68 ++++++++++++++------------- resources/lang/it_IT/list.php | 19 ++++---- resources/lang/it_IT/validation.php | 6 +-- resources/lang/nl_NL/firefly.php | 27 ++++++++++- resources/lang/nl_NL/form.php | 3 ++ resources/lang/nl_NL/import.php | 4 ++ resources/lang/nl_NL/list.php | 1 + resources/lang/nl_NL/validation.php | 2 +- resources/lang/pl_PL/config.php | 2 +- resources/lang/pl_PL/firefly.php | 27 ++++++++++- resources/lang/pl_PL/form.php | 3 ++ resources/lang/pl_PL/import.php | 4 ++ resources/lang/pl_PL/list.php | 1 + resources/lang/pl_PL/validation.php | 2 +- resources/lang/pt_BR/config.php | 2 +- resources/lang/pt_BR/firefly.php | 27 ++++++++++- resources/lang/pt_BR/form.php | 3 ++ resources/lang/pt_BR/import.php | 4 ++ resources/lang/pt_BR/list.php | 1 + resources/lang/pt_BR/validation.php | 2 +- resources/lang/ru_RU/config.php | 2 +- resources/lang/ru_RU/firefly.php | 27 ++++++++++- resources/lang/ru_RU/form.php | 3 ++ resources/lang/ru_RU/import.php | 4 ++ resources/lang/ru_RU/list.php | 1 + resources/lang/ru_RU/validation.php | 2 +- resources/lang/tr_TR/config.php | 2 +- resources/lang/tr_TR/firefly.php | 27 ++++++++++- resources/lang/tr_TR/form.php | 3 ++ resources/lang/tr_TR/import.php | 4 ++ resources/lang/tr_TR/list.php | 1 + resources/lang/tr_TR/validation.php | 2 +- 57 files changed, 463 insertions(+), 132 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index b947fd5730..606e3df3d3 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Neues Bestandskonto', 'new_expense_account' => 'Neuer Debitor (Geldausgang)', 'new_revenue_account' => 'Neuer Kreditor (Geldeingang)', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Neuer Kostenrahmen', 'new_bill' => 'Neue Rechnung', 'block_account_logout' => 'Sie wurden ausgeloggt. Blockierte Benutzerkonten können diese Seite nicht nutzen. Haben Sie sich mit einer gültigen E-Mail Adresse registriert?', @@ -443,7 +444,7 @@ return [ 'pref_home_screen_accounts' => 'Konten auf dem Startbildschirm', 'pref_home_screen_accounts_help' => 'Welche Konten sollen auf dem Startbildschirm angezeigt werden?', 'pref_view_range' => 'Sichtbare Zeiträume', - 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', + 'pref_view_range_help' => 'Einige Diagramme werden automatisch in Abschnitte aufgeteilt. Ihre Kostenrahmen werden ebenfalls in Abschnitte unterteilt. Welchen Zeitraum bevorzugen Sie?', 'pref_1D' => 'Ein Tag', 'pref_1W' => 'Eine Woche', 'pref_1M' => 'Ein Monat', @@ -590,6 +591,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'invalid_convert_selection' => 'Das von Ihnen ausgewählte Konto wird für diese Transaktion bereits verwendet oder ist nicht vorhanden.', 'source_or_dest_invalid' => 'Die korrekten Buchungsdetails konnten nicht gefunden werden. Eine Konvertierung ist nicht möglich.', + // create new stuff: 'create_new_withdrawal' => 'Erstelle eine neue Ausgabe', 'create_new_deposit' => 'Erstelle ein neues Einkommen', @@ -687,6 +689,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'delete_asset_account' => 'Bestandskonto „:name” löschen', 'delete_expense_account' => 'Spesenkonto „:name” löschen', 'delete_revenue_account' => 'Erlöskonto „:name” löschen', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Bestandskonto „:name” erfolgreich gelöscht', 'expense_deleted' => 'Spesenkonto „:name” erfolgreich gelöscht', 'revenue_deleted' => 'Erlöskonto „:name” erfolgreich gelöscht', @@ -696,13 +699,15 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'make_new_asset_account' => 'Neues Bestandskonto erstellen', 'make_new_expense_account' => 'Neuen Debitor (Ausgabe) erstellen', 'make_new_revenue_account' => 'Neuen Schuldner erstellen', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Bestandskonten', 'expense_accounts' => 'Debitoren (Ausgaben)', 'revenue_accounts' => 'Schuldner', 'cash_accounts' => 'Bargeldkonten', 'Cash account' => 'Bargeldkonto', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Konto ":account" ausgleichen', - 'overview_of_reconcile_modal' => 'Overview of reconciliation', + 'overview_of_reconcile_modal' => 'Überblick über den Kontenabgleich', 'delete_reconciliation' => 'Kontenabgleich löschen', 'update_reconciliation' => 'Kontenabgleich aktualisieren', 'amount_cannot_be_zero' => 'Der Betrag darf nicht Null sein', @@ -734,7 +739,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'select_more_than_one_category' => 'Bitte wählen Sie mehr als eine Kategorie aus', 'select_more_than_one_budget' => 'Bitte wählen Sie mehr als einen Kostenrahmen aus', 'select_more_than_one_tag' => 'Bitte wählen Sie mehr als ein Schlüsselwort aus', - 'account_default_currency' => 'Wenn Sie eine andere Währung wählen, wird bei neuen Buchungen auf diesem Konto diese Währung vorausgewählt.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Ihr Firefly III-Konto verfügt über mehr Geld, als Ihre Bank behauptet. Es gibt mehrere Möglichkeiten. Bitte wählen Sie aus, was Sie tun möchten. Drücken Sie anschließend auf „Ausgleich bestätigen”.', 'reconcile_has_less' => 'Ihr Firefly III-Konto verfügt über weniger Geld, als Ihre Bank behauptet. Es gibt mehrere Möglichkeiten. Bitte wählen Sie aus, was Sie tun möchten. Drücken Sie dann auf „Ausgleich bestätigen”.', 'reconcile_is_equal' => 'Ihr Firefly III-Konto und Ihre Kontoauszüge stimmen überein. Es gibt nichts zu tun. Bitte drücken Sie auf „Ausgleich bestätigen”, um Ihre Eingaben zu bestätigen.', @@ -752,6 +757,9 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'already_cleared_transactions' => 'Bereits ausgeglichene Umsätze (:count)', 'submitted_end_balance' => 'Übermittelter Abschlussguthaben', 'initial_balance_description' => 'Anfangsguthaben für „:account”', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Neue Kategorie', @@ -815,6 +823,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', // new user: 'welcome' => 'Willkommen bei Firefly III!', 'submit' => 'Absenden', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Erste Schritte', 'to_get_started' => 'Es ist schön, dass Sie Firefly III erfolgreich installiert haben. Um mit diesem Tool zu beginnen, geben Sie bitte den Namen Ihrer Bank und das Guthaben Ihres Hauptkontos ein. Machen Sie sich keine Sorgen, wenn Sie mehrere Konten haben. Sie können diese später hinzufügen. Dies ist nur der Anfang.', 'savings_balance_text' => 'Firefly III erstellt automatisch ein Sparkonto für Sie. Standardmäßig befindet sich kein Geld auf Ihrem Sparkonto, aber wenn Sie Firefly III das Guthaben mitteilen, wird es als solches gespeichert.', @@ -853,6 +862,10 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'Expense account' => 'Debitor (Ausgabe)', 'Revenue account' => 'Kreditor Einnahme', 'Initial balance account' => 'Eröffnungssaldo', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Kostenrahmen', 'tags' => 'Schlagwörter', 'reports' => 'Berichte', @@ -882,6 +895,10 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'monthly' => 'Monatlich', 'profile' => 'Profil', 'errors' => 'Fehler', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Standardfinanzbericht zwischen :start und :end', @@ -1133,6 +1150,10 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'deleted_link' => 'Verknüpfung löschen', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'bezieht sich auf', 'is (partially) refunded by_inward' => 'wird (teilweise) erstattet durch', 'is (partially) paid for by_inward' => 'wird (teilweise) bezahlt von', @@ -1180,6 +1201,10 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'no_accounts_intro_revenue' => 'Sie verfügen noch über keine Ertragskonten. Ertragskonten sind die Einrichtungen, von denen Sie Geld erhalten, wie z.B. von Ihrem Arbeitgeber.', 'no_accounts_imperative_revenue' => 'Ertragskonten werden beim Anlegen von Buchungen automatisch angelegt, können aber auch manuell angelegt werden. Lassen Sie uns jetzt eines erstellen:', 'no_accounts_create_revenue' => 'Neues Erlöskonto erstellen', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Lassen Sie uns jetzt ein Haushaltsplan erstellen', 'no_budgets_intro_default' => 'Sie verfügen noch über keine Kostenrahmen. Kostenrahmen werden verwendet, um Ihre Ausgaben in logische Gruppen zu gliedern, die Sie mit einem weichen Obergrenzemlimit versehen können, um Ihre Ausgaben zu begrenzen.', 'no_budgets_imperative_default' => 'Die Kostenrahmen sind die grundlegenden Instrumente des Finanzmanagements. Lassen Sie uns jetzt einen erstellen:', diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index dbe1081bd2..48097666d0 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Bestätigung', 'api_key' => 'API-Schlüssel', 'remember_me' => 'Angemeldet bleiben', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Quellkonto (Bestandskonto)', 'destination_account_expense' => 'Zielkonto (Unkostenkonto)', @@ -237,6 +240,6 @@ return [ 'repetitions' => 'Wiederholungen', 'calendar' => 'Kalender', 'weekend' => 'Wochenende', - 'client_secret' => 'Client secret', + 'client_secret' => 'Kundengeheimnis', ]; diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index c656f54b79..d8257c6c25 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -28,11 +28,11 @@ return [ 'prerequisites_breadcrumb_fake' => 'Voraussetzungen für den Scheinimportanbieter', 'prerequisites_breadcrumb_spectre' => 'Voraussetzungen für Spectre', 'prerequisites_breadcrumb_bunq' => 'Voraussetzungen für bunq', - 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'prerequisites_breadcrumb_ynab' => 'Voraussetzungen für YNAB', 'job_configuration_breadcrumb' => 'Konfiguration für „:key”', 'job_status_breadcrumb' => 'Importstatus für „:key”', 'cannot_create_for_provider' => 'Firefly III konnte keine Aufgabe für den Anbieter „:provider” erstellen.', - 'disabled_for_demo_user' => 'disabled in demo', + 'disabled_for_demo_user' => 'in der Demo deaktiviert', // index page: 'general_index_title' => 'Datei importieren', @@ -45,7 +45,7 @@ return [ 'button_plaid' => 'Import mit Plaid', 'button_yodlee' => 'Importieren mit Yodlee', 'button_quovo' => 'Import mit Quovo', - 'button_ynab' => 'Import from You Need A Budget', + 'button_ynab' => 'Aus „You Need A Budget” importieren', // global config box (index) 'global_config_title' => 'Allgemeine Importkonfiguration', 'global_config_text' => 'In Zukunft wird dieses Feld Einstellungen enthalten, die für ALLE oben genannten Importanbieter gelten.', @@ -59,7 +59,7 @@ return [ 'do_prereq_plaid' => 'Voraussetzungen für den Import mit Plaid', 'do_prereq_yodlee' => 'Voraussetzungen für den Import mit Yodlee', 'do_prereq_quovo' => 'Voraussetzungen für den Import mit Quovo', - 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + 'do_prereq_ynab' => 'Voraussetzungen für den Import aus YNAB', // provider config box (index) 'can_config_title' => 'Einstellungen importieren', @@ -81,14 +81,15 @@ return [ 'prereq_bunq_title' => 'Voraussetzungen für einen Import von bunq', 'prereq_bunq_text' => 'Um aus „bunq” importieren zu können, benötigen Sie einen API-Schlüssel. Sie können diesen über die App bekommen. Bitte beachten Sie, dass sich die Importfunktion von „bunq” noch im BETA-Stadium befindet. Es wurde nur gegen die Sandbox-API getestet.', 'prereq_bunq_ip' => '„bunq” benötigt Ihre öffentlich zugängliche IP-Adresse. Firefly III versuchte, diese mithilfe des ipify-Diensts auszufüllen. Stellen Sie sicher, dass diese IP-Adresse korrekt ist, da sonst der Import fehlschlägt.', - 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', - 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', - 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'prereq_ynab_title' => 'Voraussetzungen für einen Import aus YNAB', + 'prereq_ynab_text' => 'Um Buchungen von YNAB herunterladen zu können, erstellen Sie bitte eine neue Anwendung auf Ihrer Seite der Entwicklereinstellungen und geben Sie die Client-ID und das Geheimnis auf dieser Seite ein.', + 'prereq_ynab_redirect' => 'Um die Konfiguration abzuschließen, geben Sie die folgende Adresse auf der Seite Entwicklereinstellungen unter „URI-Weiterleitung(en)” ein.', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Schein-API-Schlüssel erfolgreich gespeichert!', 'prerequisites_saved_for_spectre' => 'App-ID und Geheimnis gespeichert!', 'prerequisites_saved_for_bunq' => 'API-Schlüssel und IP gespeichert!', - 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', + 'prerequisites_saved_for_ynab' => 'YNAB-Client-ID und Geheimnis wurde gespeichert!', // job configuration: 'job_config_apply_rules_title' => 'Aufgabenkonfiguration - Regeln übernehmen?', @@ -148,29 +149,32 @@ return [ '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.', - 'ynab_account_closed' => 'Account is closed!', - 'ynab_account_deleted' => 'Account is deleted!', - 'ynab_account_type_savings' => 'savings account', - 'ynab_account_type_checking' => 'checking account', - 'ynab_account_type_cash' => 'cash account', - 'ynab_account_type_creditCard' => 'credit card', - 'ynab_account_type_lineOfCredit' => 'line of credit', - 'ynab_account_type_otherAsset' => 'other asset account', - 'ynab_account_type_otherLiability' => 'other liabilities', - 'ynab_account_type_payPal' => 'Paypal', - 'ynab_account_type_merchantAccount' => 'merchant account', - 'ynab_account_type_investmentAccount' => 'investment account', - 'ynab_account_type_mortgage' => 'mortgage', - 'ynab_do_not_import' => '(do not import)', - 'job_config_ynab_apply_rules' => 'Apply rules', - 'job_config_ynab_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.', + 'ynab_account_closed' => 'Konto geschlossen!', + 'ynab_account_deleted' => 'Konto gelöscht!', + 'ynab_account_type_savings' => 'Sparkonto', + 'ynab_account_type_checking' => 'Girokonto', + 'ynab_account_type_cash' => 'Bargeldkonto', + 'ynab_account_type_creditCard' => 'Kreditkarte', + 'ynab_account_type_lineOfCredit' => 'Kreditrahmen', + 'ynab_account_type_otherAsset' => 'Andere Bestandskonten', + 'ynab_account_type_otherLiability' => 'Sonstige Verbindlichkeiten', + 'ynab_account_type_payPal' => 'PayPal', + 'ynab_account_type_merchantAccount' => 'Händlerkonto', + 'ynab_account_type_investmentAccount' => 'Anlagekonto', + 'ynab_account_type_mortgage' => 'Hypothek', + 'ynab_do_not_import' => '(Nicht importieren)', + 'job_config_ynab_apply_rules' => 'Regeln anwenden', + 'job_config_ynab_apply_rules_text' => 'Standardmäßig werden Ihre Regeln auf die Buchungen angewendet, die während dieser Importroutine erstellt wurden. Wenn Sie dies nicht möchten, deaktivieren Sie dieses Kontrollkästchen.', // job configuration for YNAB: - 'job_config_ynab_select_budgets' => 'Select your budget', - 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', - 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', - 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', - 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_select_budgets' => 'Wählen Sie Ihren Kostenrahmen aus', + 'job_config_ynab_select_budgets_text' => 'Sie haben :count Kostenrahmen bei YNAB gespeichert. Bitte wählen Sie diejenige aus, aus der Firefly III die Buchungen importieren soll.', + 'job_config_ynab_no_budgets' => 'Es sind keine Kostenrahmen verfügbar, aus denen importiert werden kann.', + 'ynab_no_mapping' => 'Es wurden keine Konten zum Importieren ausgewählt.', + 'job_config_ynab_bad_currency' => 'Aus de(n/m) folgenden Kostenrahmen kann nicht importieren werden, da Sie über keine Konten mit der gleichen Währung wie diese Kostenrahmen verfügen.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php index f3143b9d65..0ed57523d4 100644 --- a/resources/lang/de_DE/list.php +++ b/resources/lang/de_DE/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Summe der Umbuchungen', 'reconcile' => 'Abgleichen', 'account_on_spectre' => 'Konto (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Von diesem Konto importieren', 'sepa-ct-id' => 'SEPA • Ende-zu-Ende-Identifikationsnummer', 'sepa-ct-op' => 'SEPA • Zielkonto-Identifikationsnummer', diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 1f1499f1b1..c81af7b982 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -50,7 +50,7 @@ return [ '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 :value sein.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute muss kleiner als 10.000.000 sein', 'active_url' => ':attribute ist keine gültige URL.', 'after' => ':attribute muss ein Datum nach :date sein.', diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 72edf342b2..7d8698b072 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -269,7 +269,8 @@ return [ 'move_rule_group_up' => 'Move rule group up', 'move_rule_group_down' => 'Move rule group down', 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', - 'make_new_rule' => 'Make new rule in rule group ":title"', + 'make_new_rule' => 'Make a new rule in rule group ":title"', + 'make_new_rule_no_group' => 'Make a new rule', 'rule_is_strict' => 'strict rule', 'rule_is_not_strict' => 'non-strict rule', 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', diff --git a/resources/lang/es_ES/config.php b/resources/lang/es_ES/config.php index 7cf9c9b0ac..42cf556fb5 100644 --- a/resources/lang/es_ES/config.php +++ b/resources/lang/es_ES/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'es', - 'locale' => 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', + 'locale' => 'es, Spanish, es_ES.utf8, es_ES.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%B %e, %Y', 'month_and_date_day' => '%A %B %e, %Y', diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index eeecbbfb2a..b6d5454837 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Nueva cuenta de activo', 'new_expense_account' => 'Nueva cuenta de gastos', 'new_revenue_account' => 'Nueva cuenta de ingresos', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Nuevo presupuesto', 'new_bill' => 'Nueva factura', 'block_account_logout' => 'Tu sesión ha sido cerrada. Las cuentas bloqueadas no pueden utilizar este sitio. ¿Registrarte con una dirección válida de correo electrónico?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'L a cuenta que usted ha selecionado ya esta en uso o no existe.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + // create new stuff: 'create_new_withdrawal' => 'Crear nuevo retiro', 'create_new_deposit' => 'Crear nuevo deposito', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Eliminar cuenta de activo ":name"', 'delete_expense_account' => 'Eliminar cuenta de gastos ":name"', 'delete_revenue_account' => 'Eliminar cuenta de ganancias ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Se ha eliminado exitosamente la cuenta de activos ":name"', 'expense_deleted' => 'Exitosamente eliminado la cuenta de gastos ":name"', 'revenue_deleted' => 'Exitosamente eliminado cuenta de ganacias ":name"', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Crear nueva cuenta de activo', 'make_new_expense_account' => 'Crear nueva cuenta de gastos', 'make_new_revenue_account' => 'Crear nueva cuenta de ingresos', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Cuenta de activos', 'expense_accounts' => 'Cuentas de gastos', 'revenue_accounts' => 'Cuentas de ingresos', 'cash_accounts' => 'Cuentas de efectivo', 'Cash account' => 'Cuenta de efectivo', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Reconciliar cuenta ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Eliminar reconciliacion', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Por favor seleccione mas de una categoría', 'select_more_than_one_budget' => 'Por favor seleccione mas de un presupuesto', 'select_more_than_one_tag' => 'Por favor seleccione mas de una etiqueta', - 'account_default_currency' => 'Si usted selecciona otra moneda, las nuevas transacciones de esta cuenta tendrán esta moneda preseleccionada.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Su libro principal de Firefly III tiene mas dinero de lo que su banco afirma debería tener, Hay varias opciones. por favor elija que hacer. luego, presione "confirmar reconciliación".', 'reconcile_has_less' => 'Su libro principal de Firefly III tiene menos dinero de el que su banco dice que usted debería tener. hay varias opciones. Por favor elija que hacer, luego presione " Confirmar reconciliación".', 'reconcile_is_equal' => 'Su libro principal de Firefly III y sus estados de cuenta coinciden. No hay nada que hacer. por favor presione "Confirmar reconciliación" para confirmar su entrada.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Transacciones ya despejadas (:count)', 'submitted_end_balance' => 'Balance final enviado', 'initial_balance_description' => 'Balance inicial para ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Nueva categoría', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Bienvenido a Firefly III!', 'submit' => 'Enviar', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Comenzando', 'to_get_started' => 'Es bueno ver que usted ha instalado con éxito Firefly III. Para comenzar con esta regla, por favor ingrese el nombre de su banco y el saldo de su cuenta de cheques principal. No se preocupe todavía si tiene varias cuentas. usted puede agregarlas luego. Es solo que Firefly III necesita algo para empezar.', 'savings_balance_text' => 'Firefly II creara automáticamente una cuenta de ahorros para usted. Por @@ -853,6 +862,10 @@ return [ 'Expense account' => 'Cuenta de gastos', 'Revenue account' => 'Cuenta de ganancia', 'Initial balance account' => 'Cuenta de balance inicial', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Presupuestos', 'tags' => 'Etiquetas', 'reports' => 'Informes', @@ -882,6 +895,10 @@ return [ 'monthly' => 'Mensual', 'profile' => 'Perfil', 'errors' => 'Errores', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Reporte financiero por defecto entre :start y :end', @@ -1133,6 +1150,10 @@ return [ 'deleted_link' => 'Enlace borrado', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'relacionado con', 'is (partially) refunded by_inward' => 'es (parcialmente) es devuelto por', 'is (partially) paid for by_inward' => 'es(parcialmente) pagado por', @@ -1180,6 +1201,10 @@ return [ 'no_accounts_intro_revenue' => 'Usted no tiene cuentas de ingresos aun. Cuentas de ingresos son los lugares donde usted recibe dinero, como su empleador.', 'no_accounts_imperative_revenue' => 'Las cuentas de ganancias se crean automáticamente cuando usted crea transacciones, pero usted puede crear una manualmente también, si usted quiere. vamos a crear una ahora:', 'no_accounts_create_revenue' => 'Crear una cuenta de ingresos', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Vamos a crear un presupuesto', 'no_budgets_intro_default' => 'Todavía no tienes presupuestos. Los presupuestos se usan para organizar tus gastos en grupos, a los que puedes asignar un tope para limitarlos.', 'no_budgets_imperative_default' => 'Los presupuestos son las herramientas básicas de la gestión financiera. Vamos a crear uno ahora:', diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 8518b5faae..a4ee7f4205 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Verificación', 'api_key' => 'Clave de API', 'remember_me' => 'Recordarme', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Cuenta de origen (cuenta de activos)', 'destination_account_expense' => 'Cuenta de destino (cuenta de gastos)', diff --git a/resources/lang/es_ES/import.php b/resources/lang/es_ES/import.php index 0ae2be314c..d0d217382d 100644 --- a/resources/lang/es_ES/import.php +++ b/resources/lang/es_ES/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => '¡App ID y secreto guardados!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index 0e74c7fa7e..5ebdfe2120 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Suma de transferencias', 'reconcile' => 'Reconciliar', 'account_on_spectre' => 'Cuenta (espectro)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Importar desde esta cuenta', 'sepa-ct-id' => 'SEPA End to End Identifier', 'sepa-ct-op' => 'SEPA Opposing Account Identifier', diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 1deea71386..5c5b28099f 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -50,7 +50,7 @@ return [ '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 :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', '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 9fbab65594..2fd873931b 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Nouveau compte d’actif', 'new_expense_account' => 'Nouveau compte de dépenses', 'new_revenue_account' => 'Nouveau compte de recettes', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Nouveau budget', 'new_bill' => 'Nouvelle facture', 'block_account_logout' => 'Vous avez été déconnecté. Les comptes bloqués ne peuvent pas utiliser ce site. Vous êtes-vous enregistré avec une adresse e-mail valide ?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'Le compte que vous avez sélectionné est déjà utilisé dans cette transaction ou n\'existe pas.', 'source_or_dest_invalid' => 'Impossible de trouver les détails de transaction corrects. La conversion n\'est pas possible.', + // create new stuff: 'create_new_withdrawal' => 'Créer une nouvelle dépense', 'create_new_deposit' => 'Créer un nouveau dépôt', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Supprimer le compte d’actif ":name"', 'delete_expense_account' => 'Supprimer le compte de dépenses ":name"', 'delete_revenue_account' => 'Supprimer le compte de recettes ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Compte d’actif ":name" correctement supprimé', 'expense_deleted' => 'Compte de dépenses ":name" correctement supprimé', 'revenue_deleted' => 'Compte de recettes ":name" correctement supprimé', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Créer un nouveau compte d’actif', 'make_new_expense_account' => 'Créer un nouveau compte de dépenses', 'make_new_revenue_account' => 'Créer un nouveau compte de recettes', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Comptes d’actif', 'expense_accounts' => 'Comptes de dépenses', 'revenue_accounts' => 'Comptes de recettes', 'cash_accounts' => 'Comptes de trésorerie', 'Cash account' => 'Compte de trésorerie', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Rapprocher le compte ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Supprimer le rapprochement', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Veuillez sélectionner plus d\'une catégorie', 'select_more_than_one_budget' => 'Veuillez sélectionner plus d\'un budget', 'select_more_than_one_tag' => 'Veuillez sélectionner plus d\'un tag', - 'account_default_currency' => 'Si vous sélectionnez une autre devise, les nouvelles transactions de ce compte auront cette devise pré-sélectionnée.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Votre registre Firefly III a plus d\'argent que ce que votre banque prétend que vous devriez avoir. Veuillez choisir comment gérer la situation parmi les différentes possibilités offertes. Ensuite, appuyez sur "Confirmer le rapprochement".', 'reconcile_has_less' => 'Votre registre Firefly III a moins d\'argent que ce que votre banque prétend que vous devriez avoir. Veuillez choisir comment gérer la situation parmi les différentes possibilités offertes. Ensuite, appuyez sur "Confirmer le rapprochement".', 'reconcile_is_equal' => 'Votre registre Firefly III et vos relevés bancaires correspondent. Il n\'y a rien à faire. Appuyez sur "Confirmer le rapprochement" pour confirmer votre entrée.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Transactions déjà pointées ( :count)', 'submitted_end_balance' => 'Solde final soumis', 'initial_balance_description' => 'Balance initiale pour ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Nouvelle catégorie', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Bienvenue sur Firefly III !', 'submit' => 'Soumettre', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Mise en route', 'to_get_started' => 'Vous venez d\'installer Firefly III avec succès. Pour commencer avec cet outil, entrez le nom de votre banque et le solde de votre compte courant principal. Ne vous inquiétez pas si vous avez plusieurs comptes. Vous pourrez les ajouter plus tard. Firefly III a simplement besoin de quelque chose pour commencer.', 'savings_balance_text' => 'Firefly III créera automatiquement un compte d\'épargne pour vous. Par défaut, il n\'y aura pas d\'argent sur ce compte d\'épargne, mais si vous indiquez un montant à Firefly III, il l\'enregistrera en tant que solde.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Compte de dépenses', 'Revenue account' => 'Compte de recettes', 'Initial balance account' => 'Balance initiale', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Budgets', 'tags' => 'Tags', 'reports' => 'Rapports', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Mensuel', 'profile' => 'Profil', 'errors' => 'Erreurs', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Rapport financier par défaut entre le :start et le :end', @@ -1132,6 +1149,10 @@ return [ 'deleted_link' => 'Lien supprimé', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'se rapporte à', 'is (partially) refunded by_inward' => 'est (partiellement) remboursé par', 'is (partially) paid for by_inward' => 'est (partiellement) payé par', @@ -1179,6 +1200,10 @@ return [ 'no_accounts_intro_revenue' => 'Vous n\'avez pas encore de compte de revenus. Les comptes de revenus sont les endroits où vous recevez de l\'argent, comme votre employeur.', 'no_accounts_imperative_revenue' => 'Les comptes de revenus sont créés automatiquement lorsque vous créez des transactions, mais vous pouvez en créer manuellement, si vous le souhaitez. Nous allons en créer un maintenant :', 'no_accounts_create_revenue' => 'Créer un compte de revenus', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Nous allons créer un budget', 'no_budgets_intro_default' => 'Vous n\'avez pas encore de budget. Les budgets sont utilisés pour organiser vos dépenses dans des groupes logiques, auxquels vous pouvez donner des plafonds pour limiter vos dépenses.', 'no_budgets_imperative_default' => 'Les budgets sont les outils de base de la gestion financière. Nous allons en créer un maintenant :', diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index 24b59b598b..20b7415f19 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Vérification', 'api_key' => 'Clé API', 'remember_me' => 'Se souvenir de moi', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Compte source (compte d\'actif)', 'destination_account_expense' => 'Compte de destination (compte de dépenses)', diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index f2f55531e9..ac68684fb1 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fausse clé API enregistrée avec succès !', 'prerequisites_saved_for_spectre' => 'ID App et secret enregistrés !', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index 4c2827d1b2..f433f901dc 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Somme des transferts', 'reconcile' => 'Rapprocher', 'account_on_spectre' => 'Compte (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Importer depuis ce compte', 'sepa-ct-id' => 'Identificateur de bout en bout SEPA', 'sepa-ct-op' => 'Identifiant de compte SEPA opposable', diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index a0d1f92e4e..8e8440fb45 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -50,7 +50,7 @@ return [ 'at_least_one_action' => 'Une règle doit avoir au moins une action.', 'base64' => 'Il ne s\'agit pas de données base64 valides.', 'model_id_invalid' => 'L’ID fournit ne semble pas valide pour ce modèle.', - 'more' => ':attribute doit être supérieur à :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute doit être inférieur à 10 000 000', '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/config.php b/resources/lang/id_ID/config.php index 42f77dcca9..3c1cc8c014 100644 --- a/resources/lang/id_ID/config.php +++ b/resources/lang/id_ID/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'id', - 'locale' => 'id, Bahasa Indonesia, id_ID, id_ID.utf8, id_ID.UTF-8', + 'locale' => 'id, Bahasa Indonesia, id_ID.utf8, id_ID.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', 'month_and_date_day' => '%A %B %e, %Y', diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index 77dea623aa..78f1c63539 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Akun aset baru', 'new_expense_account' => 'Akun pengeluaran baru', 'new_revenue_account' => 'Akun pendapatan baru', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Anggaran baru', 'new_bill' => 'Tagihan baru', 'block_account_logout' => 'Kamu telah keluar Akun yang diblokir tidak dapat menggunakan situs ini. Apakah Anda mendaftar dengan alamat email yang benar?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'Akun yang telah Anda pilih sudah digunakan dalam transaksi ini atau tidak ada.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + // create new stuff: 'create_new_withdrawal' => 'Buat penarikan baru', 'create_new_deposit' => 'Buat deposit baru', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Hapus akun aset ":name"', 'delete_expense_account' => 'Hapus akun pengeluaran ":name"', 'delete_revenue_account' => 'Hapus akun pendapatan ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Berhasil menghapus akun aset ":name"', 'expense_deleted' => 'Akun pengeluaran yang berhasil dihapus ":name"', 'revenue_deleted' => 'Berhasil menghapus akun pendapatan ":name"', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Buat akun aset baru', 'make_new_expense_account' => 'Buat akun pengeluaran baru', 'make_new_revenue_account' => 'Buat akun pendapatan baru', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Akun aset', 'expense_accounts' => 'Rekening pengeluaran', 'revenue_accounts' => 'Akun pendapatan', 'cash_accounts' => 'Akun kas', 'Cash account' => 'Akun kas', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Rekonsiliasi akun ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Hapus rekonsiliasi', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Silakan pilih lebih dari satu kategori', 'select_more_than_one_budget' => 'Silakan pilih lebih dari satu anggaran', 'select_more_than_one_tag' => 'Silakan pilih lebih dari satu tag', - 'account_default_currency' => 'Jika Anda memilih mata uang lain, transaksi baru dari akun ini akan memiliki mata uang ini pra-dipilih.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Buku Firefly III Anda memiliki lebih banyak uang di dalamnya dari bank Anda klaim Anda harus. Ada beberapa pilihan. Silahkan memilih apa yang harus dilakukan. Kemudian, tekan "Konfirmasi rekonsiliasi".', 'reconcile_has_less' => 'Buku Firefly III Anda memiliki sedikit uang di dalamnya daripada bank Anda klaim Anda harus. Ada beberapa pilihan. Silahkan memilih apa yang harus dilakukan. Kemudian, tekan "Konfirmasi rekonsiliasi".', 'reconcile_is_equal' => 'Buku Firefly III Anda dan pernyataan bank Anda cocok. Tidak ada hubungannya. Silahkan tekan "Konfirmasi rekonsiliasi" untuk mengkonfirmasi masukan Anda.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Sudah dibersihkan transaksi (:count)', 'submitted_end_balance' => 'Saldo akhir yang dikirim', 'initial_balance_description' => 'Initial balance for ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Kategori baru', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Welcome to Firefly III!', 'submit' => 'Menyerahkan', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Mulai', 'to_get_started' => 'Senang melihat Anda berhasil memasang Firefly III. Untuk memulai dengan alat ini, harap masukkan nama bank dan saldo rekening giro utama Anda. Jangan khawatir jika Anda memiliki banyak akun. Anda bisa menambahkannya nanti. Hanya saja Firefly III butuh sesuatu untuk memulai.', 'savings_balance_text' => 'Firefly III secara otomatis akan membuat rekening tabungan untuk Anda. Secara default, tidak akan ada uang di rekening tabungan Anda, tapi jika Anda memberi tahu Firefly III, saldo itu akan disimpan seperti itu.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Rekening pengeluaran', 'Revenue account' => 'Akun pendapatan', 'Initial balance account' => 'Akun saldo awal', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Anggaran', 'tags' => 'Tag', 'reports' => 'Laporan', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Bulanan', 'profile' => 'Profil', 'errors' => 'Kesalahan', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Laporan keuangan standar antara :start dan :end', @@ -1132,6 +1149,10 @@ return [ 'deleted_link' => 'Tautan dihapus', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'berhubungan dengan', 'is (partially) refunded by_inward' => '(sebagian) dikembalikan oleh', 'is (partially) paid for by_inward' => 'adalah (sebagian) dibayar oleh', @@ -1179,6 +1200,10 @@ return [ 'no_accounts_intro_revenue' => 'Anda belum memiliki akun pendapatan. Akun pendapatan adalah tempat di mana Anda menerima uang dari, seperti atasan Anda.', 'no_accounts_imperative_revenue' => 'Akun pendapatan dibuat secara otomatis saat Anda membuat transaksi, namun Anda dapat membuatnya secara manual juga, jika Anda mau. Mari kita ciptakan sekarang:', 'no_accounts_create_revenue' => 'Buat akun pendapatan', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Mari buat anggaran', 'no_budgets_intro_default' => 'Anda belum memiliki anggaran. Anggaran digunakan untuk mengatur pengeluaran Anda ke dalam kelompok logis, yang bisa Anda berikan topi lunak untuk membatasi pengeluaran Anda.', 'no_budgets_imperative_default' => 'Anggaran adalah alat dasar pengelolaan keuangan. Mari kita ciptakan sekarang:', diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index f6a6585720..7639bf7d0a 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Verifikasi', 'api_key' => 'Kunci API', 'remember_me' => 'Remember me', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Akun sumber (akun aset)', 'destination_account_expense' => 'Akun tujuan (akun pengeluaran)', diff --git a/resources/lang/id_ID/import.php b/resources/lang/id_ID/import.php index bb68fb307c..9e8c694924 100644 --- a/resources/lang/id_ID/import.php +++ b/resources/lang/id_ID/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/id_ID/list.php b/resources/lang/id_ID/list.php index b427560515..9b0cfc18a6 100644 --- a/resources/lang/id_ID/list.php +++ b/resources/lang/id_ID/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Jumlah transfer', 'reconcile' => 'Menyesuaikan', 'account_on_spectre' => 'Account (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Import from this account', 'sepa-ct-id' => 'SEPA End to End Identifier', 'sepa-ct-op' => 'SEPA Opposing Account Identifier', diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index bc80cc3711..6b800d8360 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -50,7 +50,7 @@ return [ '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 :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', 'active_url' => ':attribute bukan URL yang valid.', 'after' => ':attribute harus tanggal setelah :date.', diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index b402a9674e..3c767f2e6a 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Nuovo conto attività', 'new_expense_account' => 'Nuovo conto spese', 'new_revenue_account' => 'Nuovo conto entrate', + 'new_liabilities_account' => 'New liability', '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?', @@ -109,7 +110,7 @@ return [ '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', + 'left_in_budget_limit' => 'Disponibile per spese in base ai budget', 'current_period' => 'Periodo corrente', 'show_the_current_period_and_overview' => 'Mostra il periodo e la panoramica correnti', 'pref_languages_locale' => 'Affinché una lingua diversa dall\'inglese funzioni correttamente, il sistema operativo deve essere dotato delle corrette informazioni locali. Se questi non sono presenti, i dati di valuta, le date e gli importi potrebbero essere formattati in modo errato.', @@ -139,9 +140,9 @@ return [ 'chart_all_journals_for_account' => 'Grafico di tutte le transazioni per il conto :name', 'journals_in_period_for_account' => 'Tutte le transazioni per il conto :name fra :start e :end', 'transferred' => 'Trasferito', - 'all_withdrawal' => 'Tutte le Spese', + 'all_withdrawal' => 'Tutte le spese', 'all_transactions' => 'Tutte le Transazioni', - 'title_withdrawal_between' => 'Tutte le Spese tra :start e :end', + 'title_withdrawal_between' => 'Tutte le spese tra :start e :end', 'all_deposit' => 'Tutte le entrate', 'title_deposit_between' => 'Tutte le entrate fra :start e :end', 'all_transfers' => 'Tutti i trasferimenti', @@ -443,7 +444,7 @@ return [ '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' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', + 'pref_view_range_help' => 'Alcuni grafici vengono raggruppati automaticamente in periodi. Anche i tuoi budget saranno raggruppati in periodi. Quale periodo preferisci?', 'pref_1D' => 'Un giorno', 'pref_1W' => 'Una settimana', 'pref_1M' => 'Un mese', @@ -589,6 +590,7 @@ return [ '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: 'create_new_withdrawal' => 'Crea un nuovo prelievo', 'create_new_deposit' => 'Crea una nuova entrata', @@ -668,7 +670,7 @@ return [ 'bill_is_active' => 'Bolletta attiva', 'bill_expected_between' => 'Previsto tra :start e :end', 'bill_will_automatch' => 'La bolletta verrà automaticamente collegata alle transazioni corrispondenti', - 'skips_over' => 'salta sopra', + 'skips_over' => 'salta', 'bill_store_error' => 'Si è verificato un errore imprevisto durante la memorizzazione della nuova bolletta. Controlla i file di log', 'list_inactive_rule' => 'regola inattiva', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Elimina conto attività ":name"', 'delete_expense_account' => 'Elimina conto spese ":name"', 'delete_revenue_account' => 'Elimina conto entrate ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Conto attività ":name" eliminato correttamente', 'expense_deleted' => 'Conto spese ":name" eliminato correttamente', 'revenue_deleted' => 'Conto entrate ":name" eliminato correttamente', @@ -695,13 +698,15 @@ return [ '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', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Conti attività', 'expense_accounts' => 'Conti spese', 'revenue_accounts' => 'Conti entrate', 'cash_accounts' => 'Conti contanti', 'Cash account' => 'Conto contanti', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Riconciliazione conto ":account"', - 'overview_of_reconcile_modal' => 'Overview of reconciliation', + 'overview_of_reconcile_modal' => 'Panoramica della riconciliazione', 'delete_reconciliation' => 'Elimina riconciliazione', 'update_reconciliation' => 'Aggiorna riconciliazione', 'amount_cannot_be_zero' => 'L\'importo non può essere zero', @@ -733,10 +738,10 @@ return [ 'select_more_than_one_category' => 'Si prega di selezionare più di una categoria', 'select_more_than_one_budget' => 'Si prega di selezionare più di un budget', 'select_more_than_one_tag' => 'Si prega di selezionare più di una etichetta', - 'account_default_currency' => 'Se selezioni un\'altra valuta, le nuove transazioni di questo conto avranno questa valuta predefinita.', - '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".', + 'account_default_currency' => 'This will be the default currency associated with this account.', + 'reconcile_has_more' => 'Il libro mastro di Firefly III ha più denaro rispetto a quanto afferma la tua banca. Ci sono diverse opzioni. Scegli cosa fare. In seguito, premi "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.', + 'reconcile_is_equal' => 'Il libro mastro di Firefly III e i tuoi estratti conto bancari corrispondono. Non c\'è niente da fare. Premi "Conferma riconciliazione" per confermare l\'inserimento.', '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.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Transazioni già conciliate (:count)', 'submitted_end_balance' => 'Saldo finale inserito', 'initial_balance_description' => 'Saldo iniziale per ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Nuova categoria', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Benvenuto in Firefly III!', 'submit' => 'Invia', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', '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ù 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.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Conto spese', 'Revenue account' => 'Conto entrate', 'Initial balance account' => 'Saldo iniziale conto', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Budget', 'tags' => 'Etichette', 'reports' => 'Resoconti', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Mensile', 'profile' => 'Profilo', 'errors' => 'Errori', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Resoconto predefinito delle tue finanze tra :start e :end', @@ -897,7 +914,7 @@ return [ 'report_this_fiscal_year_quick' => 'Anno fiscale corrente - tutti i conti', 'report_all_time_quick' => 'Sempre tutti i conti', 'reports_can_bookmark' => 'Ricorda che i resoconti possono essere aggiunti ai segnalibri.', - 'incomeVsExpenses' => 'Entrate verso spese', + 'incomeVsExpenses' => 'Entrate vs spese', 'accountBalances' => 'Saldo dei conti', 'balanceStart' => 'Saldo all\'inizio del periodo', 'balanceEnd' => 'Saldo alla fine del periodo', @@ -999,10 +1016,10 @@ return [ 'day' => 'Giorno', 'budgeted' => 'Preventivato', 'period' => 'Periodo', - 'balance' => 'Bilancio', + 'balance' => 'Saldo', 'sum' => 'Somma', 'average' => 'Media', - 'balanceFor' => 'Bilancio per :name', + 'balanceFor' => 'Saldo di :name', // piggy banks: 'add_money_to_piggy' => 'Aggiungi denaro al salvadanaio":name"', @@ -1011,16 +1028,16 @@ return [ 'store_piggy_bank' => 'Salva il nuovo salvadanaio', 'stored_piggy_bank' => 'Salva il nuovo salvadanaio ":name"', 'account_status' => 'Stato conto', - 'left_for_piggy_banks' => 'Lasciato nei salvadanai', - 'sum_of_piggy_banks' => 'Somma di salvadanai', - 'saved_so_far' => 'Salvato finora', - 'left_to_save' => 'Lasciato per salvare', + 'left_for_piggy_banks' => 'Rimanente per i salvadanai', + 'sum_of_piggy_banks' => 'Somma dei salvadanai', + 'saved_so_far' => 'Risparmiato finora', + 'left_to_save' => 'Rimanente da risparmiare', 'suggested_amount' => 'Quantità mensile consigliata da salvare', 'add_money_to_piggy_title' => 'Aggiungi denaro al salvadanaio ":name"', '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' => 'Suggeriti per mese', + 'suggested_savings_per_month' => 'Suggerimento per mese', 'remove' => 'Rimuovi', 'max_amount_add' => 'L\'importo massimo che puoi aggiungere è', @@ -1051,7 +1068,7 @@ return [ 'updated_tag' => 'Etichetta ":tag" aggiornata', 'created_tag' => 'Etichetta ":tag" creata correttamente', - 'transaction_journal_information' => 'Informazione Transazione', + 'transaction_journal_information' => 'Informazioni transazione', 'transaction_journal_meta' => 'Meta informazioni', 'total_amount' => 'Importo totale', 'number_of_decimals' => 'Cifre decimali', @@ -1104,9 +1121,9 @@ return [ 'deleted_link_type' => 'Tipo di collegamento eliminato ":name"', 'stored_new_link_type' => 'Memorizza nuovo tipo di collegamento ":name"', 'cannot_edit_link_type' => 'Impossibile modificare il tipo di collegamento ":name"', - 'link_type_help_name' => 'Ie. "Duplicati"', - 'link_type_help_inward' => 'Ie. "duplicati"', - 'link_type_help_outward' => 'Ie. "è duplicato da"', + 'link_type_help_name' => 'Es.: "Duplicati"', + 'link_type_help_inward' => 'Es.: "duplicati"', + 'link_type_help_outward' => 'Es.: "è duplicata da"', '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', @@ -1132,14 +1149,18 @@ return [ 'deleted_link' => 'Elimina collegamento', // link translations: - 'relates to_inward' => 'inerente a', + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', + 'relates to_inward' => 'correlata a', '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' => 'Transazione in ingresso', 'outward_transaction' => 'Transazione in uscita', 'relates to_outward' => 'inerente a', - '(partially) refunds_outward' => '(parzialmente) rimborsa', + '(partially) refunds_outward' => 'rimborsa (parzialmente)', '(partially) pays for_outward' => '(parzialmente) paga per', '(partially) reimburses_outward' => '(parzialmente) rimborsa', @@ -1179,6 +1200,10 @@ return [ '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_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', '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.', 'no_budgets_imperative_default' => 'I budget sono gli strumenti di base della gestione finanziaria. Creiamone uno ora:', diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index 4fffc58257..ddecc52867 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -47,11 +47,11 @@ return [ 'journal_source_id' => 'Conto attività (origine)', 'BIC' => 'BIC', 'verify_password' => 'Verifica password di sicurezza', - 'source_account' => 'Conto sorgente', + 'source_account' => 'Conto di origine', 'destination_account' => 'Conto destinazione', 'journal_destination_id' => 'Conto attività (destinazione)', 'asset_destination_account' => 'Conto attività (destinazione)', - 'asset_source_account' => 'Conto attività (sorgente)', + 'asset_source_account' => 'Conto attività (origine)', 'journal_description' => 'Descrizione', 'note' => 'Note', 'split_journal' => 'Dividi questa transazione', @@ -84,11 +84,14 @@ return [ 'verification' => 'Verifica', 'api_key' => 'Chiave API', 'remember_me' => 'Ricordami', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Conto origine (conto attività)', 'destination_account_expense' => 'Conto destinazione (conto spese)', 'destination_account_asset' => 'Conto destinazione (conto attività)', - 'source_account_revenue' => 'Conto sorgente (conto entrate)', + 'source_account_revenue' => 'Conto di origine (conto entrate)', 'type' => 'Tipo', 'convert_Withdrawal' => 'Converti prelievo', 'convert_Deposit' => 'Converti entrata', @@ -99,7 +102,7 @@ return [ 'existing_attachments' => 'Allegati esistenti', 'date' => 'Data', 'interest_date' => 'Data interesse', - 'book_date' => 'Agenda', + 'book_date' => 'Data contabile', 'process_date' => 'Data elaborazione', 'category' => 'Categoria', 'tags' => 'Etichette', @@ -222,9 +225,9 @@ return [ 'due_date' => 'Data scadenza', 'payment_date' => 'Data pagamento', 'invoice_date' => 'Data fatturazione', - 'internal_reference' => 'Referenze interne', - 'inward' => 'Descrizione interna', - 'outward' => 'Descrizione esterna', + 'internal_reference' => 'Riferimento interno', + 'inward' => 'Descrizione in ingresso', + 'outward' => 'Descrizione in uscita', 'rule_group_id' => 'Gruppo regole', 'transaction_description' => 'Descrizione transazione', 'first_date' => 'Prima volta', @@ -237,6 +240,6 @@ return [ 'repetitions' => 'Ripetizioni', 'calendar' => 'Calendario', 'weekend' => 'Fine settimana', - 'client_secret' => 'Client secret', + 'client_secret' => 'Segreto del client', ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index 892ee094d6..cf03c06776 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -28,11 +28,11 @@ return [ 'prerequisites_breadcrumb_fake' => 'Prerequisiti per il fornitore di importazione fittizio', 'prerequisites_breadcrumb_spectre' => 'Prerequisiti per Spectre', 'prerequisites_breadcrumb_bunq' => 'Prerequisiti per bunq', - 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'prerequisites_breadcrumb_ynab' => 'Prerequisiti per YNAB', 'job_configuration_breadcrumb' => 'Configurazione per ":key"', 'job_status_breadcrumb' => 'Stato di importazione per ":key"', 'cannot_create_for_provider' => 'Firefly III non può creare un\'operazione per il provider ":provider".', - 'disabled_for_demo_user' => 'disabled in demo', + 'disabled_for_demo_user' => 'disabilitata nella demo', // index page: 'general_index_title' => 'Importa un file', @@ -45,10 +45,10 @@ return [ 'button_plaid' => 'Importa usando Plaid', 'button_yodlee' => 'Importa usando Yodlee', 'button_quovo' => 'Importa usando Quovo', - 'button_ynab' => 'Import from You Need A Budget', + 'button_ynab' => 'Importa da You Need A Budget', // global config box (index) 'global_config_title' => 'Configurazione globale di importazione', - 'global_config_text' => 'In futuro, questo riquadro presenterà le preferenze che si applicano a TUTTI i fornitori di importazione di cui sopra.', + 'global_config_text' => 'In futuro questo riquadro presenterà le preferenze che si applicano a TUTTI i fornitori di importazione di cui sopra.', // prerequisites box (index) 'need_prereq_title' => 'Prerequisiti di importazione', 'need_prereq_intro' => 'Alcuni metodi di importazione richiedono la tua attenzione prima che possano essere utilizzati. Ad esempio, potrebbero richiedere speciali chiavi API o segreti dell\'applicazione. Puoi configurarli qui. L\'icona indica se questi prerequisiti sono stati soddisfatti.', @@ -59,7 +59,7 @@ return [ 'do_prereq_plaid' => 'Prerequisiti per le importazioni usando Plaid', 'do_prereq_yodlee' => 'Prerequisiti per le importazioni usando Yodlee', 'do_prereq_quovo' => 'Prerequisiti per le importazioni usando Quovo', - 'do_prereq_ynab' => 'Prerequisites for imports from YNAB', + 'do_prereq_ynab' => 'Prerequisiti per le importazioni da YNAB', // provider config box (index) 'can_config_title' => 'Configurazione di importazione', @@ -81,14 +81,15 @@ return [ 'prereq_bunq_title' => 'Prerequisiti per un\'importazione da bunq', 'prereq_bunq_text' => 'Per importare da bunq, è necessario ottenere una chiave API. Puoi farlo attraverso l\'app. Si noti che la funzione di importazione per bunq è in BETA. È stato testato solo contro l\'API sandbox.', 'prereq_bunq_ip' => 'bunq richiede il tuo indirizzo IP esterno. Firefly III ha provato a riempire questo campo utilizzando il servizio ipify. Assicurati che questo indirizzo IP sia corretto altrimenti l\'importazione fallirà.', - 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', - 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', - 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'prereq_ynab_title' => 'Prerequisiti per un\'importazione da YNAB', + 'prereq_ynab_text' => 'Per poter scaricare le transazioni da YNAB crea una nuova applicazione nella tua Pagina delle impostazioni per sviluppatore e inserisci l\'ID e il segreto del client in questa pagina.', + 'prereq_ynab_redirect' => 'Per completare la configurazione inserisci il seguente URL nella Pagina delle impostazioni per sviluppatore alla voce "Reindirizza URI".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Chiave API fittizia memorizzata correttamente!', 'prerequisites_saved_for_spectre' => 'ID dell\'app e segreto memorizzati!', 'prerequisites_saved_for_bunq' => 'Chiave API e IP memorizzati!', - 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', + 'prerequisites_saved_for_ynab' => 'ID del client e segreto di YNAB memorizzati!', // job configuration: 'job_config_apply_rules_title' => 'Configurazione dell\'operazione - applicare le tue regole?', @@ -148,29 +149,32 @@ return [ '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.', - 'ynab_account_closed' => 'Account is closed!', - 'ynab_account_deleted' => 'Account is deleted!', - 'ynab_account_type_savings' => 'savings account', - 'ynab_account_type_checking' => 'checking account', - 'ynab_account_type_cash' => 'cash account', - 'ynab_account_type_creditCard' => 'credit card', - 'ynab_account_type_lineOfCredit' => 'line of credit', - 'ynab_account_type_otherAsset' => 'other asset account', - 'ynab_account_type_otherLiability' => 'other liabilities', - 'ynab_account_type_payPal' => 'Paypal', - 'ynab_account_type_merchantAccount' => 'merchant account', - 'ynab_account_type_investmentAccount' => 'investment account', - 'ynab_account_type_mortgage' => 'mortgage', - 'ynab_do_not_import' => '(do not import)', - 'job_config_ynab_apply_rules' => 'Apply rules', - 'job_config_ynab_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.', + 'ynab_account_closed' => 'Il conto è chiuso!', + 'ynab_account_deleted' => 'Il conto è stato eliminato!', + 'ynab_account_type_savings' => 'conto di risparmio', + 'ynab_account_type_checking' => 'conto corrente', + 'ynab_account_type_cash' => 'conto contanti', + 'ynab_account_type_creditCard' => 'carta di credito', + 'ynab_account_type_lineOfCredit' => 'linea di credito', + 'ynab_account_type_otherAsset' => 'altro conto attività', + 'ynab_account_type_otherLiability' => 'altre passività', + 'ynab_account_type_payPal' => 'PayPal', + 'ynab_account_type_merchantAccount' => 'conto d\'affari', + 'ynab_account_type_investmentAccount' => 'conto d\'investimento', + 'ynab_account_type_mortgage' => 'mutuo', + 'ynab_do_not_import' => '(non importare)', + 'job_config_ynab_apply_rules' => 'Applica regole', + 'job_config_ynab_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 YNAB: - 'job_config_ynab_select_budgets' => 'Select your budget', - 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', - 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', - 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', - 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_select_budgets' => 'Seleziona il tuo budget', + 'job_config_ynab_select_budgets_text' => 'Hai :count budget memorizzati in YNAB. Seleziona quello da cui Firefly III importerà le transazioni.', + 'job_config_ynab_no_budgets' => 'Non ci sono budget disponibili da cui importare.', + 'ynab_no_mapping' => 'Sembra che tu non abbia selezionato nessun conto da cui importare.', + 'job_config_ynab_bad_currency' => 'Non puoi importare dai seguenti budget perché non hai dei conti con la stessa valuta di questi budget.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', @@ -261,7 +265,7 @@ return [ 'column_amount_credit' => 'Importo (colonna credito)', 'column_amount-comma-separated' => 'Importo (virgola come separatore decimale)', 'column_bill-id' => 'ID bolletta (mappa FF3)', - 'column_bill-name' => 'Nome conto', + 'column_bill-name' => 'Nome bolletta', 'column_budget-id' => 'ID budget (mappa FF3)', 'column_budget-name' => 'Nome budget', 'column_category-id' => 'ID categoria (mappa FF3)', @@ -272,7 +276,7 @@ return [ '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-book' => 'Data contabile della transazione', 'column_date-process' => 'Data processo della transazione', 'column_date-transaction' => 'Data', 'column_date-due' => 'Data di scadenza della transazione', diff --git a/resources/lang/it_IT/list.php b/resources/lang/it_IT/list.php index 1fab9f426d..355a9f126c 100644 --- a/resources/lang/it_IT/list.php +++ b/resources/lang/it_IT/list.php @@ -29,8 +29,8 @@ return [ 'create_date' => 'Creato il', 'update_date' => 'Aggiornato il', 'updated_at' => 'Aggiornato il', - 'balance_before' => 'Bilancio prima', - 'balance_after' => 'Bilancio dopo', + 'balance_before' => 'Saldo precedente', + 'balance_after' => 'Saldo successivo', 'name' => 'Nome', 'role' => 'Ruolo', 'currentBalance' => 'Saldo corrente', @@ -45,21 +45,21 @@ return [ 'matchingAmount' => 'Importo', 'split_number' => 'Diviso #', 'destination' => 'Destinazione', - 'source' => 'Sorgente', + 'source' => 'Origine', 'next_expected_match' => 'Prossimo abbinamento previsto', 'automatch' => 'Abbinamento automatico?', 'repeat_freq' => 'Si ripete', 'description' => 'Descrizione', 'amount' => 'Importo', - 'internal_reference' => 'Referenze interne', + 'internal_reference' => 'Riferimento interno', 'date' => 'Data', 'interest_date' => 'Data interessi', - 'book_date' => 'Agenda', + 'book_date' => 'Data contabile', 'process_date' => 'Data lavorazione', 'due_date' => 'Data scadenza', 'payment_date' => 'Data pagamento', 'invoice_date' => 'Data fatturazione', - 'interal_reference' => 'Referenze interne', + 'interal_reference' => 'Riferimento interno', 'notes' => 'Note', 'from' => 'Da', 'piggy_bank' => 'Salvadanaio', @@ -80,7 +80,7 @@ return [ 'is_admin' => 'È amministratore', 'has_two_factor' => 'Ha 2FA', 'blocked_code' => 'Codice blocco', - 'source_account' => 'Conto sorgente', + 'source_account' => 'Conto di origine', 'destination_account' => 'Conto destinazione', 'accounts_count' => 'Numero di conti', 'journals_count' => 'Numero di transazioni', @@ -93,8 +93,8 @@ return [ 'rule_and_groups_count' => 'Numero di regole e gruppi di regole', 'tags_count' => 'Numero di etichette', 'tags' => 'Etichette', - 'inward' => 'Descrizione interna', - 'outward' => 'Descrizione esterna', + 'inward' => 'Descrizione in ingresso', + 'outward' => 'Descrizione in uscita', 'number_of_transactions' => 'Numero di transazioni', 'total_amount' => 'Importo totale', 'sum' => 'Somma', @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Somma dei trasferimenti', 'reconcile' => 'Riconcilia', 'account_on_spectre' => 'Conto (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Importo da questo conto', 'sepa-ct-id' => 'Identificativo End-To-End SEPA', 'sepa-ct-op' => 'Identificativo SEPA Conto Controparte', diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index a5ddcfa8dd..c9052a89ad 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -50,7 +50,7 @@ return [ '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 più grande di :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute deve essere minore di 10.000.000', 'active_url' => ':attribute non è un URL valido.', 'after' => ':attribute deve essere una data dopo :date.', @@ -70,7 +70,7 @@ return [ 'confirmed' => ':attribute la conferma non corrisponde.', 'date' => ':attribute non è una data valida', 'date_format' => ':attribute non corrisponde al formato :format.', - 'different' => ':attribute e :other deve essere diverso.', + 'different' => 'I campi :attribute e :other devono essere diversi.', 'digits' => ':attribute deve essere :digits cifre.', 'digits_between' => ':attribute deve essere :min e :max cifre.', 'email' => ':attribute deve essere un indirizzo email valido.', @@ -94,7 +94,7 @@ return [ '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.', + 'numeric_source' => 'L\'importo di origine 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/firefly.php b/resources/lang/nl_NL/firefly.php index 500f0f75a9..a9472dc631 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Nieuwe betaalrekening', 'new_expense_account' => 'Nieuwe crediteur', 'new_revenue_account' => 'Nieuwe debiteur', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Nieuw budget', 'new_bill' => 'Nieuw contract', 'block_account_logout' => 'Je bent helaas uitgelogd. Geblokkeerde accounts kunnen deze site niet gebruiken. Heb je een geldig e-mailadres gebruikt toen je je registreerde?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'De rekening die je hebt geselecteerd wordt al gebruikt in deze transactie, of bestaat niet.', 'source_or_dest_invalid' => 'Kan de juiste transactiegegevens niet vinden. Conversie is niet mogelijk.', + // create new stuff: 'create_new_withdrawal' => 'Nieuwe uitgave', 'create_new_deposit' => 'Nieuwe inkomsten', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Verwijder betaalrekening ":name"', 'delete_expense_account' => 'Verwijder crediteur ":name"', 'delete_revenue_account' => 'Verwijder debiteur ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Betaalrekening ":name" is verwijderd.', 'expense_deleted' => 'Crediteur ":name" is verwijderd.', 'revenue_deleted' => 'Debiteur ":name" is verwijderd.', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Nieuwe betaalrekening', 'make_new_expense_account' => 'Nieuwe crediteur', 'make_new_revenue_account' => 'Nieuwe debiteur', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Betaalrekeningen', 'expense_accounts' => 'Crediteuren', 'revenue_accounts' => 'Debiteuren', 'cash_accounts' => 'Contant geldrekeningen', 'Cash account' => 'Contant geldrekening', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Afstemmen betaalrekening ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Verwijder correctie', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Selecteer meer dan één categorie', 'select_more_than_one_budget' => 'Selecteer meer dan één budget', 'select_more_than_one_tag' => 'Selecteer meer dan één tag', - 'account_default_currency' => 'Nieuwe transacties van deze betaalrekening krijgen automatisch deze valuta.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Je Firefly III boekhouding bevat meer geld dan je bank beweert dat er in zou moeten zitten. Je hebt verschillende opties. Kies wat je wilt doen, en klik dan "Bevestig correctie".', 'reconcile_has_less' => 'Je Firefly III boekhouding bevat minder geld dan je bank beweert dat er in zou moeten zitten. Je hebt verschillende opties. Kies wat je wilt doen, en klik dan "Bevestig correctie".', 'reconcile_is_equal' => 'Je Firefly III boekhouding en je bankafschriften lopen gelijk. Je hoeft niets te doen. Klik op "Bevestig correctie" om je invoer te bevestigen.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Al afgestemde transacties (:count)', 'submitted_end_balance' => 'Ingevoerd eindsaldo', 'initial_balance_description' => 'Startsaldo voor ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Nieuwe categorie', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Welkom bij Firefly III!', 'submit' => 'Invoeren', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Aan de start!', 'to_get_started' => 'Het is goed om te zien dat de installatie van Firefly III gelukt is. Voer de naam van je bank in en het saldo van je belangrijkste betaalrekening. Meerdere rekeningen kan je later toevoegen, maar we moeten ergens beginnen natuurlijk.', 'savings_balance_text' => 'Firefly III maakt automatisch een spaarrekening voor je. Daar zit normaal geen geld in, maar je kan hier opgeven hoeveel er op staat.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Crediteur', 'Revenue account' => 'Debiteur', 'Initial balance account' => 'Startbalansrekening', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Budgetten', 'tags' => 'Tags', 'reports' => 'Overzichten', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Maandelijks', 'profile' => 'Profiel', 'errors' => 'Fouten', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Standaard financieel rapport (:start tot :end)', @@ -1132,6 +1149,10 @@ return [ 'deleted_link' => 'Koppeling verwijderd', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'gerelateerd aan', 'is (partially) refunded by_inward' => 'wordt (deels) terugbetaald door', 'is (partially) paid for by_inward' => 'wordt (deels) betaald door', @@ -1179,6 +1200,10 @@ return [ 'no_accounts_intro_revenue' => 'Je hebt nog geen debiteuren-rekeningen. Debiteuren zijn de plaatsen of mensen waar je geld van ontvangt, zoals je werkgever.', 'no_accounts_imperative_revenue' => 'Crediteuren worden automatisch aangemaakt als je transacties aanmaakt, maar je kan ze ook met de hand maken. Zoals nu:', 'no_accounts_create_revenue' => 'Maak een debiteur', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Je hebt een budget nodig', 'no_budgets_intro_default' => 'Je hebt nog geen budgetten. Budgetten gebruik je om je uitgaven te groeperen. Daarmee kan je je uitgaven beperken.', 'no_budgets_imperative_default' => 'Budgetten zijn de basis-gereedschappen van financieel beheer. Je kan nu een budget maken:', diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index 48f11b02b0..770452ffb9 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Verificatie', 'api_key' => 'API sleutel', 'remember_me' => 'Aangemeld blijven', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Bronrekening (betaalrekening)', 'destination_account_expense' => 'Doelrekening (crediteur)', diff --git a/resources/lang/nl_NL/import.php b/resources/lang/nl_NL/import.php index 42f2761d49..2b1a88d29a 100644 --- a/resources/lang/nl_NL/import.php +++ b/resources/lang/nl_NL/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Nep API-sleutel is opgeslagen!', 'prerequisites_saved_for_spectre' => 'APP ID en secret opgeslagen!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php index 2e258977ad..f3e9d5d5a2 100644 --- a/resources/lang/nl_NL/list.php +++ b/resources/lang/nl_NL/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Som van overschrijvingen', 'reconcile' => 'Afstemmen', 'account_on_spectre' => 'Rekening (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Importeer van deze rekening', 'sepa-ct-id' => 'SEPA end-to-end identificatie', 'sepa-ct-op' => 'SEPA identificatie tegenpartij', diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index d1b75de2a3..8dc082f9fc 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -50,7 +50,7 @@ return [ '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 :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute moet minder zijn dan 10.000.000', '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 d3fcf79ace..c90c7e4735 100644 --- a/resources/lang/pl_PL/config.php +++ b/resources/lang/pl_PL/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'pl', - 'locale' => 'pl, Polish, polski, pl_PL, pl_PL.utf8, pl_PL.UTF-8', + 'locale' => 'pl, Polish, polski, pl_PL.utf8, pl_PL.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', 'month_and_date_day' => '%A %B %e, %Y', diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 7c5687f66e..4f19f75c2d 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Nowe konto aktywów', 'new_expense_account' => 'Nowe konto wydatków', 'new_revenue_account' => 'Nowe konto przychodów', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Nowy budżet', 'new_bill' => 'Nowy rachunek', 'block_account_logout' => 'Zostałeś wylogowany. Zablokowane konta nie mogą korzystać z tej strony. Czy zarejestrowałeś się z prawidłowym adresem e-mail?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'Wybrane konto jest już używane w tej transakcji lub nie istnieje.', 'source_or_dest_invalid' => 'Nie można znaleźć poprawnych szczegółów transakcji. Konwersja nie jest możliwa.', + // create new stuff: 'create_new_withdrawal' => 'Utwórz nową wypłatę', 'create_new_deposit' => 'Utwórz nową wpłatę', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Usuń konto aktywów ":name"', 'delete_expense_account' => 'Usuń konto wydatków ":name"', 'delete_revenue_account' => 'Usuń konto przychodów ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Pomyślnie usunięto konto aktywów ":name"', 'expense_deleted' => 'Pomyślnie usunięto konto wydatków ":name"', 'revenue_deleted' => 'Pomyślnie usunięto konto przychodów ":name"', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Utwórz nowe konto aktywów', 'make_new_expense_account' => 'Utwórz nowe konto wydatków', 'make_new_revenue_account' => 'Utwórz nowe konto przychodów', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Konta aktywów', 'expense_accounts' => 'Konta wydatków', 'revenue_accounts' => 'Konta przychodów', 'cash_accounts' => 'Konta gotówkowe', 'Cash account' => 'Konto gotówkowe', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Uzgodnij konto ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Usuń uzgodnienie', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Proszę wybierz więcej niż jedną kategorię', 'select_more_than_one_budget' => 'Proszę wybierz więcej niż jeden budżet', 'select_more_than_one_tag' => 'Proszę wybierz więcej niż jeden tag', - 'account_default_currency' => 'Jeśli wybierzesz inną walutę, nowe transakcje z tego konta będą miały tę walutę wstępnie wybraną.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Twoja księga główna Firefly III ma więcej pieniędzy niż bank twierdzi, że powinieneś mieć. Istnieje kilka opcji. Wybierz, co zrobić. Następnie naciśnij "Potwierdź uzgodnienie".', 'reconcile_has_less' => 'Twoja księga główna Firefly III ma mniej pieniędzy niż bank twierdzi, że powinieneś mieć. Istnieje kilka opcji. Wybierz, co zrobić. Następnie naciśnij "Potwierdź uzgodnienie".', 'reconcile_is_equal' => 'Twoja księga główna Firefly III i wyciągi bankowe są zgodne. Nie ma nic do zrobienia. Naciśnij "Potwierdź uzgodnienie" aby potwierdzić twój wybór.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Transakcje uzgodnione wcześniej (:count)', 'submitted_end_balance' => 'Przesłane saldo końcowe', 'initial_balance_description' => 'Saldo początkowe dla ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Nowa kategoria', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Witaj w Firefly III!', 'submit' => 'Prześlij', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Pierwsze kroki', 'to_get_started' => 'Dobrze, że udało ci się zainstalować Firefly III. Aby rozpocząć korzystanie z tego narzędzia, wprowadź nazwę swojego banku i saldo głównego rachunku bieżącego. Nie martw się, jeśli masz wiele kont. Możesz dodać je później. Po prostu Firefly III potrzebuje czegoś na początek.', 'savings_balance_text' => 'Firefly III automatycznie utworzy dla ciebie konto oszczędnościowe. Domyślnie na twoim koncie oszczędnościowym nie ma pieniędzy, ale jeśli chcesz, Firefly III może je tam przechowywać.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Konto wydatków', 'Revenue account' => 'Konto przychodów', 'Initial balance account' => 'Początkowe saldo konta', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Budżety', 'tags' => 'Tagi', 'reports' => 'Raporty', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Miesięcznie', 'profile' => 'Profil', 'errors' => 'Błędy', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Domyślny raport finansowy między :start i :end', @@ -1132,6 +1149,10 @@ return [ 'deleted_link' => 'Usunięto powiązanie', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'odnosi się do', 'is (partially) refunded by_inward' => 'jest (częściowo) zwracane przez', 'is (partially) paid for by_inward' => 'jest (częściowo) opłacane przez', @@ -1179,6 +1200,10 @@ return [ 'no_accounts_intro_revenue' => 'Nie masz jeszcze żadnych kont przychodów. Konta przychodów to miejsca, z których dostajesz pieniądze, takie jak pracodawca.', 'no_accounts_imperative_revenue' => 'Konta przychodów są tworzone automatycznie podczas tworzenia transakcji, ale możesz również utworzyć je ręcznie, jeśli chcesz. Stwórzmy jedno teraz:', 'no_accounts_create_revenue' => 'Utwórz konto przychodów', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Stwórzmy budżet', 'no_budgets_intro_default' => 'Nie masz jeszcze żadnych budżetów. Budżety są wykorzystywane do organizowania twoich wydatków w logiczne grupy, które możesz obserwować, aby ograniczyć swoje wydatki.', 'no_budgets_imperative_default' => 'Budżety są podstawowymi narzędziami zarządzania finansami. Stwórzmy jeden teraz:', diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index 43aca5551b..2fbaabf6f8 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Weryfikacja', 'api_key' => 'Klucz API', 'remember_me' => 'Zapamiętaj mnie', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Konto źródłowe (konto aktywów)', 'destination_account_expense' => 'Konto docelowe (konto wydatków)', diff --git a/resources/lang/pl_PL/import.php b/resources/lang/pl_PL/import.php index f79f6959ef..6ff41e31d3 100644 --- a/resources/lang/pl_PL/import.php +++ b/resources/lang/pl_PL/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index 2411a3e125..2c761d4df3 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Suma transferów', 'reconcile' => 'Uzgodnij', 'account_on_spectre' => 'Konto (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Importuj z tego konta', 'sepa-ct-id' => 'SEPA End to End Identifier', 'sepa-ct-op' => 'SEPA Opposing Account Identifier', diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index a776c0faa0..4dd9007d9e 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -50,7 +50,7 @@ return [ 'at_least_one_action' => 'Reguła powinna mieć co najmniej jedną akcję.', 'base64' => 'To nie są prawidłowe dane zakodowane w base64.', 'model_id_invalid' => 'The given ID seems invalid for this model.', - 'more' => ':attribute must be larger than :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', '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/config.php b/resources/lang/pt_BR/config.php index f0401d93b3..e4e83c74e6 100644 --- a/resources/lang/pt_BR/config.php +++ b/resources/lang/pt_BR/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'pt-br', - 'locale' => 'pt-br, pt_BR, pt_BR.utf8, pt_BR.UTF-8', + 'locale' => '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', diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 96b0815825..cd245083c3 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Nova conta de ativo', 'new_expense_account' => 'Nova conta de despesa', 'new_revenue_account' => 'Nova conta de receita', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Novo orçamento', 'new_bill' => 'Nova fatura', 'block_account_logout' => 'Você foi desconectado. Contas bloqueadas não podem usar este site. Você se registrou com um email válido?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'A conta que você selecionou já é usada nesta transação ou não existe.', 'source_or_dest_invalid' => 'Os detalhes corretos da transação não foram encontrados. A conversão não é possível.', + // create new stuff: 'create_new_withdrawal' => 'Criar nova retirada', 'create_new_deposit' => 'Criar um novo depósito', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Excluir conta do activo ":name"', 'delete_expense_account' => 'Excluir conta de despesas ":name"', 'delete_revenue_account' => 'Excluir conta de receitas ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Conta de ativo ":name" excluído com sucesso', 'expense_deleted' => 'Conta de despesa ":name" excluída com sucesso', 'revenue_deleted' => 'Conta de receitas ":name" excluída com sucesso', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Criar uma nova conta de ativo', 'make_new_expense_account' => 'Criar uma nova conta de despesa', 'make_new_revenue_account' => 'Criar uma nova conta de receita', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Contas de ativo', 'expense_accounts' => 'Contas de despesas', 'revenue_accounts' => 'Contas de receitas', 'cash_accounts' => 'Contas Correntes', 'Cash account' => 'Conta Corrente', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Reconciliar conta ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Eliminar reconciliação', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Por favor, selecione mais de uma categoria', 'select_more_than_one_budget' => 'Por favor, selecione mais de um orçamento', 'select_more_than_one_tag' => 'Por favor, selecione mais de uma tag', - 'account_default_currency' => 'Se você selecionar outra moeda, as novas transações desta conta terão essa moeda pré-selecionada.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Seu registro do Firefly III tem mais dinheiro nele do que o seu banco afirma que você deveria ter. Existem várias opções. Escolha o que fazer. Em seguida, pressione "Confirmar reconciliação".', 'reconcile_has_less' => 'Seu registro do Firefly III tem menos dinheiro nele do que o seu banco afirma que você deveria ter. Existem várias opções. Escolha o que fazer. Em seguida, pressione "Confirmar reconciliação".', 'reconcile_is_equal' => 'Seu registro do Firefly III e seus registros bancários combinam. Não há nada a se fazer. Pressione "Confirmar reconciliação" para confirmar sua entrada.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Transações já removidas (:count)', 'submitted_end_balance' => 'Saldo final enviado', 'initial_balance_description' => 'Saldo inicial para ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Nova categoria', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Bem Vindo ao Firefly III!', 'submit' => 'Enviar', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Iniciar', 'to_get_started' => 'É bom ver que você instalou o Firefly III com sucesso. Para começar com esta ferramenta, insira o nome do banco e o saldo da sua principal conta corrente. Não se preocupe ainda se você tiver várias contas. Você pode adicionar aqueles mais tarde. É só que o Firefly III precisa de algo para começar.', 'savings_balance_text' => 'O Firefly III criará automaticamente uma conta de poupança para você. Por padrão, não haverá dinheiro na sua conta de poupança, mas se você contar o saldo ao Firefly III, ele será armazenado como tal.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Conta de Despesa', 'Revenue account' => 'Conta de Receita', 'Initial balance account' => 'Saldo inicial da conta', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Orçamentos', 'tags' => 'Tags', 'reports' => 'Relatórios', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Mensal', 'profile' => 'Perfil', 'errors' => 'Erros', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Relatório financeiro padrão entre :start e :end', @@ -1132,6 +1149,10 @@ return [ 'deleted_link' => 'Excluir ligação', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'relacionado a', 'is (partially) refunded by_inward' => 'é (parcialmente) devolvido por', 'is (partially) paid for by_inward' => 'é (parcialmente) pago por', @@ -1179,6 +1200,10 @@ return [ 'no_accounts_intro_revenue' => 'Você ainda não possui contas de receita. As contas de receita são os locais onde você recebe dinheiro, como o seu empregador.', 'no_accounts_imperative_revenue' => 'As contas de receita são criadas automaticamente quando você cria transações, mas você também pode criar uma manualmente, se desejar. Vamos criar um agora:', 'no_accounts_create_revenue' => 'Criar uma conta de receita', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Vamos criar um orçamento', 'no_budgets_intro_default' => 'Você ainda não tem orçamentos. Os orçamentos são usados ​​para organizar suas despesas em grupos lógicos, o que você pode usar como uma base para limitar suas despesas.', 'no_budgets_imperative_default' => 'Os orçamentos são as ferramentas básicas de gestão financeira. Vamos criar um agora:', diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index b3ed2eb0f0..11c7165dce 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Verificação', 'api_key' => 'Chave da API', 'remember_me' => 'Lembrar-me', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Conta de origem (conta de ativo)', 'destination_account_expense' => 'Conta de destino (conta de despesa)', diff --git a/resources/lang/pt_BR/import.php b/resources/lang/pt_BR/import.php index 963475b00b..d07233482e 100644 --- a/resources/lang/pt_BR/import.php +++ b/resources/lang/pt_BR/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php index 96df1164db..7d2697b97e 100644 --- a/resources/lang/pt_BR/list.php +++ b/resources/lang/pt_BR/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Soma das transferências', 'reconcile' => 'Pago', 'account_on_spectre' => 'Conta (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Importar desta conta', 'sepa-ct-id' => 'SEPA Identificador end-to-end', 'sepa-ct-op' => 'SEPA Identificador de conta de contrária', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 9d498a54db..2879383619 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -50,7 +50,7 @@ return [ '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 :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', '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 58b0bfdd02..d71c4aa20b 100644 --- a/resources/lang/ru_RU/config.php +++ b/resources/lang/ru_RU/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'ru', - 'locale' => 'ru, Russian, ru_RU, ru_RU.utf8, ru_RU.UTF-8', + 'locale' => 'ru, Russian, ru_RU.utf8, ru_RU.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', 'month_and_date_day' => '%A, %B %e %Y', diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index f5292f8f7d..6d2b76aad6 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -60,6 +60,7 @@ return [ 'new_asset_account' => 'Новый счет активов', 'new_expense_account' => 'Новый расходный счет', 'new_revenue_account' => 'Новый доходный счет', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Новый бюджет', 'new_bill' => 'Новый счёт к оплате', 'block_account_logout' => 'Вы вышли из системы. Заблокированные учётные записи не могут использовать этот сайт. Вы зарегистрировались с действующим адресом электронной почты?', @@ -589,6 +590,7 @@ return [ 'invalid_convert_selection' => 'Выбранный вами счёт уже используется в этой транзакции или не существует.', 'source_or_dest_invalid' => 'Не удается найти правильные сведения о транзакции. Преобразование невозможно.', + // create new stuff: 'create_new_withdrawal' => 'Создать новый расход', 'create_new_deposit' => 'Создать новый доход', @@ -686,6 +688,7 @@ return [ 'delete_asset_account' => 'Удалить основной счёт ":name"', 'delete_expense_account' => 'Удалить счёт расходов ":name"', 'delete_revenue_account' => 'Удалить счёт доходов ":name"', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => 'Основной счёт ":name" успешно удалён', 'expense_deleted' => 'Счёт расхода ":name" успешно удалён', 'revenue_deleted' => 'Счёт дохода ":name" успешно удалён', @@ -695,11 +698,13 @@ return [ 'make_new_asset_account' => 'Создать новый основной счёт', 'make_new_expense_account' => 'Создать новый счёт расхода', 'make_new_revenue_account' => 'Создать новый счёт дохода', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Основные счета', 'expense_accounts' => 'Счета расходов', 'revenue_accounts' => 'Счета доходов', 'cash_accounts' => 'Наличные деньги', 'Cash account' => 'Наличные деньги', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Сверка счёта ":account"', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Удалить сверку', @@ -733,7 +738,7 @@ return [ 'select_more_than_one_category' => 'Пожалуйста, выберите больше одной категории', 'select_more_than_one_budget' => 'Пожалуйста, выберите больше одного бюджета', 'select_more_than_one_tag' => 'Пожалуйста, выберите больше одной метки', - 'account_default_currency' => 'Если вы выберете другую валюту, новые транзакции по данному счёту будут по умолчанию использовать именно её.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'В вашей бухгалтерской книге Firefly III учтено больше денег, чем должно быть, согласно выписке из банка. Существует несколько вариантов. Пожалуйста, выберите, что делать, а затем нажмите «Подтвердить сверку».', 'reconcile_has_less' => 'В вашей бухгалтерской книге Firefly III учтено меньше денег, чем должно быть, согласно выписке из банка. Существует несколько вариантов. Пожалуйста, выберите, что делать, а затем нажмите «Подтвердить сверку».', 'reconcile_is_equal' => 'Ваша бухгалтерская книга Firefly III и ваши банковские выписки совпадают. Ничего делать не нужно. Пожалуйста, нажмите «Подтвердить сверку» для ввода данных.', @@ -751,6 +756,9 @@ return [ 'already_cleared_transactions' => 'Уже удалённые транзакции (:count)', 'submitted_end_balance' => 'Подтверждённый конечный баланс', 'initial_balance_description' => 'Начальный баланс для ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Новая категория', @@ -814,6 +822,7 @@ return [ // new user: 'welcome' => 'Добро пожаловать в Firefly III!', 'submit' => 'Подтвердить', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Начало работы', 'to_get_started' => 'Приятно видеть, что вы успешно установили Firefly III. Чтобы начать работу, введите, пожалуйста, название своего банка и баланс вашего основного банковского счёта. Если вы планируете использовать несколько счетов, не волнуйтесь, вы сможете добавить их позже. Сейчас Firefly III просто нужны какие-нибудь первоначальные данные.', 'savings_balance_text' => 'Firefly III автоматически создаст сберегательный счёт для вас. По умолчанию на вашем сберегательном счёте не будет денег, но если вы укажете начальный баланс, он будет сохранен.', @@ -852,6 +861,10 @@ return [ 'Expense account' => 'Счета расходов', 'Revenue account' => 'Счета доходов', 'Initial balance account' => 'Начальный баланс для счёта', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Бюджет', 'tags' => 'Метки', 'reports' => 'Отчёты', @@ -881,6 +894,10 @@ return [ 'monthly' => 'Ежемесячно', 'profile' => 'Профиль', 'errors' => 'Ошибки', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => 'Стандартный финансовый отчёт за период с :start по :end', @@ -1132,6 +1149,10 @@ return [ 'deleted_link' => 'Связь удалена', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'связано с', 'is (partially) refunded by_inward' => '(частично) возвращён', 'is (partially) paid for by_inward' => '(частично) оплачен', @@ -1179,6 +1200,10 @@ return [ 'no_accounts_intro_revenue' => 'У вас ещё нет счетов дохода. Счета дохода - это источники вашего дохода (например, ваш работодатель).', 'no_accounts_imperative_revenue' => 'Счета дохода создаются автоматически при создании транзакций, но вы можете создать их вручную, если хотите. Давайте создадим один сейчас:', 'no_accounts_create_revenue' => 'Создать счёт дохода', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Давайте создадим бюджет', 'no_budgets_intro_default' => 'У вас пока нет бюджетов. Бюджеты используются для упорядочивания ваших расходов в логические группы, с помощью наблюдения за которыми вы можете ограничить свои расходы.', 'no_budgets_imperative_default' => 'Бюджеты - это основные инструменты управления финансами. Давайте создадим один сейчас:', diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index ce357ba026..237636c4e0 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Проверка', 'api_key' => 'API-ключ', 'remember_me' => 'Запомнить меня', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Исходный счёт (основной счёт)', 'destination_account_expense' => 'Счёт назначения (счёт расхода)', diff --git a/resources/lang/ru_RU/import.php b/resources/lang/ru_RU/import.php index 0b20a2fad6..a0ce54152e 100644 --- a/resources/lang/ru_RU/import.php +++ b/resources/lang/ru_RU/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Ключ Fake API успешно сохранен!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index a9d4e44c18..655efe5eab 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -104,6 +104,7 @@ return [ 'sum_transfers' => 'Сумма переводов', 'reconcile' => 'Сверка', 'account_on_spectre' => 'Счёт (Spectre)', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Импортировать с этого счёта', 'sepa-ct-id' => 'Идентификатор SEPA end-to-end', 'sepa-ct-op' => 'Идентификатор учетной записи SEPA', diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index 88fce4d5ff..edc9787d26 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -50,7 +50,7 @@ return [ '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 :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', 'active_url' => ':attribute не является допустимым URL-адресом.', 'after' => ':attribute должна быть позже :date.', diff --git a/resources/lang/tr_TR/config.php b/resources/lang/tr_TR/config.php index 8e98d16bf4..f0971dbfd6 100644 --- a/resources/lang/tr_TR/config.php +++ b/resources/lang/tr_TR/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'tr', - 'locale' => 'tr, Turkish, tr_TR, tr_TR.utf8, tr_TR.S.UTF-8', + 'locale' => 'en, English, en_US.utf8, en_US.UTF-8', 'month' => '%B %Y', 'month_and_day' => '%e %B %Y', 'month_and_date_day' => '%A %B %e, %Y', diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index 6e26744d38..0af1a11131 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -61,6 +61,7 @@ return [ 'new_asset_account' => 'Yeni varlık hesabı', 'new_expense_account' => 'Yeni gider hesabı', 'new_revenue_account' => 'Yeni Gelir Hesabı', + 'new_liabilities_account' => 'New liability', 'new_budget' => 'Yeni bütçe', 'new_bill' => 'Yeni Fatura', 'block_account_logout' => 'Çıkış yaptınız. Engellenen hesaplar bu siteyi kullanamaz. Geçerli bir e-posta adresiyle kayıt oldunuz mu?', @@ -592,6 +593,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'invalid_convert_selection' => 'Seçtiğiniz hesap zaten bu işlemde kullanılmış veya mevcut değil.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + // create new stuff: 'create_new_withdrawal' => 'Yeni çekim oluştur', 'create_new_deposit' => 'Yeni mevduat oluştur', @@ -689,6 +691,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'delete_asset_account' => '":name" Öğe hesabını sil', 'delete_expense_account' => '":name" Masraf hesaplarını sil', 'delete_revenue_account' => '":name" Gelir hesabını sil', + 'delete_liabilities_account' => 'Delete liability ":name"', 'asset_deleted' => '":name" Adlı varlık hesabı başarıyla silindi', 'expense_deleted' => '":name" gider hesabı başarıyla silindi', 'revenue_deleted' => '":name" gelir hesabı başarıyla silindi', @@ -698,11 +701,13 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'make_new_asset_account' => 'Yeni varlık hesabı oluştur', 'make_new_expense_account' => 'Yeni gider hesabı oluştur', 'make_new_revenue_account' => 'Yeni gelir hesabı oluştur', + 'make_new_liabilities_account' => 'Create a new liability', 'asset_accounts' => 'Varlık hesapları', 'expense_accounts' => 'Gider hesapları', 'revenue_accounts' => 'Gelir hesapları', 'cash_accounts' => 'Nakit Hesabı', 'Cash account' => 'Nakit Hesabı', + 'liabilities_accounts' => 'Liabilities', 'reconcile_account' => 'Hesabı ":account" dengeleyin', 'overview_of_reconcile_modal' => 'Overview of reconciliation', 'delete_reconciliation' => 'Mutabakatı sil', @@ -736,7 +741,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'select_more_than_one_category' => 'Lütfen birden fazla kategori seçin', 'select_more_than_one_budget' => 'Lütfen birden fazla bütçe seçin', 'select_more_than_one_tag' => 'Lütfen birden fazla etiket seçin', - 'account_default_currency' => 'Eğer başka bir para birimi seçerseniz, bu hesaptan yapılacak yeni işlemler için o para birimi kullanılacaktır.', + 'account_default_currency' => 'This will be the default currency associated with this account.', 'reconcile_has_more' => 'Firefly III hesabınızda bankanızın iddia ettiğinden daha fazla para var. Birkaç seçenek var. Lütfen ne yapılacağını seçin. Daha sonra "Uzlaşmayı onayla" ya basın.', 'reconcile_has_less' => 'Firefly III\'de bankanızın iddia ettiğinden daha az para var. Birkaç seçenek var. Lütfen ne yapılacağını seçin. Daha sonra "Uzlaşmayı onayla"\'ya basın.', 'reconcile_is_equal' => 'Firefly III hesabınızdaki para ile banka ekstreniz eşleşiyor. Yapılacak bir şey yok. Lütfen girdinizi onaylamak için "Uzlaşmayı onayla"\'ya basın.', @@ -754,6 +759,9 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'already_cleared_transactions' => 'Halihazırda temizlenmiş işlemler (:count)', 'submitted_end_balance' => 'Gönderilen bitiş bakiyesi', 'initial_balance_description' => 'Initial balance for ":account"', + 'interest_calc_daily' => 'Per day', + 'interest_calc_monthly' => 'Per month', + 'interest_calc_yearly' => 'Per year', // categories: 'new_category' => 'Yeni Kategori', @@ -817,6 +825,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', // new user: 'welcome' => 'Firefly III\'e hoşgeldiniz!', 'submit' => 'Gönder', + 'submit_yes_really' => 'Submit (I know what I\'m doing)', 'getting_started' => 'Başla', 'to_get_started' => 'Firefly III\'ü başarılı şekilde yüklediğinizi görmek güzel. Bu aracı kullanmak için lütfen banka adınızı ve ana hesabınızın bakiyesini girin. Birden fazla hesabınız varsa endişelenmeyin. Onları daha sonra ekleyebilirsiniz. Bu adım sadece Firefly III\'ün bir yerden başlaması gerektiği içindir.', 'savings_balance_text' => 'Firefly III sizin için otomatik olarak bir birikim hesabı oluşturacaktır. Varsayılan olarak birikim hesabınızda hiç para olmayacaktır ama Firefly III\'e dengelemesini söylerseniz o şekilde saklayacaktır.', @@ -855,6 +864,10 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'Expense account' => 'Gider hesabı', 'Revenue account' => 'Gelir hesabı', 'Initial balance account' => 'Başlangıç bakiye hesabı', + 'account_type_Debt' => 'Debt', + 'account_type_Loan' => 'Loan', + 'account_type_Mortgage' => 'Mortgage', + 'account_type_Credit card' => 'Credit card', 'budgets' => 'Bütçeler', 'tags' => 'Etiketler', 'reports' => 'Raporlar', @@ -884,6 +897,10 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'monthly' => 'Aylık', 'profile' => 'Profil', 'errors' => 'Hatalar', + 'debt_start_date' => 'Start date of debt', + 'debt_start_amount' => 'Start amount of debt', + 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', + 'store_new_liabilities_account' => 'Store new liability', // reports: 'report_default' => ':start ve :end arasında varsayılan finans raporu', @@ -1135,6 +1152,10 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'deleted_link' => 'Bağlantı silindi', // link translations: + 'Paid_name' => 'Paid', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Related', 'relates to_inward' => 'ile ilişkili', 'is (partially) refunded by_inward' => 'tarafından (kısmen) geri ödendi', 'is (partially) paid for by_inward' => 'tarafından (kısmen) ödendi', @@ -1182,6 +1203,10 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'no_accounts_intro_revenue' => 'Henüz gelir hesaplarınız yok. Gelir hesapları para almanızı sağlayan yerdir, tıpkı işvereniniz gibi.', 'no_accounts_imperative_revenue' => 'Gelir hesapları, işlemler oluşturduğunuzda otomatikmen oluşturulur, ama isterseniz manuel olarak da bir tane oluşturabilirsiniz. Hadi şimdi bir tane oluşturalım:', 'no_accounts_create_revenue' => 'Bir gelir hesabı oluşturun', + 'no_accounts_title_liabilities' => 'Let\'s create a liability!', + 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your credit card(s), (student) loans and other debts.', + 'no_accounts_imperative_liabilities' => 'You don\'t have to use this feature, but it can be useful if you want to keep track of these things.', + 'no_accounts_create_liabilities' => 'Create a liability', 'no_budgets_title_default' => 'Bütçe oluşturalım', 'no_budgets_intro_default' => 'Henüz bütçeniz yok. Bütçeler, giderlerinizi mantıklı guruplara organize etmenizi sağlar, bunlara giderlerinizi sınırlamak için esnek bir sınır verebilmeniz için kullanılır.', 'no_budgets_imperative_default' => 'Bütçeler, finansal yönetimin temel araçlarıdır. Şimdi bir tane oluşturalım:', diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index 8cf94ad9c0..41ba500819 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -84,6 +84,9 @@ return [ 'verification' => 'Doğrulama', 'api_key' => 'API anahtarı', 'remember_me' => 'Remember me', + 'liability_type_id' => 'Liability type', + 'interest' => 'Interest', + 'interest_period' => 'Interest period', 'source_account_asset' => 'Kaynak Hesabı (varlık hesabı)', 'destination_account_expense' => 'Hedef Hesap (gider hesabı)', diff --git a/resources/lang/tr_TR/import.php b/resources/lang/tr_TR/import.php index 7ab8cc4a96..5c76b100c8 100644 --- a/resources/lang/tr_TR/import.php +++ b/resources/lang/tr_TR/import.php @@ -84,6 +84,7 @@ return [ 'prereq_ynab_title' => 'Prerequisites for an import from YNAB', 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', // prerequisites success messages: 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', @@ -171,6 +172,9 @@ return [ 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Select accounts', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', diff --git a/resources/lang/tr_TR/list.php b/resources/lang/tr_TR/list.php index b30665dc0e..4e870520c2 100644 --- a/resources/lang/tr_TR/list.php +++ b/resources/lang/tr_TR/list.php @@ -105,6 +105,7 @@ return [ 'sum_transfers' => 'Transferlerin toplamı', 'reconcile' => 'Onaylanmış', 'account_on_spectre' => '(Spectre) Hesabı', + 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Bu hesaptan içeri aktar', 'sepa-ct-id' => 'SEPA End to End Identifier', 'sepa-ct-op' => 'SEPA Opposing Account Identifier', diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index 37d900f4a7..dcb44a8834 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -50,7 +50,7 @@ return [ '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 must be larger than :value.', + 'more' => ':attribute must be larger than zero.', 'less' => ':attribute must be less than 10,000,000', 'active_url' => ':attribute geçerli bir URL değil.', 'after' => ':attribute :date tarihinden sonrası için tarihlendirilmelidir.', From 422e80530b1232fa0c12d05c76d5599e495d228f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 5 Aug 2018 15:34:20 +0200 Subject: [PATCH 032/166] Refactor rule creation. --- .../Controllers/Rule/CreateController.php | 51 ++--- .../Controllers/Rule/SelectController.php | 20 +- app/Http/Requests/RuleFormRequest.php | 100 +++++---- app/Repositories/Rule/RuleRepository.php | 40 ++-- .../Http/Controllers/RuleManagement.php | 86 ++++---- .../Factory/TriggerFactory.php | 2 +- app/TransactionRules/Processor.php | 4 +- app/Validation/FireflyValidator.php | 193 +++++++----------- public/js/ff/rules/create-edit.js | 139 +++++++++---- resources/views/rules/partials/action.twig | 10 +- resources/views/rules/partials/trigger.twig | 11 +- routes/breadcrumbs.php | 18 +- 12 files changed, 337 insertions(+), 337 deletions(-) diff --git a/app/Http/Controllers/Rule/CreateController.php b/app/Http/Controllers/Rule/CreateController.php index e35b243640..622440301d 100644 --- a/app/Http/Controllers/Rule/CreateController.php +++ b/app/Http/Controllers/Rule/CreateController.php @@ -30,7 +30,6 @@ use FireflyIII\Models\Bill; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; - use FireflyIII\Support\Http\Controllers\RuleManagement; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -71,6 +70,8 @@ class CreateController extends Controller /** * Create a new rule. It will be stored under the given $ruleGroup. * + * TODO reinstate bill specific code. + * * @param Request $request * @param RuleGroup $ruleGroup * @@ -78,51 +79,33 @@ class CreateController extends Controller * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function create(Request $request, RuleGroup $ruleGroup) + public function create(Request $request, RuleGroup $ruleGroup = null) { $this->createDefaultRuleGroup(); $this->createDefaultRule(); - $bill = null; - $billId = (int)$request->get('fromBill'); - $preFilled = [ + $preFilled = [ 'strict' => true, ]; - $oldTriggers = []; - $oldActions = []; - $returnToBill = false; + $oldTriggers = []; + $oldActions = []; - if ('true' === $request->get('return')) { - $returnToBill = true; - } - - // has bill? - if ($billId > 0) { - $bill = $this->billRepos->find($billId); - } - - // has old input? + // restore actions and triggers from old input: if ($request->old()) { $oldTriggers = $this->getPreviousTriggers($request); $oldActions = $this->getPreviousActions($request); } - // has existing bill refered to in URI? - if (null !== $bill && !$request->old()) { - - // create some sensible defaults: - $preFilled['title'] = (string)trans('firefly.new_rule_for_bill_title', ['name' => $bill->name]); - $preFilled['description'] = (string)trans('firefly.new_rule_for_bill_description', ['name' => $bill->name]); - - - // get triggers and actions for bill: - $oldTriggers = $this->getTriggersForBill($bill); - $oldActions = $this->getActionsForBill($bill); - } $triggerCount = \count($oldTriggers); $actionCount = \count($oldActions); $subTitleIcon = 'fa-clone'; - $subTitle = (string)trans('firefly.make_new_rule', ['title' => $ruleGroup->title]); + // title depends on whether or not there is a rule group: + $subTitle = (string)trans('firefly.make_new_rule_no_group'); + if (null !== $ruleGroup) { + $subTitle = (string)trans('firefly.make_new_rule', ['title' => $ruleGroup->title]); + } + + // flash old data $request->session()->flash('preFilled', $preFilled); // put previous url in session if not redirect from store (not "create another"). @@ -132,11 +115,7 @@ class CreateController extends Controller session()->forget('rules.create.fromStore'); return view( - 'rules.rule.create', - compact( - 'subTitleIcon', 'oldTriggers', 'returnToBill', 'preFilled', 'bill', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroup', - 'subTitle' - ) + 'rules.rule.create', compact('subTitleIcon', 'oldTriggers', 'preFilled', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroup', 'subTitle') ); } diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index b8c30229f2..fbc3afc763 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -124,7 +124,6 @@ class SelectController extends Controller return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle')); } - /** * This method allows the user to test a certain set of rule triggers. The rule triggers are passed along * using the URL parameters (GET), and are usually put there using a Javascript thing. @@ -267,18 +266,13 @@ class SelectController extends Controller private function getValidTriggerList(TestRuleFormRequest $request): array { $triggers = []; - $data = [ - 'rule-triggers' => $request->get('rule-trigger'), - 'rule-trigger-values' => $request->get('rule-trigger-value'), - 'rule-trigger-stop' => $request->get('rule-trigger-stop'), - ]; - if (\is_array($data['rule-triggers'])) { - foreach ($data['rule-triggers'] as $index => $triggerType) { - $data['rule-trigger-stop'][$index] = (int)($data['rule-trigger-stop'][$index] ?? 0.0); - $triggers[] = [ - 'type' => $triggerType, - 'value' => $data['rule-trigger-values'][$index], - 'stopProcessing' => 1 === (int)$data['rule-trigger-stop'][$index], + $data = $request->get('rule_triggers'); + if (\is_array($data)) { + foreach ($data as $index => $triggerInfo) { + $triggers[] = [ + 'type' => $triggerInfo['name'] ?? '', + 'value' => $triggerInfo['value'] ?? '', + 'stop_processing' => 1 === (int)($triggerInfo['stop_processing'] ?? '0'), ]; } } diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 84a6e3fe30..de1e104185 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -50,45 +50,17 @@ class RuleFormRequest extends Request */ public function getRuleData(): array { - $data = [ + $data = [ 'title' => $this->string('title'), 'rule_group_id' => $this->integer('rule_group_id'), 'active' => $this->boolean('active'), 'trigger' => $this->string('trigger'), 'description' => $this->string('description'), - 'stop-processing' => $this->boolean('stop_processing'), + 'stop_processing' => $this->boolean('stop_processing'), 'strict' => $this->boolean('strict'), - 'rule-triggers' => [], - 'rule-actions' => [], + 'rule_triggers' => $this->getRuleTriggerData(), + 'rule_actions' => $this->getRuleActionData(), ]; - $triggers = $this->get('rule-trigger'); - $triggerValues = $this->get('rule-trigger-value'); - $triggerStop = $this->get('rule-trigger-stop'); - - $actions = $this->get('rule-action'); - $actionValues = $this->get('rule-action-value'); - $actionStop = $this->get('rule-action-stop'); - - if (\is_array($triggers)) { - foreach ($triggers as $index => $value) { - $data['rule-triggers'][] = [ - 'name' => $value, - 'value' => $triggerValues[$index] ?? '', - 'stop-processing' => 1 === (int)($triggerStop[$index] ?? 0), - ]; - } - } - - if (\is_array($actions)) { - foreach ($actions as $index => $value) { - $data['rule-actions'][] = [ - 'name' => $value, - 'value' => $actionValues[$index] ?? '', - 'stop-processing' => 1 === (int)($actionStop[$index] ?? 0), - ]; - } - } - return $data; } @@ -112,21 +84,65 @@ class RuleFormRequest extends Request $titleRule = 'required|between:1,100|uniqueObjectForUser:rules,title,' . (int)$this->get('id'); } $rules = [ - 'title' => $titleRule, - 'description' => 'between:1,5000|nullable', - 'stop_processing' => 'boolean', - 'rule_group_id' => 'required|belongsToUser:rule_groups', - 'trigger' => 'required|in:store-journal,update-journal', - 'rule-trigger.*' => 'required|in:' . implode(',', $validTriggers), - 'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue', - 'rule-action.*' => 'required|in:' . implode(',', $validActions), - 'strict' => 'in:0,1', + 'title' => $titleRule, + 'description' => 'between:1,5000|nullable', + 'stop_processing' => 'boolean', + 'rule_group_id' => 'required|belongsToUser:rule_groups', + 'trigger' => 'required|in:store-journal,update-journal', + 'rule_triggers.*.name' => 'required|in:' . implode(',', $validTriggers), + 'rule_triggers.*.value' => 'required|min:1|ruleTriggerValue', + 'rule-actions.*.name' => 'required|in:' . implode(',', $validActions), + 'strict' => 'in:0,1', ]; // since Laravel does not support this stuff yet, here's a trick. for ($i = 0; $i < 10; ++$i) { - $rules['rule-action-value.' . $i] = 'required_if:rule-action.' . $i . ',' . $contextActions . '|ruleActionValue'; + $key = sprintf('rule_actions.%d.value', $i); + $rule = sprintf('required-if:rule_actions.%d.name,%s|ruleActionValue', $i, $contextActions); + $rules[$key] = $rule; } return $rules; } + + /** + * @return array + */ + private function getRuleActionData(): array + { + $return = []; + $actionData= $this->get('rule_actions'); + if (\is_array($actionData)) { + foreach ($actionData as $action) { + $stopProcessing = $action['stop_processing'] ?? '0'; + $return[] = [ + 'name' => $action['name'] ?? 'invalid', + 'value' => $action['value'] ?? '', + 'stop_processing' => 1 === (int)$stopProcessing, + ]; + } + } + + return $return; + } + + /** + * @return array + */ + private function getRuleTriggerData(): array + { + $return = []; + $triggerData = $this->get('rule_triggers'); + if (\is_array($triggerData)) { + foreach ($triggerData as $trigger) { + $stopProcessing = $trigger['stop_processing'] ?? '0'; + $return[] = [ + 'name' => $trigger['name'] ?? 'invalid', + 'value' => $trigger['value'] ?? '', + 'stop_processing' => 1 === (int)$stopProcessing, + ]; + } + } + + return $return; + } } diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index a0b4ae4b3b..a17f1ff9c9 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -292,7 +292,7 @@ class RuleRepository implements RuleRepositoryInterface $rule->order = ($order + 1); $rule->active = true; $rule->strict = $data['strict'] ?? false; - $rule->stop_processing = 1 === (int)$data['stop-processing']; + $rule->stop_processing = 1 === (int)$data['stop_processing']; $rule->title = $data['title']; $rule->description = \strlen($data['description']) > 0 ? $data['description'] : null; @@ -319,7 +319,7 @@ class RuleRepository implements RuleRepositoryInterface $ruleAction->rule()->associate($rule); $ruleAction->order = $values['order']; $ruleAction->active = true; - $ruleAction->stop_processing = $values['stopProcessing']; + $ruleAction->stop_processing = $values['stop_processing']; $ruleAction->action_type = $values['action']; $ruleAction->action_value = $values['value'] ?? ''; $ruleAction->save(); @@ -339,7 +339,7 @@ class RuleRepository implements RuleRepositoryInterface $ruleTrigger->rule()->associate($rule); $ruleTrigger->order = $values['order']; $ruleTrigger->active = true; - $ruleTrigger->stop_processing = $values['stopProcessing']; + $ruleTrigger->stop_processing = $values['stop_processing']; $ruleTrigger->trigger_type = $values['action']; $ruleTrigger->trigger_value = $values['value'] ?? ''; $ruleTrigger->save(); @@ -358,7 +358,7 @@ class RuleRepository implements RuleRepositoryInterface // update rule: $rule->rule_group_id = $data['rule_group_id']; $rule->active = $data['active']; - $rule->stop_processing = $data['stop-processing']; + $rule->stop_processing = $data['stop_processing']; $rule->title = $data['title']; $rule->strict = $data['strict'] ?? false; $rule->description = $data['description']; @@ -388,15 +388,15 @@ class RuleRepository implements RuleRepositoryInterface private function storeActions(Rule $rule, array $data): bool { $order = 1; - foreach ($data['rule-actions'] as $action) { + foreach ($data['rule_actions'] as $action) { $value = $action['value'] ?? ''; - $stopProcessing = $action['stop-processing'] ?? false; + $stopProcessing = $action['stop_processing'] ?? false; $actionValues = [ - 'action' => $action['name'], - 'value' => $value, - 'stopProcessing' => $stopProcessing, - 'order' => $order, + 'action' => $action['name'], + 'value' => $value, + 'stop_processing' => $stopProcessing, + 'order' => $order, ]; $this->storeAction($rule, $actionValues); @@ -417,22 +417,22 @@ class RuleRepository implements RuleRepositoryInterface $stopProcessing = false; $triggerValues = [ - 'action' => 'user_action', - 'value' => $data['trigger'], - 'stopProcessing' => $stopProcessing, - 'order' => $order, + 'action' => 'user_action', + 'value' => $data['trigger'], + 'stop_processing' => $stopProcessing, + 'order' => $order, ]; $this->storeTrigger($rule, $triggerValues); - foreach ($data['rule-triggers'] as $trigger) { + foreach ($data['rule_triggers'] as $trigger) { $value = $trigger['value'] ?? ''; - $stopProcessing = $trigger['stop-processing'] ?? false; + $stopProcessing = $trigger['stop_processing'] ?? false; $triggerValues = [ - 'action' => $trigger['name'], - 'value' => $value, - 'stopProcessing' => $stopProcessing, - 'order' => $order, + 'action' => $trigger['name'], + 'value' => $value, + 'stop_processing' => $stopProcessing, + 'order' => $order, ]; $this->storeTrigger($rule, $triggerValues); diff --git a/app/Support/Http/Controllers/RuleManagement.php b/app/Support/Http/Controllers/RuleManagement.php index 7033ae5e25..60d9b0bcb9 100644 --- a/app/Support/Http/Controllers/RuleManagement.php +++ b/app/Support/Http/Controllers/RuleManagement.php @@ -93,33 +93,30 @@ trait RuleManagement */ protected function getPreviousActions(Request $request): array { - $newIndex = 0; - $actions = []; - /** @var array $oldActions */ - $oldActions = \is_array($request->old('rule-action')) ? $request->old('rule-action') : []; - foreach ($oldActions as $index => $entry) { - $count = ($newIndex + 1); - $checked = isset($request->old('rule-action-stop')[$index]) ? true : false; - try { - $actions[] = view( - 'rules.partials.action', - [ - 'oldAction' => $entry, - 'oldValue' => $request->old('rule-action-value')[$index], - 'oldChecked' => $checked, - 'count' => $count, - ] - )->render(); - // @codeCoverageIgnoreStart - } catch (Throwable $e) { - Log::debug(sprintf('Throwable was thrown in getPreviousActions(): %s', $e->getMessage())); - Log::error($e->getTraceAsString()); + $index = 0; + $triggers = []; + $oldInput = $request->old('rule_actions'); + if (\is_array($oldInput)) { + foreach ($oldInput as $oldAction) { + try { + $triggers[] = view( + 'rules.partials.action', + [ + 'oldAction' => $oldAction['name'], + 'oldValue' => $oldAction['value'], + 'oldChecked' => 1 === (int)($oldAction['stop_processing'] ?? '0'), + 'count' => $index + 1, + ] + )->render(); + } catch (Throwable $e) { + Log::debug(sprintf('Throwable was thrown in getPreviousActions(): %s', $e->getMessage())); + Log::error($e->getTraceAsString()); + } + $index++; } - // @codeCoverageIgnoreEnd - ++$newIndex; } - return $actions; + return $triggers; } /** @@ -129,30 +126,27 @@ trait RuleManagement */ protected function getPreviousTriggers(Request $request): array { - $newIndex = 0; + $index = 0; $triggers = []; - /** @var array $oldTriggers */ - $oldTriggers = \is_array($request->old('rule-trigger')) ? $request->old('rule-trigger') : []; - foreach ($oldTriggers as $index => $entry) { - $count = ($newIndex + 1); - $oldChecked = isset($request->old('rule-trigger-stop')[$index]) ? true : false; - try { - $triggers[] = view( - 'rules.partials.trigger', - [ - 'oldTrigger' => $entry, - 'oldValue' => $request->old('rule-trigger-value')[$index], - 'oldChecked' => $oldChecked, - 'count' => $count, - ] - )->render(); - // @codeCoverageIgnoreStart - } catch (Throwable $e) { - Log::debug(sprintf('Throwable was thrown in getPreviousTriggers(): %s', $e->getMessage())); - Log::error($e->getTraceAsString()); + $oldInput = $request->old('rule_triggers'); + if (\is_array($oldInput)) { + foreach ($oldInput as $oldTrigger) { + try { + $triggers[] = view( + 'rules.partials.trigger', + [ + 'oldTrigger' => $oldTrigger['name'], + 'oldValue' => $oldTrigger['value'], + 'oldChecked' => 1 === (int)($oldTrigger['stop_processing'] ?? '0'), + 'count' => $index + 1, + ] + )->render(); + } catch (Throwable $e) { + Log::debug(sprintf('Throwable was thrown in getPreviousTriggers(): %s', $e->getMessage())); + Log::error($e->getTraceAsString()); + } + $index++; } - // @codeCoverageIgnoreEnd - ++$newIndex; } return $triggers; diff --git a/app/TransactionRules/Factory/TriggerFactory.php b/app/TransactionRules/Factory/TriggerFactory.php index 96caf8d78e..3bced52e35 100644 --- a/app/TransactionRules/Factory/TriggerFactory.php +++ b/app/TransactionRules/Factory/TriggerFactory.php @@ -89,7 +89,7 @@ class TriggerFactory /** @var AbstractTrigger $class */ $class = self::getTriggerClass($triggerType); $obj = $class::makeFromStrings($triggerValue, $stopProcessing); - Log::debug('Created trigger from string', ['type' => $triggerType, 'value' => $triggerValue, 'stopProcessing' => $stopProcessing, 'class' => $class]); + Log::debug('Created trigger from string', ['type' => $triggerType, 'value' => $triggerValue, 'stop_processing' => $stopProcessing, 'class' => $class]); return $obj; } diff --git a/app/TransactionRules/Processor.php b/app/TransactionRules/Processor.php index 14d44d0fc7..a3de4387f0 100644 --- a/app/TransactionRules/Processor.php +++ b/app/TransactionRules/Processor.php @@ -122,7 +122,7 @@ final class Processor * * The given triggers must be in the following format: * - * [type => xx, value => yy, stopProcessing => bool], [type => xx, value => yy, stopProcessing => bool], + * [type => xx, value => yy, stop_processing => bool], [type => xx, value => yy, stop_processing => bool], * * @param array $triggers * @@ -135,7 +135,7 @@ final class Processor $self = new self; foreach ($triggers as $entry) { $entry['value'] = $entry['value'] ?? ''; - $trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stopProcessing']); + $trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stop_processing']); $self->triggers->push($trigger); } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index c44ac55640..fa14ce1b70 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -247,148 +247,99 @@ class FireflyValidator extends Validator * * @return bool */ - public function validateRuleActionValue($attribute): bool + public function validateRuleActionValue(string $attribute, string $value): bool { - // get the index from a string like "rule-action-value.2". + // first, get the index from this string: $parts = explode('.', $attribute); - $index = $parts[\count($parts) - 1]; - if ($index === 'value') { - // user is coming from API. - $index = $parts[\count($parts) - 2]; - } - $index = (int)$index; + $index = (int)($parts[1] ?? '0'); - // get actions from $this->data - $actions = []; - if (isset($this->data['rule-action']) && \is_array($this->data['rule-action'])) { - $actions = $this->data['rule-action']; - } - if (isset($this->data['rule-actions']) && \is_array($this->data['rule-actions'])) { - $actions = $this->data['rule-actions']; + // get the name of the trigger from the data array: + $actionType = $this->data['rule_actions'][$index]['name'] ?? 'invalid'; + + // if it's "invalid" return false. + if ('invalid' === $actionType) { + return false; } + // if it's set_budget, verify the budget name: + if ('set_budget' === $actionType) { + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); + $budgets = $repository->getBudgets(); + // count budgets, should have at least one + $count = $budgets->filter( + function (Budget $budget) use ($value) { + return $budget->name === $value; + } + )->count(); - // loop all rule-actions. - // check if rule-action-value matches the thing. - if (\is_array($actions)) { - $name = $this->getRuleActionName($index); - $value = $this->getRuleActionValue($index); - switch ($name) { - default: - - return true; - case 'set_budget': - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $budgets = $repository->getBudgets(); - // count budgets, should have at least one - $count = $budgets->filter( - function (Budget $budget) use ($value) { - return $budget->name === $value; - } - )->count(); - - return 1 === $count; - case 'link_to_bill': - /** @var BillRepositoryInterface $repository */ - $repository = app(BillRepositoryInterface::class); - $bill = $repository->findByName($value); - - return null !== $bill; - case 'invalid': - return false; - } + return 1 === $count; } - return false; + // if it's link to bill, verify the name of the bill. + if ('link_to_bill' === $actionType) { + /** @var BillRepositoryInterface $repository */ + $repository = app(BillRepositoryInterface::class); + $bill = $repository->findByName($value); + + return null !== $bill; + } + + // return true for the rest. + return true; } /** - * @param $attribute + * $attribute has the format rule_triggers.%d.value. + * + * @param string $attribute + * @param string $value * * @return bool */ - public function validateRuleTriggerValue($attribute): bool + public function validateRuleTriggerValue(string $attribute, string $value): bool { - // get the index from a string like "rule-trigger-value.2". + // + + // first, get the index from this string: $parts = explode('.', $attribute); - $index = $parts[\count($parts) - 1]; - // if the index is not a number, then we might be dealing with an API $attribute - // which is formatted "rule-triggers.0.value" - if ($index === 'value') { - $index = $parts[\count($parts) - 2]; - } - $index = (int)$index; + $index = (int)($parts[1] ?? '0'); - // get triggers from $this->data - $triggers = []; - if (isset($this->data['rule-trigger']) && \is_array($this->data['rule-trigger'])) { - $triggers = $this->data['rule-trigger']; - } - if (isset($this->data['rule-triggers']) && \is_array($this->data['rule-triggers'])) { - $triggers = $this->data['rule-triggers']; + // get the name of the trigger from the data array: + $triggerType = $this->data['rule_triggers'][$index]['name'] ?? 'invalid'; + + // invalid always returns false: + if ('invalid' === $triggerType) { + return false; } - // loop all rule-triggers. - // check if rule-value matches the thing. - if (\is_array($triggers)) { - $name = $this->getRuleTriggerName($index); - $value = $this->getRuleTriggerValue($index); - - // break on some easy checks: - switch ($name) { - case 'amount_less': - case 'amount_more': - case 'amount_exactly': - $result = is_numeric($value); - if (false === $result) { - return false; - } - break; - case 'from_account_starts': - case 'from_account_ends': - case 'from_account_is': - case 'from_account_contains': - case 'to_account_starts': - case 'to_account_ends': - case 'to_account_is': - case 'to_account_contains': - case 'description_starts': - case 'description_ends': - case 'description_contains': - case 'description_is': - case 'category_is': - case 'budget_is': - case 'tag_is': - case 'currency_is': - case 'notes_contain': - case 'notes_start': - case 'notes_end': - case 'notes_are': - return \strlen($value) > 0; - - break; - case 'transaction_type': - $count = TransactionType::where('type', $value)->count(); - if (!(1 === $count)) { - return false; - } - break; - case 'invalid': - return false; - } - // still a special case where the trigger is - // triggered in such a way that it would trigger ANYTHING. We can check for such things - // with function willmatcheverything - // we know which class it is so dont bother checking that. - $classes = Config::get('firefly.rule-triggers'); - /** @var TriggerInterface $class */ - $class = $classes[$name]; - - return !$class::willMatchEverything($value); + // these trigger types need a numerical check: + $numerical = ['amount_less', 'amount_more', 'amount_exactly']; + if (\in_array($triggerType, $numerical, true)) { + return is_numeric($value); } - return false; + // these trigger types need a simple strlen check: + $length = ['from_account_starts', 'from_account_ends', 'from_account_is', 'from_account_contains', 'to_account_starts', 'to_account_ends', + 'to_account_is', 'to_account_contains', 'description_starts', 'description_ends', 'description_contains', 'description_is', 'category_is', + 'budget_is', 'tag_is', 'currency_is', 'notes_contain', 'notes_start', 'notes_end', 'notes_are',]; + if (\in_array($triggerType, $length, true)) { + return '' !== $value; + } + + // check transaction type. + if ('transaction_type' === $triggerType) { + $count = TransactionType::where('type', $value)->count(); + + return 1 !== $count; + } + + // and finally a "will match everything check": + $classes = app('config')->get('firefly.rule-triggers'); + /** @var TriggerInterface $class */ + $class = $classes[$triggerType]; + + return !$class::willMatchEverything($value); } /** diff --git a/public/js/ff/rules/create-edit.js b/public/js/ff/rules/create-edit.js index a2d5b89451..1a8f1f2503 100644 --- a/public/js/ff/rules/create-edit.js +++ b/public/js/ff/rules/create-edit.js @@ -22,20 +22,26 @@ $(function () { "use strict"; - if (triggerCount === 0) { - addNewTrigger(); - } - if (actionCount === 0) { - addNewAction(); - } + if (triggerCount > 0) { + console.log('trigger count is larger than zero, call onAddNewTrigger.'); onAddNewTrigger(); } if (actionCount > 0) { + console.log('action count is larger than zero, call onAddNewAction.'); onAddNewAction(); } + if (triggerCount === 0) { + console.log('trigger count is zero, add trigger.'); + addNewTrigger(); + } + if (actionCount === 0) { + console.log('action count is zero, add action.'); + addNewAction(); + } + $('.add_rule_trigger').click(addNewTrigger); $('.add_rule_action').click(addNewAction); $('.test_rule_triggers').click(testRuleTriggers); @@ -49,7 +55,7 @@ $(function () { function addNewTrigger() { "use strict"; triggerCount++; - + console.log('In addNewTrigger(), count is now ' + triggerCount); // disable the button $('.add_rule_trigger').attr('disabled', 'disabled'); @@ -81,7 +87,7 @@ function addNewTrigger() { function addNewAction() { "use strict"; actionCount++; - + console.log('In addNewAction(), count is now ' + actionCount); // disable the button $('.add_rule_action').attr('disabled', 'disabled'); @@ -154,18 +160,24 @@ function removeAction(e) { */ function onAddNewAction() { "use strict"; + console.log('Now in onAddNewAction()'); + + var selectQuery = 'select[name^="rule_actions["][name$="][name]"]'; + var selectResult = $(selectQuery); + + console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length); // update all "select action type" dropdown buttons so they will respond correctly - $('select[name^="rule-action["]').unbind('change').change(function (e) { + selectResult.unbind('change').change(function (e) { var target = $(e.target); updateActionInput(target) }); - $.each($('.rule-action-holder'), function (i, v) { - var holder = $(v); - var select = holder.find('select'); - updateActionInput(select); - }); + // $.each($('.rule-action-holder'), function (i, v) { + // var holder = $(v); + // var select = holder.find('select'); + // updateActionInput(select); + // }); } /** @@ -173,18 +185,24 @@ function onAddNewAction() { */ function onAddNewTrigger() { "use strict"; + console.log('Now in onAddNewTrigger()'); - // update all "select trigger type" dropdown buttons so they will respond correctly - $('select[name^="rule-trigger["]').unbind('change').change(function (e) { + var selectQuery = 'select[name^="rule_triggers["][name$="][name]"]'; + var selectResult = $(selectQuery); + + console.log('Select query is "' + selectQuery + '" and the result length is ' + selectResult.length); + + // trigger when user changes the trigger type. + selectResult.unbind('change').change(function (e) { var target = $(e.target); updateTriggerInput(target) }); - $.each($('.rule-trigger-holder'), function (i, v) { - var holder = $(v); - var select = holder.find('select'); - updateTriggerInput(select); - }); + // $.each($('.rule-trigger-holder'), function (i, v) { + // var holder = $(v); + // var select = holder.find('select'); + // updateTriggerInput(select); + // }); } /** @@ -193,42 +211,56 @@ function onAddNewTrigger() { * @param selectList */ function updateActionInput(selectList) { + console.log('Now in updateActionInput() for a select list, currently with value "' + selectList.val() + '".'); // the actual row this select list is in: var parent = selectList.parent().parent(); // the text input we're looking for: - var input = parent.find('input[name^="rule-action-value["]'); - input.removeAttr('disabled'); + var inputQuery = 'input[name^="rule_actions["][name$="][value]"]'; + var inputResult = parent.find(inputQuery); + + console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.'); + + inputResult.removeAttr('disabled'); switch (selectList.val()) { case 'set_category': - createAutoComplete(input, 'json/categories'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/categories'); break; case 'clear_category': case 'clear_budget': case 'clear_notes': case 'remove_all_tags': - input.attr('disabled', 'disabled'); + console.log('Select list value is ' + selectList.val() +', so input needs to be disabled.'); + inputResult.attr('disabled', 'disabled'); break; case 'set_budget': - createAutoComplete(input, 'json/budgets'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/budgets'); break; case 'add_tag': case 'remove_tag': - createAutoComplete(input, 'json/tags'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/tags'); break; case 'set_description': - createAutoComplete(input, 'json/transaction-journals/all'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/transaction-journals/all'); break; case 'set_source_account': - createAutoComplete(input, 'json/all-accounts'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/all-accounts'); break; case 'set_destination_account': - createAutoComplete(input, 'json/all-accounts'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/all-accounts'); break; case 'link_to_bill': - createAutoComplete(input, 'json/bills'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/bills'); break; default: - input.typeahead('destroy'); + console.log('Select list value is ' + selectList.val() +', destroy auto complete, do nothing else.'); + inputResult.typeahead('destroy'); break; } } @@ -239,11 +271,15 @@ function updateActionInput(selectList) { * @param selectList */ function updateTriggerInput(selectList) { + console.log('Now in updateTriggerInput() for a select list, currently with value "' + selectList.val() + '".'); // the actual row this select list is in: var parent = selectList.parent().parent(); // the text input we're looking for: - var input = parent.find('input[name^="rule-trigger-value["]'); - input.prop('disabled', false); + var inputQuery = 'input[name^="rule_triggers["][name$="][value]"]'; + var inputResult = parent.find(inputQuery); + + console.log('Searching for children in this row with query "' + inputQuery + '" resulted in ' + inputResult.length + ' results.'); + inputResult.prop('disabled', false); switch (selectList.val()) { case 'from_account_starts': case 'from_account_ends': @@ -253,26 +289,31 @@ function updateTriggerInput(selectList) { case 'to_account_ends': case 'to_account_is': case 'to_account_contains': - createAutoComplete(input, 'json/all-accounts'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/all-accounts'); break; case 'tag_is': - // also make tag thing? - createAutoComplete(input, 'json/tags'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/tags'); break; case 'budget_is': - createAutoComplete(input, 'json/budgets'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/budgets'); break; case 'category_is': - createAutoComplete(input, 'json/categories'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/categories'); break; case 'transaction_type': - createAutoComplete(input, 'json/transaction-types'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/transaction-types'); break; case 'description_starts': case 'description_ends': case 'description_contains': case 'description_is': - createAutoComplete(input, 'json/transaction-journals/all'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/transaction-journals/all'); break; case 'has_no_category': case 'has_any_category': @@ -282,14 +323,17 @@ function updateTriggerInput(selectList) { case 'no_notes': case 'any_notes': case 'has_any_tag': - input.prop('disabled', true); - input.typeahead('destroy'); + console.log('Select list value is ' + selectList.val() +', so input needs to be disabled.'); + inputResult.prop('disabled', true); + inputResult.typeahead('destroy'); break; case 'currency_is': - createAutoComplete(input, 'json/currency-names'); + console.log('Select list value is ' + selectList.val() +', so input needs auto complete.'); + createAutoComplete(inputResult, 'json/currency-names'); break; default: - input.typeahead('destroy'); + console.log('Select list value is ' + selectList.val() +', destroy auto complete, do nothing else.'); + inputResult.typeahead('destroy'); break; } } @@ -300,9 +344,13 @@ function updateTriggerInput(selectList) { * @param URI */ function createAutoComplete(input, URI) { + console.log('Now in createAutoComplete().') input.typeahead('destroy'); $.getJSON(URI).done(function (data) { + console.log('Input now has auto complete from URI ' + URI); input.typeahead({source: data, autoSelect: false}); + }).fail(function() { + console.log('Could not grab URI ' + URI + ' so autocomplete will not work'); }); } @@ -319,6 +367,7 @@ function testRuleTriggers() { // Serialize all trigger data var triggerData = $(".rule-trigger-tbody").find("input[type=text], input[type=checkbox], select").serializeArray(); + console.log('Found the following trigger data: ' + triggerData); // Find a list of existing transactions that match these triggers $.get('rules/test', triggerData).done(function (data) { diff --git a/resources/views/rules/partials/action.twig b/resources/views/rules/partials/action.twig index 4c13329f86..80b03d1474 100644 --- a/resources/views/rules/partials/action.twig +++ b/resources/views/rules/partials/action.twig @@ -3,12 +3,14 @@
    {% endif %} + {% if what == 'liabilities' %} + + + {% endif %} - + {# hide last activity to make room for other stuff #} + {% if what != 'liabilities' %} + + {% endif %} @@ -40,6 +47,12 @@ {% endfor %} {% endif %} + {% if what == 'liabilities' %} + + + {% endif %} - {% if account.lastActivityDate %} - - {% else %} - + {# hide last activity to make room for other stuff #} + {% if what != 'liabilities' %} + {% if account.lastActivityDate %} + + {% else %} + + {% endif %} {% endif %} {% for transaction in transactions %} - {% include 'partials.transaction-row' %} + {% include 'partials.journal-row' %} {% endfor %}
    {{ link.source|journalTotalAmount }}{{ linkType.outward }}{{ journalLinkTranslation('outward', linkType.outward) }} {{ link.destination.description }} + {# {% if errors.has('rule-action.'~count) %}

    {{ errors.first('rule-action.'~count) }}

    {% endif %} + #} - {% for key,name in allRuleActions() %} {% endfor %} @@ -16,18 +18,20 @@
    - + {# {% if errors.has(('rule-action-value.'~count)) %}

    {{ errors.first('rule-action-value.'~count) }}

    {% endif %} + #}
    diff --git a/resources/views/rules/partials/trigger.twig b/resources/views/rules/partials/trigger.twig index e8f9279426..2db8dfd5a7 100644 --- a/resources/views/rules/partials/trigger.twig +++ b/resources/views/rules/partials/trigger.twig @@ -3,12 +3,14 @@
    + {# {% if errors.has('rule-trigger.'~count) %}

    {{ errors.first('rule-trigger.'~count) }}

    {% endif %} + #} - {% for key,name in allRuleTriggers() %}
    - + + {# {% if errors.has(('rule-trigger-value.'~count)) %}

    {{ errors.first('rule-trigger-value.'~count) }}

    {% endif %} + #}
    diff --git a/routes/breadcrumbs.php b/routes/breadcrumbs.php index 42e37b0914..473eb2c467 100644 --- a/routes/breadcrumbs.php +++ b/routes/breadcrumbs.php @@ -155,7 +155,9 @@ try { $breadcrumbs->parent('accounts.show', $account); $what = config('firefly.shortNamesByFullName.' . $account->accountType->type); - $breadcrumbs->push(trans('firefly.edit_' . $what . '_account', ['name' => limitStringLength($account->name)]), route('accounts.edit', [$account->id])); + $breadcrumbs->push( + trans('firefly.edit_' . $what . '_account', ['name' => limitStringLength($account->name)]), route('accounts.edit', [$account->id]) + ); } ); @@ -294,7 +296,9 @@ try { $object = $attachment->attachable; if ($object instanceof TransactionJournal) { $breadcrumbs->parent('transactions.show', $object); - $breadcrumbs->push(trans('firefly.delete_attachment', ['name' => limitStringLength($attachment->filename)]), route('attachments.edit', [$attachment])); + $breadcrumbs->push( + trans('firefly.delete_attachment', ['name' => limitStringLength($attachment->filename)]), route('attachments.edit', [$attachment]) + ); } else { throw new FireflyException('Cannot make breadcrumb for attachment connected to object of type ' . get_class($object)); } @@ -834,9 +838,15 @@ try { Breadcrumbs::register( 'rules.create', - function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup) { + function (BreadcrumbsGenerator $breadcrumbs, RuleGroup $ruleGroup = null) { $breadcrumbs->parent('rules.index'); - $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup])); + if (null === $ruleGroup) { + $breadcrumbs->push(trans('firefly.make_new_rule_no_group'), route('rules.create')); + } + if (null !== $ruleGroup) { + $breadcrumbs->push(trans('firefly.make_new_rule', ['title' => $ruleGroup->title]), route('rules.create', [$ruleGroup])); + } + } ); Breadcrumbs::register( From 0a89f4000de1ea412a799d20353014d05a224346 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 5 Aug 2018 15:41:13 +0200 Subject: [PATCH 033/166] Synchronise API and app rule management. --- app/Api/V1/Requests/RuleRequest.php | 77 +++++++++++++++++++---------- app/Validation/FireflyValidator.php | 5 +- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/app/Api/V1/Requests/RuleRequest.php b/app/Api/V1/Requests/RuleRequest.php index d44833852e..f8e50e4da2 100644 --- a/app/Api/V1/Requests/RuleRequest.php +++ b/app/Api/V1/Requests/RuleRequest.php @@ -56,27 +56,12 @@ class RuleRequest extends Request 'rule_group_title' => $this->string('rule_group_title'), 'trigger' => $this->string('trigger'), 'strict' => $this->boolean('strict'), - 'stop-processing' => $this->boolean('stop_processing'), + 'stop_processing' => $this->boolean('stop_processing'), 'active' => $this->boolean('active'), - 'rule-triggers' => [], - 'rule-actions' => [], + 'rule_triggers' => $this->getRuleTriggers(), + 'rule_actions' => $this->getRuleActions(), ]; - foreach ($this->get('rule-triggers') as $trigger) { - $data['rule-triggers'][] = [ - 'name' => $trigger['name'], - 'value' => $trigger['value'], - 'stop-processing' => 1 === (int)($trigger['stop-processing'] ?? 0), - ]; - } - foreach ($this->get('rule-actions') as $action) { - $data['rule-actions'][] = [ - 'name' => $action['name'], - 'value' => $action['value'], - 'stop-processing' => 1 === (int)($action['stop-processing'] ?? 0), - ]; - } - return $data; } @@ -99,12 +84,12 @@ class RuleRequest extends Request 'rule_group_id' => 'required|belongsToUser:rule_groups|required_without:rule_group_title', 'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title', 'trigger' => 'required|in:store-journal,update-journal', - 'rule-triggers.*.name' => 'required|in:' . implode(',', $validTriggers), - 'rule-triggers.*.stop-processing' => 'boolean', - 'rule-triggers.*.value' => 'required|min:1|ruleTriggerValue', // - 'rule-actions.*.name' => 'required|in:' . implode(',', $validActions), - 'rule-actions.*.value' => 'required_if:rule-action.*.type,' . $contextActions . '|ruleActionValue', - 'rule-actions.*.stop-processing' => 'boolean', + 'rule_triggers.*.name' => 'required|in:' . implode(',', $validTriggers), + 'rule_triggers.*.stop_processing' => 'boolean', + 'rule_triggers.*.value' => 'required|min:1|ruleTriggerValue', + 'rule_actions.*.name' => 'required|in:' . implode(',', $validActions), + 'rule_actions.*.value' => 'required_if:rule_actions.*.type,' . $contextActions . '|ruleActionValue', + 'rule_actions.*.stop_processing' => 'boolean', 'strict' => 'required|boolean', 'stop_processing' => 'required|boolean', 'active' => 'required|boolean', @@ -138,7 +123,7 @@ class RuleRequest extends Request protected function atLeastOneAction(Validator $validator): void { $data = $validator->getData(); - $repetitions = $data['rule-actions'] ?? []; + $repetitions = $data['rule_actions'] ?? []; // need at least one transaction if (0 === \count($repetitions)) { $validator->errors()->add('title', (string)trans('validation.at_least_one_action')); @@ -153,10 +138,50 @@ class RuleRequest extends Request protected function atLeastOneTrigger(Validator $validator): void { $data = $validator->getData(); - $repetitions = $data['rule-triggers'] ?? []; + $repetitions = $data['rule_triggers'] ?? []; // need at least one transaction if (0 === \count($repetitions)) { $validator->errors()->add('title', (string)trans('validation.at_least_one_trigger')); } } + + /** + * @return array + */ + private function getRuleActions(): array + { + $actions = $this->get('rule_actions'); + $return = []; + if (\is_array($actions)) { + foreach ($actions as $action) { + $return[] = [ + 'name' => $action['name'], + 'value' => $action['value'], + 'stop_processing' => 1 === (int)($action['stop-processing'] ?? '0'), + ]; + } + } + + return $return; + } + + /** + * @return array + */ + private function getRuleTriggers(): array + { + $triggers = $this->get('rule_triggers'); + $return = []; + if (\is_array($triggers)) { + foreach ($triggers as $trigger) { + $return[] = [ + 'name' => $trigger['name'], + 'value' => $trigger['value'], + 'stop_processing' => 1 === (int)($trigger['stop-processing'] ?? '0'), + ]; + } + } + + return $return; + } } diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index fa14ce1b70..5125bb6a90 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -337,7 +337,10 @@ class FireflyValidator extends Validator // and finally a "will match everything check": $classes = app('config')->get('firefly.rule-triggers'); /** @var TriggerInterface $class */ - $class = $classes[$triggerType]; + $class = $classes[$triggerType] ?? false; + if(false === $class) { + return false; + } return !$class::willMatchEverything($value); } From 33294dd9f04c3ee89a45f40bd8eeee8271485bc5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 5 Aug 2018 18:59:15 +0200 Subject: [PATCH 034/166] Allow editing of liabilities. --- .../Controllers/Account/EditController.php | 31 +++++++- .../Controllers/Account/IndexController.php | 11 +-- app/Http/Controllers/BillController.php | 31 ++------ .../Controllers/Rule/CreateController.php | 51 ++++++++++++- app/Models/Account.php | 7 +- .../Account/AccountRepository.php | 2 +- app/Support/ExpandedForm.php | 71 ++++++++++--------- .../Http/Controllers/RuleManagement.php | 14 ++-- resources/lang/en_US/firefly.php | 3 + resources/lang/en_US/list.php | 3 + resources/views/accounts/create.twig | 3 +- resources/views/accounts/edit.twig | 12 +++- resources/views/list/accounts.twig | 34 ++++++--- resources/views/partials/control-bar.twig | 9 +++ routes/breadcrumbs.php | 44 +++++++----- routes/web.php | 1 + .../Account/CreateControllerTest.php | 8 +++ 17 files changed, 228 insertions(+), 107 deletions(-) diff --git a/app/Http/Controllers/Account/EditController.php b/app/Http/Controllers/Account/EditController.php index 3bab623ed8..0201d5659c 100644 --- a/app/Http/Controllers/Account/EditController.php +++ b/app/Http/Controllers/Account/EditController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Http\Controllers\Account; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Http\Request; @@ -85,6 +86,26 @@ class EditController extends Controller $roles[$role] = (string)trans('firefly.account_role_' . $role); } + // types of liability: + $debt = $this->repository->getAccountTypeByType(AccountType::DEBT); + $loan = $this->repository->getAccountTypeByType(AccountType::LOAN); + $mortgage = $this->repository->getAccountTypeByType(AccountType::MORTGAGE); + $creditCard = $this->repository->getAccountTypeByType(AccountType::CREDITCARD); + $liabilityTypes = [ + $debt->id => (string)trans('firefly.account_type_' . AccountType::DEBT), + $loan->id => (string)trans('firefly.account_type_' . AccountType::LOAN), + $mortgage->id => (string)trans('firefly.account_type_' . AccountType::MORTGAGE), + $creditCard->id => (string)trans('firefly.account_type_' . AccountType::CREDITCARD), + ]; + asort($liabilityTypes); + + // interest calculation periods: + $interestPeriods = [ + 'daily' => (string)trans('firefly.interest_calc_daily'), + 'monthly' => (string)trans('firefly.interest_calc_monthly'), + 'yearly' => (string)trans('firefly.interest_calc_yearly'), + ]; + // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('accounts.edit.fromUpdate')) { $this->rememberPreviousUri('accounts.edit.uri'); @@ -108,16 +129,24 @@ class EditController extends Controller 'ccMonthlyPaymentDate' => $repository->getMetaValue($account, 'ccMonthlyPaymentDate'), 'BIC' => $repository->getMetaValue($account, 'BIC'), 'openingBalanceDate' => $openingBalanceDate, + 'liability_type_id' => $account->account_type_id, 'openingBalance' => $openingBalanceAmount, 'virtualBalance' => $account->virtual_balance, 'currency_id' => $currency->id, + 'interest' => $repository->getMetaValue($account, 'interest'), + 'interest_period' => $repository->getMetaValue($account, 'interest_period'), 'notes' => $this->repository->getNoteText($account), 'active' => $hasOldInput ? (bool)$request->old('active') : $account->active, ]; + if ('liabilities' === $what) { + $preFilled['openingBalance'] = bcmul($preFilled['openingBalance'], '-1'); + } $request->session()->flash('preFilled', $preFilled); - return view('accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled')); + return view( + 'accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled', 'liabilityTypes', 'interestPeriods') + ); } diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index 533c7e2e9b..98da7143e4 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -101,10 +101,13 @@ class IndexController extends Controller $accounts->each( function (Account $account) use ($activities, $startBalances, $endBalances) { - $account->lastActivityDate = $this->isInArray($activities, $account->id); - $account->startBalance = $this->isInArray($startBalances, $account->id); - $account->endBalance = $this->isInArray($endBalances, $account->id); - $account->difference = bcsub($account->endBalance, $account->startBalance); + $account->lastActivityDate = $this->isInArray($activities, $account->id); + $account->startBalance = $this->isInArray($startBalances, $account->id); + $account->endBalance = $this->isInArray($endBalances, $account->id); + $account->difference = bcsub($account->endBalance, $account->startBalance); + $account->interest = round($this->repository->getMetaValue($account, 'interest'), 6); + $account->interestPeriod = (string)trans('firefly.interest_calc_' . $this->repository->getMetaValue($account, 'interest_period')); + $account->accountTypeString = (string)trans('firefly.account_type_' . $account->accountType->type); } ); diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 5e3e12f362..efc6dfb0b5 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -276,10 +276,10 @@ class BillController extends Controller public function show(Request $request, Bill $bill) { // add info about rules: - $rules = $this->billRepository->getRulesForBill($bill); - $subTitle = $bill->name; + $rules = $this->billRepository->getRulesForBill($bill); + $subTitle = $bill->name; /** @var Carbon $start */ - $start = session('start'); + $start = session('start'); /** @var Carbon $end */ $end = session('end'); $year = $start->year; @@ -342,30 +342,7 @@ class BillController extends Controller $request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); // @codeCoverageIgnore } - // do return to original bill form? - $return = 'false'; - if (1 === (int)$request->get('create_another')) { - $return = 'true'; - } - - $group = null; - // find first rule group, or create one: - $count = $this->ruleGroupRepos->count(); - if (0 === $count) { - $data = [ - 'title' => (string)trans('firefly.rulegroup_for_bills_title'), - 'description' => (string)trans('firefly.rulegroup_for_bills_description'), - ]; - $group = $this->ruleGroupRepos->store($data); - } - if ($count > 0) { - $group = $this->ruleGroupRepos->getActiveGroups($bill->user)->first(); - } - - // redirect to page that will create a new rule. - $params = http_build_query(['fromBill' => $bill->id, 'return' => $return]); - - return redirect(route('rules.create', [$group->id]) . '?' . $params); + return redirect(route('rules.create-from-bill', [$bill->id])); } /** diff --git a/app/Http/Controllers/Rule/CreateController.php b/app/Http/Controllers/Rule/CreateController.php index 622440301d..e475de3551 100644 --- a/app/Http/Controllers/Rule/CreateController.php +++ b/app/Http/Controllers/Rule/CreateController.php @@ -70,8 +70,6 @@ class CreateController extends Controller /** * Create a new rule. It will be stored under the given $ruleGroup. * - * TODO reinstate bill specific code. - * * @param Request $request * @param RuleGroup $ruleGroup * @@ -119,6 +117,55 @@ class CreateController extends Controller ); } + /** + * Create a new rule. It will be stored under the given $ruleGroup. + * + * @param Request $request + * @param Bill $bill + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function createFromBill(Request $request, Bill $bill) + { + $request->session()->flash('info', (string)trans('firefly.instructions_rule_from_bill', ['name' => $bill->name])); + + $this->createDefaultRuleGroup(); + $this->createDefaultRule(); + $preFilled = [ + 'strict' => true, + 'title' => (string)trans('firefly.new_rule_for_bill_title', ['name' => $bill->name]), + 'description' => (string)trans('firefly.new_rule_for_bill_description', ['name' => $bill->name]), + ]; + + // make triggers and actions from the bill itself. + + // get triggers and actions for bill: + $oldTriggers = $this->getTriggersForBill($bill); + $oldActions = $this->getActionsForBill($bill); + + $triggerCount = \count($oldTriggers); + $actionCount = \count($oldActions); + $subTitleIcon = 'fa-clone'; + + // title depends on whether or not there is a rule group: + $subTitle = (string)trans('firefly.make_new_rule_no_group'); + + // flash old data + $request->session()->flash('preFilled', $preFilled); + + // put previous url in session if not redirect from store (not "create another"). + if (true !== session('rules.create.fromStore')) { + $this->rememberPreviousUri('rules.create.uri'); + } + session()->forget('rules.create.fromStore'); + + return view( + 'rules.rule.create', compact('subTitleIcon', 'oldTriggers', 'preFilled', 'oldActions', 'triggerCount', 'actionCount', 'subTitle') + ); + } + /** * Store the new rule. * diff --git a/app/Models/Account.php b/app/Models/Account.php index b4d40cc1af..c4a25daf2f 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -53,8 +53,11 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon lastActivityDate * @property Collection accountMeta * @property bool encrypted - * @property int account_type_id - * @property Collection piggyBanks + * @property int account_type_id + * @property Collection piggyBanks + * @property string $interest + * @property string $interestPeriod + * @property string accountTypeString * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 29616566f1..8489a62a80 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -299,10 +299,10 @@ class AccountRepository implements AccountRepositoryInterface public function getNoteText(Account $account): ?string { $note = $account->notes()->first(); + if (null === $note) { return null; } - return $note->text; } diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 51609ca6c1..08709f9661 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -41,7 +41,6 @@ use Illuminate\Support\HtmlString; use Illuminate\Support\MessageBag; use Log; use RuntimeException; -use Session; use Throwable; /** @@ -246,7 +245,7 @@ class ExpandedForm $value = $value ?? 1; $options['checked'] = true === $checked; - if (Session::has('preFilled')) { + if (app('session')->has('preFilled')) { $preFilled = session('preFilled'); $options['checked'] = $preFilled[$name] ?? $options['checked']; } @@ -528,34 +527,6 @@ class ExpandedForm return $html; } - /** - * Function to render a percentage. - * - * @param string $name - * @param mixed $value - * @param array $options - * - * @return string - * - */ - public function percentage(string $name, $value = null, array $options = null): string - { - $label = $this->label($name, $options); - $options = $this->expandOptionArray($name, $label, $options); - $classes = $this->getHolderClasses($name); - $value = $this->fillFieldValue($name, $value); - $options['step'] = 'any'; - unset($options['placeholder']); - try { - $html = view('form.percentage', compact('classes', 'name', 'label', 'value', 'options'))->render(); - } catch (Throwable $e) { - Log::debug(sprintf('Could not render percentage(): %s', $e->getMessage())); - $html = 'Could not render percentage.'; - } - - return $html; - } - /** * @param string $type * @param string $name @@ -598,6 +569,34 @@ class ExpandedForm return $html; } + /** + * Function to render a percentage. + * + * @param string $name + * @param mixed $value + * @param array $options + * + * @return string + * + */ + public function percentage(string $name, $value = null, array $options = null): string + { + $label = $this->label($name, $options); + $options = $this->expandOptionArray($name, $label, $options); + $classes = $this->getHolderClasses($name); + $value = $this->fillFieldValue($name, $value); + $options['step'] = 'any'; + unset($options['placeholder']); + try { + $html = view('form.percentage', compact('classes', 'name', 'label', 'value', 'options'))->render(); + } catch (Throwable $e) { + Log::debug(sprintf('Could not render percentage(): %s', $e->getMessage())); + $html = 'Could not render percentage.'; + } + + return $html; + } + /** * @param string $name * @param mixed $value @@ -784,12 +783,16 @@ class ExpandedForm */ public function textarea(string $name, $value = null, array $options = null): string { - $value = $value ?? ''; $label = $this->label($name, $options); $options = $this->expandOptionArray($name, $label, $options); $classes = $this->getHolderClasses($name); $value = $this->fillFieldValue($name, $value); $options['rows'] = 4; + + if (null === $value) { + $value = ''; + } + try { $html = view('form.textarea', compact('classes', 'name', 'label', 'value', 'options'))->render(); } catch (Throwable $e) { @@ -880,12 +883,13 @@ class ExpandedForm * @return mixed * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function fillFieldValue(string $name, $value) + protected function fillFieldValue(string $name, $value = null) { - if (Session::has('preFilled')) { + if (app('session')->has('preFilled')) { $preFilled = session('preFilled'); $value = isset($preFilled[$name]) && null === $value ? $preFilled[$name] : $value; } + try { if (null !== request()->old($name)) { $value = request()->old($name); @@ -894,6 +898,7 @@ class ExpandedForm // don't care about session errors. Log::debug(sprintf('Run time: %s', $e->getMessage())); } + if ($value instanceof Carbon) { $value = $value->format('Y-m-d'); } diff --git a/app/Support/Http/Controllers/RuleManagement.php b/app/Support/Http/Controllers/RuleManagement.php index 60d9b0bcb9..2dd2415f6c 100644 --- a/app/Support/Http/Controllers/RuleManagement.php +++ b/app/Support/Http/Controllers/RuleManagement.php @@ -47,36 +47,36 @@ trait RuleManagement if (0 === $ruleRepository->count()) { $data = [ 'rule_group_id' => $ruleRepository->getFirstRuleGroup()->id, - 'stop-processing' => 0, + 'stop_processing' => 0, 'title' => (string)trans('firefly.default_rule_name'), 'description' => (string)trans('firefly.default_rule_description'), 'trigger' => 'store-journal', 'strict' => true, - 'rule-triggers' => [ + 'rule_triggers' => [ [ 'name' => 'description_is', 'value' => (string)trans('firefly.default_rule_trigger_description'), - 'stop-processing' => false, + 'stop_processing' => false, ], [ 'name' => 'from_account_is', 'value' => (string)trans('firefly.default_rule_trigger_from_account'), - 'stop-processing' => false, + 'stop_processing' => false, ], ], - 'rule-actions' => [ + 'rule_actions' => [ [ 'name' => 'prepend_description', 'value' => (string)trans('firefly.default_rule_action_prepend'), - 'stop-processing' => false, + 'stop_processing' => false, ], [ 'name' => 'set_category', 'value' => (string)trans('firefly.default_rule_action_set_category'), - 'stop-processing' => false, + 'stop_processing' => false, ], ], ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 7d8698b072..cd7e7b10e5 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -271,6 +271,7 @@ return [ 'save_rules_by_moving' => 'Save these rule(s) by moving them to another rule group:', 'make_new_rule' => 'Make a new rule in rule group ":title"', 'make_new_rule_no_group' => 'Make a new rule', + 'instructions_rule_from_bill' => 'In order to match transactions to your new bill ":name", Firefly III can create a rule that will automatically be checked against any transactions you store. Please verify the details below and store the rule to have Firefly III automatically match transactions to your new bill.', 'rule_is_strict' => 'strict rule', 'rule_is_not_strict' => 'non-strict rule', 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', @@ -694,6 +695,7 @@ return [ 'expense_deleted' => 'Successfully deleted expense account ":name"', 'revenue_deleted' => 'Successfully deleted revenue account ":name"', 'update_asset_account' => 'Update asset account', + 'update_liabilities_account' => 'Update liability', 'update_expense_account' => 'Update expense account', 'update_revenue_account' => 'Update revenue account', 'make_new_asset_account' => 'Create a new asset account', @@ -899,6 +901,7 @@ return [ 'debt_start_amount' => 'Start amount of debt', 'debt_start_amount_help' => 'Please enter a positive amount. Feel free to enter the current amount and date.', 'store_new_liabilities_account' => 'Store new liability', + 'edit_liabilities_account' => 'Edit liability ":name"', // reports: 'report_default' => 'Default financial report between :start and :end', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index bc36c21a41..4a50b795c8 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -130,4 +130,7 @@ return [ 'transaction_s' => 'Transaction(s)', 'field' => 'Field', 'value' => 'Value', + 'interest' => 'Interest', + 'interest_period' => 'interest period', + 'liability_type' => 'Type of liability', ]; diff --git a/resources/views/accounts/create.twig b/resources/views/accounts/create.twig index dc32ab6e77..7b8dc44121 100644 --- a/resources/views/accounts/create.twig +++ b/resources/views/accounts/create.twig @@ -17,7 +17,7 @@
    {{ ExpandedForm.text('name') }} - {% if what == 'asset' or what=='liabilities' %} + {% if what == 'asset' or what == 'liabilities' %} {{ ExpandedForm.currencyList('currency_id', null, {helpText:'account_default_currency'|_}) }} {% endif %} {% if what == 'liabilities' %} @@ -26,7 +26,6 @@ {{ ExpandedForm.date('openingBalanceDate', null, {label:'debt_start_date'|_}) }} {{ ExpandedForm.percentage('interest') }} {{ ExpandedForm.select('interest_period', interestPeriods) }} - {% endif %}
    diff --git a/resources/views/accounts/edit.twig b/resources/views/accounts/edit.twig index 827f0f4cc9..eea4a4c9eb 100644 --- a/resources/views/accounts/edit.twig +++ b/resources/views/accounts/edit.twig @@ -17,9 +17,17 @@
    {{ ExpandedForm.text('name') }} - {% if account.accountType.type == 'Default account' or account.accountType.type == 'Asset account' %} + {% if account.accountType.type == 'Default account' or account.accountType.type == 'Asset account' or what == 'liabilities' %} {{ ExpandedForm.currencyList('currency_id', null, {helpText:'account_default_currency'|_}) }} {% endif %} + + {% if what == 'liabilities' %} + {{ ExpandedForm.select('liability_type_id', liabilityTypes) }} + {{ ExpandedForm.amountNoCurrency('openingBalance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }} + {{ ExpandedForm.date('openingBalanceDate', null, {label:'debt_start_date'|_}) }} + {{ ExpandedForm.percentage('interest') }} + {{ ExpandedForm.select('interest_period', interestPeriods) }} + {% endif %}
    @@ -47,7 +55,7 @@ {{ ExpandedForm.amountNoCurrency('virtualBalance',null) }} {% endif %} - {{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }} + {{ ExpandedForm.textarea('notes',preFilled.notes,{helpText: trans('firefly.field_supports_markdown')}) }} {# only correct way to do active checkbox #} {{ ExpandedForm.checkbox('active', 1) }} diff --git a/resources/views/list/accounts.twig b/resources/views/list/accounts.twig index 7d016cbf77..499fcf6ad5 100644 --- a/resources/views/list/accounts.twig +++ b/resources/views/list/accounts.twig @@ -9,10 +9,17 @@ {% if what == 'asset' %}
    {{ trans('list.liability_type') }}{{ trans('list.interest') }} ({{ trans('list.interest_period') }}) {{ trans('list.currentBalance') }}
    {{ account.accountTypeString }} + {{ account.interest }}% ({{ account.interestPeriod|lower }}) + @@ -53,14 +66,17 @@ {% endif %}
    diff --git a/resources/views/list/transactions.twig b/resources/views/list/transactions.twig new file mode 100644 index 0000000000..f83f1cefb7 --- /dev/null +++ b/resources/views/list/transactions.twig @@ -0,0 +1,79 @@ +{# render pagination #} +{{ transactions.render|raw }} + + + + + + + + + + + + + {# Only show budgets when asked in some way #} + {% if showBudgets %} + + {% endif %} + + {# Only show categories when asked in some way #} + {% if showCategories %} + + {% endif %} + + {# Only show bill when asked in some way #} + {% if showBill %} + + {% endif %} + + + + {% for transaction in transactions %} + {% include 'partials.transaction-row' %} + {% endfor %} + +
    {{ trans('list.description') }}{{ trans('list.amount') }}
    + +
    +
    + {{ transactions.render|raw }} +
    +
    + diff --git a/resources/views/partials/journal-row.twig b/resources/views/partials/journal-row.twig new file mode 100644 index 0000000000..9f392268ee --- /dev/null +++ b/resources/views/partials/journal-row.twig @@ -0,0 +1,70 @@ +
    + {# is reconciled? #} + {{ transaction|transactionReconciled }} + + + {{ transaction|transactionDescription }} + + {# is a split journal #} + {{ transaction|transactionIsSplit }} + + {# count attachments #} + {{ transaction|transactionHasAtt }} + + + {{ transaction|transactionAmount }}
    {{ transaction|transactionAmount }} - - ~ {{ ((entry.amount_max+ entry.amount_min)/2)|formatAmount }} + + ~ {{ formatAmountByCurrency(entry.currency, (entry.amount_max + entry.amount_min)/2) }} {{ 'sum'|_ }} ({{ 'active_bills_only'|_ }}) - {{ sum_min|formatAmount }} + {{ formatAmountBySymbol(sum_min,'¤') }} - {{ sum_max|formatAmount }} + {{ formatAmountBySymbol(sum_max,'¤') }}  {{ 'average_per_bill'|_ }} ({{ 'active_bills_only'|_ }}) - {{ avg_min|formatAmount }} + {{ formatAmountBySymbol(avg_min,'¤') }} - {{ avg_max|formatAmount }} + {{ formatAmountBySymbol(avg_max,'¤') }}  {{ 'expected_total'|_ }} ({{ 'active_bills_only'|_ }}) - ~ {{ expected_total|formatAmount }} + ~ {{ formatAmountBySymbol(expected_total,'¤') }}  
    ","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function mt(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&O(t,e)?C.merge([t],n):n}function gt(t,e){for(var n=0,r=t.length;n-1)i&&i.push(o);else if(c=C.contains(o.ownerDocument,o),a=mt(f.appendChild(o),"script"),c&>(a),n)for(l=0;o=a[l++];)ht.test(o.type||"")&&n.push(o);return f}yt=a.createDocumentFragment().appendChild(a.createElement("div")),(bt=a.createElement("input")).setAttribute("type","radio"),bt.setAttribute("checked","checked"),bt.setAttribute("name","t"),yt.appendChild(bt),g.checkClone=yt.cloneNode(!0).cloneNode(!0).lastChild.checked,yt.innerHTML="",g.noCloneChecked=!!yt.cloneNode(!0).lastChild.defaultValue;var xt=a.documentElement,Ct=/^key/,Tt=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,kt=/^([^.]*)(?:\.(.+)|)/;function $t(){return!0}function At(){return!1}function St(){try{return a.activeElement}catch(t){}}function Et(t,e,n,r,i,o){var a,s;if("object"==typeof e){for(s in"string"!=typeof n&&(r=r||n,n=void 0),e)Et(t,s,n,r,e[s],o);return t}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=At;else if(!i)return t;return 1===o&&(a=i,(i=function(t){return C().off(t),a.apply(this,arguments)}).guid=a.guid||(a.guid=C.guid++)),t.each(function(){C.event.add(this,e,i,r,n)})}C.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,c,l,f,p,d,h,v,m=Y.get(t);if(m)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&C.find.matchesSelector(xt,i),n.guid||(n.guid=C.guid++),(u=m.events)||(u=m.events={}),(a=m.handle)||(a=m.handle=function(e){return void 0!==C&&C.event.triggered!==e.type?C.event.dispatch.apply(t,arguments):void 0}),c=(e=(e||"").match(F)||[""]).length;c--;)d=v=(s=kt.exec(e[c])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=C.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=C.event.special[d]||{},l=C.extend({type:d,origType:v,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&C.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,l),l.handler.guid||(l.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,l):p.push(l),C.event.global[d]=!0)},remove:function(t,e,n,r,i){var o,a,s,u,c,l,f,p,d,h,v,m=Y.hasData(t)&&Y.get(t);if(m&&(u=m.events)){for(c=(e=(e||"").match(F)||[""]).length;c--;)if(d=v=(s=kt.exec(e[c])||[])[1],h=(s[2]||"").split(".").sort(),d){for(f=C.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)l=p[o],!i&&v!==l.origType||n&&n.guid!==l.guid||s&&!s.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(p.splice(o,1),l.selector&&p.delegateCount--,f.remove&&f.remove.call(t,l));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(t,h,m.handle)||C.removeEvent(t,d,m.handle),delete u[d])}else for(d in u)C.event.remove(t,d+e[c],n,r,!0);C.isEmptyObject(u)&&Y.remove(t,"handle events")}},dispatch:function(t){var e,n,r,i,o,a,s=C.event.fix(t),u=new Array(arguments.length),c=(Y.get(this,"events")||{})[s.type]||[],l=C.event.special[s.type]||{};for(u[0]=s,e=1;e=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==t.type||!0!==c.disabled)){for(o=[],a={},n=0;n-1:C.find(i,this,null,[c]).length),a[i]&&o.push(r);o.length&&s.push({elem:c,handlers:o})}return c=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,jt=/\s*$/g;function It(t,e){return O(t,"table")&&O(11!==e.nodeType?e:e.firstChild,"tr")&&C(t).children("tbody")[0]||t}function Lt(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function Rt(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function Pt(t,e){var n,r,i,o,a,s,u,c;if(1===e.nodeType){if(Y.hasData(t)&&(o=Y.access(t),a=Y.set(e,o),c=o.events))for(i in delete a.handle,a.events={},c)for(n=0,r=c[i].length;n1&&"string"==typeof h&&!g.checkClone&&Nt.test(h))return t.each(function(i){var o=t.eq(i);v&&(e[0]=h.call(this,i,o.html())),Ft(o,e,n,r)});if(p&&(o=(i=wt(e,t[0].ownerDocument,!1,t,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(a=C.map(mt(i,"script"),Lt)).length;f")},clone:function(t,e,n){var r,i,o,a,s,u,c,l=t.cloneNode(!0),f=C.contains(t.ownerDocument,t);if(!(g.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||C.isXMLDoc(t)))for(a=mt(l),r=0,i=(o=mt(t)).length;r0&>(a,!f&&mt(t,"script")),l},cleanData:function(t){for(var e,n,r,i=C.event.special,o=0;void 0!==(n=t[o]);o++)if(G(n)){if(e=n[Y.expando]){if(e.events)for(r in e.events)i[r]?C.event.remove(n,r):C.removeEvent(n,r,e.handle);n[Y.expando]=void 0}n[Z.expando]&&(n[Z.expando]=void 0)}}}),C.fn.extend({detach:function(t){return Mt(this,t,!0)},remove:function(t){return Mt(this,t)},text:function(t){return z(this,function(t){return void 0===t?C.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return Ft(this,arguments,function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||It(this,t).appendChild(t)})},prepend:function(){return Ft(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=It(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return Ft(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return Ft(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(C.cleanData(mt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return C.clone(this,t,e)})},html:function(t){return z(this,function(t){var e=this[0]||{},n=0,r=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!jt.test(t)&&!vt[(dt.exec(t)||["",""])[1].toLowerCase()]){t=C.htmlPrefilter(t);try{for(;n=0&&(u+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-o-u-s-.5))),u}function te(t,e,n){var r=Bt(t),i=Ut(t,e,r),o="border-box"===C.css(t,"boxSizing",!1,r),a=o;if(qt.test(i)){if(!n)return i;i="auto"}return a=a&&(g.boxSizingReliable()||i===t.style[e]),("auto"===i||!parseFloat(i)&&"inline"===C.css(t,"display",!1,r))&&(i=t["offset"+e[0].toUpperCase()+e.slice(1)],a=!0),(i=parseFloat(i)||0)+Zt(t,e,n||(o?"border":"content"),a,r,i)+"px"}function ee(t,e,n,r,i){return new ee.prototype.init(t,e,n,r,i)}C.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=Ut(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(t,e,n,r){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var i,o,a,s=J(e),u=Vt.test(e),c=t.style;if(u||(e=Qt(s)),a=C.cssHooks[e]||C.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(t,!1,r))?i:c[e];"string"===(o=typeof n)&&(i=it.exec(n))&&i[1]&&(n=ut(t,e,i),o="number"),null!=n&&n==n&&("number"===o&&(n+=i&&i[3]||(C.cssNumber[s]?"":"px")),g.clearCloneStyle||""!==n||0!==e.indexOf("background")||(c[e]="inherit"),a&&"set"in a&&void 0===(n=a.set(t,n,r))||(u?c.setProperty(e,n):c[e]=n))}},css:function(t,e,n,r){var i,o,a,s=J(e);return Vt.test(e)||(e=Qt(s)),(a=C.cssHooks[e]||C.cssHooks[s])&&"get"in a&&(i=a.get(t,!0,n)),void 0===i&&(i=Ut(t,e,r)),"normal"===i&&e in Kt&&(i=Kt[e]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),C.each(["height","width"],function(t,e){C.cssHooks[e]={get:function(t,n,r){if(n)return!zt.test(C.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?te(t,e,r):st(t,Xt,function(){return te(t,e,r)})},set:function(t,n,r){var i,o=Bt(t),a="border-box"===C.css(t,"boxSizing",!1,o),s=r&&Zt(t,e,r,a,o);return a&&g.scrollboxSize()===o.position&&(s-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(o[e])-Zt(t,e,"border",!1,o)-.5)),s&&(i=it.exec(n))&&"px"!==(i[3]||"px")&&(t.style[e]=n,n=C.css(t,e)),Yt(0,n,s)}}}),C.cssHooks.marginLeft=Wt(g.reliableMarginLeft,function(t,e){if(e)return(parseFloat(Ut(t,"marginLeft"))||t.getBoundingClientRect().left-st(t,{marginLeft:0},function(){return t.getBoundingClientRect().left}))+"px"}),C.each({margin:"",padding:"",border:"Width"},function(t,e){C.cssHooks[t+e]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[t+ot[r]+e]=o[r]||o[r-2]||o[0];return i}},"margin"!==t&&(C.cssHooks[t+e].set=Yt)}),C.fn.extend({css:function(t,e){return z(this,function(t,e,n){var r,i,o={},a=0;if(Array.isArray(e)){for(r=Bt(t),i=e.length;a1)}}),C.Tween=ee,ee.prototype={constructor:ee,init:function(t,e,n,r,i,o){this.elem=t,this.prop=n,this.easing=i||C.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=r,this.unit=o||(C.cssNumber[n]?"":"px")},cur:function(){var t=ee.propHooks[this.prop];return t&&t.get?t.get(this):ee.propHooks._default.get(this)},run:function(t){var e,n=ee.propHooks[this.prop];return this.options.duration?this.pos=e=C.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):ee.propHooks._default.set(this),this}},ee.prototype.init.prototype=ee.prototype,ee.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=C.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){C.fx.step[t.prop]?C.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[C.cssProps[t.prop]]&&!C.cssHooks[t.prop]?t.elem[t.prop]=t.now:C.style(t.elem,t.prop,t.now+t.unit)}}},ee.propHooks.scrollTop=ee.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},C.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},C.fx=ee.prototype.init,C.fx.step={};var ne,re,ie=/^(?:toggle|show|hide)$/,oe=/queueHooks$/;function ae(){re&&(!1===a.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ae):n.setTimeout(ae,C.fx.interval),C.fx.tick())}function se(){return n.setTimeout(function(){ne=void 0}),ne=Date.now()}function ue(t,e){var n,r=0,i={height:t};for(e=e?1:0;r<4;r+=2-e)i["margin"+(n=ot[r])]=i["padding"+n]=t;return e&&(i.opacity=i.width=t),i}function ce(t,e,n){for(var r,i=(le.tweeners[e]||[]).concat(le.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(t){return this.each(function(){C.removeAttr(this,t)})}}),C.extend({attr:function(t,e,n){var r,i,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===t.getAttribute?C.prop(t,e,n):(1===o&&C.isXMLDoc(t)||(i=C.attrHooks[e.toLowerCase()]||(C.expr.match.bool.test(e)?fe:void 0)),void 0!==n?null===n?void C.removeAttr(t,e):i&&"set"in i&&void 0!==(r=i.set(t,n,e))?r:(t.setAttribute(e,n+""),n):i&&"get"in i&&null!==(r=i.get(t,e))?r:null==(r=C.find.attr(t,e))?void 0:r)},attrHooks:{type:{set:function(t,e){if(!g.radioValue&&"radio"===e&&O(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,r=0,i=e&&e.match(F);if(i&&1===t.nodeType)for(;n=i[r++];)t.removeAttribute(n)}}),fe={set:function(t,e,n){return!1===e?C.removeAttr(t,n):t.setAttribute(n,n),n}},C.each(C.expr.match.bool.source.match(/\w+/g),function(t,e){var n=pe[e]||C.find.attr;pe[e]=function(t,e,r){var i,o,a=e.toLowerCase();return r||(o=pe[a],pe[a]=i,i=null!=n(t,e,r)?a:null,pe[a]=o),i}});var de=/^(?:input|select|textarea|button)$/i,he=/^(?:a|area)$/i;function ve(t){return(t.match(F)||[]).join(" ")}function me(t){return t.getAttribute&&t.getAttribute("class")||""}function ge(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(F)||[]}C.fn.extend({prop:function(t,e){return z(this,C.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[C.propFix[t]||t]})}}),C.extend({prop:function(t,e,n){var r,i,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&C.isXMLDoc(t)||(e=C.propFix[e]||e,i=C.propHooks[e]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(t,n,e))?r:t[e]=n:i&&"get"in i&&null!==(r=i.get(t,e))?r:t[e]},propHooks:{tabIndex:{get:function(t){var e=C.find.attr(t,"tabindex");return e?parseInt(e,10):de.test(t.nodeName)||he.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),g.optSelected||(C.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),C.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){C.propFix[this.toLowerCase()]=this}),C.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(y(t))return this.each(function(e){C(this).addClass(t.call(this,e,me(this)))});if((e=ge(t)).length)for(;n=this[u++];)if(i=me(n),r=1===n.nodeType&&" "+ve(i)+" "){for(a=0;o=e[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=ve(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(y(t))return this.each(function(e){C(this).removeClass(t.call(this,e,me(this)))});if(!arguments.length)return this.attr("class","");if((e=ge(t)).length)for(;n=this[u++];)if(i=me(n),r=1===n.nodeType&&" "+ve(i)+" "){for(a=0;o=e[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(s=ve(r))&&n.setAttribute("class",s)}return this},toggleClass:function(t,e){var n=typeof t,r="string"===n||Array.isArray(t);return"boolean"==typeof e&&r?e?this.addClass(t):this.removeClass(t):y(t)?this.each(function(n){C(this).toggleClass(t.call(this,n,me(this),e),e)}):this.each(function(){var e,i,o,a;if(r)for(i=0,o=C(this),a=ge(t);e=a[i++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else void 0!==t&&"boolean"!==n||((e=me(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":Y.get(this,"__className__")||""))})},hasClass:function(t){var e,n,r=0;for(e=" "+t+" ";n=this[r++];)if(1===n.nodeType&&(" "+ve(me(n))+" ").indexOf(e)>-1)return!0;return!1}});var ye=/\r/g;C.fn.extend({val:function(t){var e,n,r,i=this[0];return arguments.length?(r=y(t),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?t.call(this,n,C(this).val()):t)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=C.map(i,function(t){return null==t?"":t+""})),(e=C.valHooks[this.type]||C.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,i,"value")||(this.value=i))})):i?(e=C.valHooks[i.type]||C.valHooks[i.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(ye,""):null==n?"":n:void 0}}),C.extend({valHooks:{option:{get:function(t){var e=C.find.attr(t,"value");return null!=e?e:ve(C.text(t))}},select:{get:function(t){var e,n,r,i=t.options,o=t.selectedIndex,a="select-one"===t.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(t.selectedIndex=-1),o}}}}),C.each(["radio","checkbox"],function(){C.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=C.inArray(C(t).val(),e)>-1}},g.checkOn||(C.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}),g.focusin="onfocusin"in n;var be=/^(?:focusinfocus|focusoutblur)$/,_e=function(t){t.stopPropagation()};C.extend(C.event,{trigger:function(t,e,r,i){var o,s,u,c,l,f,p,d,v=[r||a],m=h.call(t,"type")?t.type:t,g=h.call(t,"namespace")?t.namespace.split("."):[];if(s=d=u=r=r||a,3!==r.nodeType&&8!==r.nodeType&&!be.test(m+C.event.triggered)&&(m.indexOf(".")>-1&&(m=(g=m.split(".")).shift(),g.sort()),l=m.indexOf(":")<0&&"on"+m,(t=t[C.expando]?t:new C.Event(m,"object"==typeof t&&t)).isTrigger=i?2:3,t.namespace=g.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),e=null==e?[t]:C.makeArray(e,[t]),p=C.event.special[m]||{},i||!p.trigger||!1!==p.trigger.apply(r,e))){if(!i&&!p.noBubble&&!b(r)){for(c=p.delegateType||m,be.test(c+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(r.ownerDocument||a)&&v.push(u.defaultView||u.parentWindow||n)}for(o=0;(s=v[o++])&&!t.isPropagationStopped();)d=s,t.type=o>1?c:p.bindType||m,(f=(Y.get(s,"events")||{})[t.type]&&Y.get(s,"handle"))&&f.apply(s,e),(f=l&&s[l])&&f.apply&&G(s)&&(t.result=f.apply(s,e),!1===t.result&&t.preventDefault());return t.type=m,i||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(v.pop(),e)||!G(r)||l&&y(r[m])&&!b(r)&&((u=r[l])&&(r[l]=null),C.event.triggered=m,t.isPropagationStopped()&&d.addEventListener(m,_e),r[m](),t.isPropagationStopped()&&d.removeEventListener(m,_e),C.event.triggered=void 0,u&&(r[l]=u)),t.result}},simulate:function(t,e,n){var r=C.extend(new C.Event,n,{type:t,isSimulated:!0});C.event.trigger(r,null,e)}}),C.fn.extend({trigger:function(t,e){return this.each(function(){C.event.trigger(t,e,this)})},triggerHandler:function(t,e){var n=this[0];if(n)return C.event.trigger(t,e,n,!0)}}),g.focusin||C.each({focus:"focusin",blur:"focusout"},function(t,e){var n=function(t){C.event.simulate(e,t.target,C.event.fix(t))};C.event.special[e]={setup:function(){var r=this.ownerDocument||this,i=Y.access(r,e);i||r.addEventListener(t,n,!0),Y.access(r,e,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=Y.access(r,e)-1;i?Y.access(r,e,i):(r.removeEventListener(t,n,!0),Y.remove(r,e))}}});var we=n.location,xe=Date.now(),Ce=/\?/;C.parseXML=function(t){var e;if(!t||"string"!=typeof t)return null;try{e=(new n.DOMParser).parseFromString(t,"text/xml")}catch(t){e=void 0}return e&&!e.getElementsByTagName("parsererror").length||C.error("Invalid XML: "+t),e};var Te=/\[\]$/,ke=/\r?\n/g,$e=/^(?:submit|button|image|reset|file)$/i,Ae=/^(?:input|select|textarea|keygen)/i;function Se(t,e,n,r){var i;if(Array.isArray(e))C.each(e,function(e,i){n||Te.test(t)?r(t,i):Se(t+"["+("object"==typeof i&&null!=i?e:"")+"]",i,n,r)});else if(n||"object"!==x(e))r(t,e);else for(i in e)Se(t+"["+i+"]",e[i],n,r)}C.param=function(t,e){var n,r=[],i=function(t,e){var n=y(e)?e():e;r[r.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(t)||t.jquery&&!C.isPlainObject(t))C.each(t,function(){i(this.name,this.value)});else for(n in t)Se(n,t[n],e,i);return r.join("&")},C.fn.extend({serialize:function(){return C.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=C.prop(this,"elements");return t?C.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!C(this).is(":disabled")&&Ae.test(this.nodeName)&&!$e.test(t)&&(this.checked||!pt.test(t))}).map(function(t,e){var n=C(this).val();return null==n?null:Array.isArray(n)?C.map(n,function(t){return{name:e.name,value:t.replace(ke,"\r\n")}}):{name:e.name,value:n.replace(ke,"\r\n")}}).get()}});var Ee=/%20/g,Oe=/#.*$/,je=/([?&])_=[^&]*/,Ne=/^(.*?):[ \t]*([^\r\n]*)$/gm,De=/^(?:GET|HEAD)$/,Ie=/^\/\//,Le={},Re={},Pe="*/".concat("*"),Fe=a.createElement("a");function Me(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var r,i=0,o=e.toLowerCase().match(F)||[];if(y(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(t[r]=t[r]||[]).unshift(n)):(t[r]=t[r]||[]).push(n)}}function qe(t,e,n,r){var i={},o=t===Re;function a(s){var u;return i[s]=!0,C.each(t[s]||[],function(t,s){var c=s(e,n,r);return"string"!=typeof c||o||i[c]?o?!(u=c):void 0:(e.dataTypes.unshift(c),a(c),!1)}),u}return a(e.dataTypes[0])||!i["*"]&&a("*")}function Be(t,e){var n,r,i=C.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((i[n]?t:r||(r={}))[n]=e[n]);return r&&C.extend(!0,t,r),t}Fe.href=we.href,C.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:we.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(we.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Pe,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":C.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Be(Be(t,C.ajaxSettings),e):Be(C.ajaxSettings,t)},ajaxPrefilter:Me(Le),ajaxTransport:Me(Re),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var r,i,o,s,u,c,l,f,p,d,h=C.ajaxSetup({},e),v=h.context||h,m=h.context&&(v.nodeType||v.jquery)?C(v):C.event,g=C.Deferred(),y=C.Callbacks("once memory"),b=h.statusCode||{},_={},w={},x="canceled",T={readyState:0,getResponseHeader:function(t){var e;if(l){if(!s)for(s={};e=Ne.exec(o);)s[e[1].toLowerCase()]=e[2];e=s[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return l?o:null},setRequestHeader:function(t,e){return null==l&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,_[t]=e),this},overrideMimeType:function(t){return null==l&&(h.mimeType=t),this},statusCode:function(t){var e;if(t)if(l)T.always(t[T.status]);else for(e in t)b[e]=[b[e],t[e]];return this},abort:function(t){var e=t||x;return r&&r.abort(e),k(0,e),this}};if(g.promise(T),h.url=((t||h.url||we.href)+"").replace(Ie,we.protocol+"//"),h.type=e.method||e.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(F)||[""],null==h.crossDomain){c=a.createElement("a");try{c.href=h.url,c.href=c.href,h.crossDomain=Fe.protocol+"//"+Fe.host!=c.protocol+"//"+c.host}catch(t){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=C.param(h.data,h.traditional)),qe(Le,h,e,T),l)return T;for(p in(f=C.event&&h.global)&&0==C.active++&&C.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!De.test(h.type),i=h.url.replace(Oe,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(Ee,"+")):(d=h.url.slice(i.length),h.data&&(h.processData||"string"==typeof h.data)&&(i+=(Ce.test(i)?"&":"?")+h.data,delete h.data),!1===h.cache&&(i=i.replace(je,"$1"),d=(Ce.test(i)?"&":"?")+"_="+xe+++d),h.url=i+d),h.ifModified&&(C.lastModified[i]&&T.setRequestHeader("If-Modified-Since",C.lastModified[i]),C.etag[i]&&T.setRequestHeader("If-None-Match",C.etag[i])),(h.data&&h.hasContent&&!1!==h.contentType||e.contentType)&&T.setRequestHeader("Content-Type",h.contentType),T.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+Pe+"; q=0.01":""):h.accepts["*"]),h.headers)T.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(v,T,h)||l))return T.abort();if(x="abort",y.add(h.complete),T.done(h.success),T.fail(h.error),r=qe(Re,h,e,T)){if(T.readyState=1,f&&m.trigger("ajaxSend",[T,h]),l)return T;h.async&&h.timeout>0&&(u=n.setTimeout(function(){T.abort("timeout")},h.timeout));try{l=!1,r.send(_,k)}catch(t){if(l)throw t;k(-1,t)}}else k(-1,"No Transport");function k(t,e,a,s){var c,p,d,_,w,x=e;l||(l=!0,u&&n.clearTimeout(u),r=void 0,o=s||"",T.readyState=t>0?4:0,c=t>=200&&t<300||304===t,a&&(_=function(t,e,n){for(var r,i,o,a,s=t.contents,u=t.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=t.mimeType||e.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||t.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(h,T,a)),_=function(t,e,n,r){var i,o,a,s,u,c={},l=t.dataTypes.slice();if(l[1])for(a in t.converters)c[a.toLowerCase()]=t.converters[a];for(o=l.shift();o;)if(t.responseFields[o]&&(n[t.responseFields[o]]=e),!u&&r&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),u=o,o=l.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=c[u+" "+o]||c["* "+o]))for(i in c)if((s=i.split(" "))[1]===o&&(a=c[u+" "+s[0]]||c["* "+s[0]])){!0===a?a=c[i]:!0!==c[i]&&(o=s[0],l.unshift(s[1]));break}if(!0!==a)if(a&&t.throws)e=a(e);else try{e=a(e)}catch(t){return{state:"parsererror",error:a?t:"No conversion from "+u+" to "+o}}}return{state:"success",data:e}}(h,_,T,c),c?(h.ifModified&&((w=T.getResponseHeader("Last-Modified"))&&(C.lastModified[i]=w),(w=T.getResponseHeader("etag"))&&(C.etag[i]=w)),204===t||"HEAD"===h.type?x="nocontent":304===t?x="notmodified":(x=_.state,p=_.data,c=!(d=_.error))):(d=x,!t&&x||(x="error",t<0&&(t=0))),T.status=t,T.statusText=(e||x)+"",c?g.resolveWith(v,[p,x,T]):g.rejectWith(v,[T,x,d]),T.statusCode(b),b=void 0,f&&m.trigger(c?"ajaxSuccess":"ajaxError",[T,h,c?p:d]),y.fireWith(v,[T,x]),f&&(m.trigger("ajaxComplete",[T,h]),--C.active||C.event.trigger("ajaxStop")))}return T},getJSON:function(t,e,n){return C.get(t,e,n,"json")},getScript:function(t,e){return C.get(t,void 0,e,"script")}}),C.each(["get","post"],function(t,e){C[e]=function(t,n,r,i){return y(n)&&(i=i||r,r=n,n=void 0),C.ajax(C.extend({url:t,type:e,dataType:i,data:n,success:r},C.isPlainObject(t)&&t))}}),C._evalUrl=function(t){return C.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},C.fn.extend({wrapAll:function(t){var e;return this[0]&&(y(t)&&(t=t.call(this[0])),e=C(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this},wrapInner:function(t){return y(t)?this.each(function(e){C(this).wrapInner(t.call(this,e))}):this.each(function(){var e=C(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=y(t);return this.each(function(n){C(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(t){return this.parent(t).not("body").each(function(){C(this).replaceWith(this.childNodes)}),this}}),C.expr.pseudos.hidden=function(t){return!C.expr.pseudos.visible(t)},C.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},C.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(t){}};var He={0:200,1223:204},Ue=C.ajaxSettings.xhr();g.cors=!!Ue&&"withCredentials"in Ue,g.ajax=Ue=!!Ue,C.ajaxTransport(function(t){var e,r;if(g.cors||Ue&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];for(a in t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)s.setRequestHeader(a,i[a]);e=function(t){return function(){e&&(e=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===t?s.abort():"error"===t?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(He[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=e(),r=s.onerror=s.ontimeout=e("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){e&&r()})},e=e("abort");try{s.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}}),C.ajaxPrefilter(function(t){t.crossDomain&&(t.contents.script=!1)}),C.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return C.globalEval(t),t}}}),C.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),C.ajaxTransport("script",function(t){var e,n;if(t.crossDomain)return{send:function(r,i){e=C(" + + {# this entry is in the header so it's loaded early #} + {# SHA256: C45493A8175B10AC47EEDFC7C20AC31FAE5C804FB6C4F75468DB0F95112664BF #} + {# favicons #} {% include('partials.favicons') %} @@ -54,7 +66,7 @@
    - + +{# Java libraries and stuff: #} + +{# Moment JS #} + +{# All kinds of variables. #} + +{# big fat JS thing courtesy of Vue#} +{# date range picker, current template, etc.#} - + +{# Firefly III code#} - {% if not shownDemo %}