From dd17a20b6083703b506cb5907c44731337b551d4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 4 May 2017 17:30:12 +0200 Subject: [PATCH 001/103] New translations firefly.php (Slovenian) --- resources/lang/sl_SI/firefly.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/lang/sl_SI/firefly.php b/resources/lang/sl_SI/firefly.php index 3f7e800a62..0b4ebb7269 100644 --- a/resources/lang/sl_SI/firefly.php +++ b/resources/lang/sl_SI/firefly.php @@ -11,21 +11,21 @@ return [ // general stuff: - 'language_incomplete' => 'incomplete translation', - 'close' => 'Close', + 'language_incomplete' => 'nepopoln prevod', + 'close' => 'zapri', 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'welcomeBack' => 'What\'s playing?', - 'everything' => 'Everything', - 'customRange' => 'Custom range', - 'apply' => 'Apply', - 'cancel' => 'Cancel', + 'edit' => 'uredi', + 'delete' => 'izbriši', + 'welcomeBack' => 'Kaj dogaja?', + 'everything' => 'vse', + 'customRange' => 'obseg po meri', + 'apply' => 'uporabi', + 'cancel' => 'prekliči', 'from' => 'From', 'to' => 'To', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'search_results_for' => 'Search results for ":query"', + 'showEverything' => 'pokaži vse', + 'never' => 'nikoli', + 'search_results_for' => 'rezultati iskanja za ":query"', 'advanced_search' => 'Advanced search', 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', 'bounced_error' => 'The message sent to :email bounced, so no access for you.', From 9856a1831a3513526b8b33c484e5120742e75ec1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 4 May 2017 17:40:13 +0200 Subject: [PATCH 002/103] New translations firefly.php (Slovenian) --- resources/lang/sl_SI/firefly.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/lang/sl_SI/firefly.php b/resources/lang/sl_SI/firefly.php index 0b4ebb7269..8358bb5d84 100644 --- a/resources/lang/sl_SI/firefly.php +++ b/resources/lang/sl_SI/firefly.php @@ -26,7 +26,7 @@ return [ 'showEverything' => 'pokaži vse', 'never' => 'nikoli', 'search_results_for' => 'rezultati iskanja za ":query"', - 'advanced_search' => 'Advanced search', + 'advanced_search' => 'napredno iskanje', 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', 'bounced_error' => 'The message sent to :email bounced, so no access for you.', 'deleted_error' => 'These credentials do not match our records.', @@ -38,13 +38,13 @@ return [ 'Opening balance' => 'Opening balance', 'create_new_stuff' => 'Create new stuff', 'new_withdrawal' => 'New withdrawal', - 'new_deposit' => 'New deposit', - 'new_transfer' => 'New transfer', + 'new_deposit' => 'nov depozit', + 'new_transfer' => 'nov prenos', 'new_asset_account' => 'nov premoženjski račun', 'new_expense_account' => 'nov konto za stroške', - 'new_revenue_account' => 'New revenue account', - 'new_budget' => 'New budget', - 'new_bill' => 'New bill', + 'new_revenue_account' => 'nov konto za dohodke', + 'new_budget' => 'nov budžet', + 'new_bill' => 'nov trajnik', 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', 'flash_success' => 'Success!', 'flash_info' => 'Message', From 7f082ea38914370a711d8f7b08f544033aef1fbb Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 6 May 2017 10:20:27 +0200 Subject: [PATCH 003/103] Remove focus thing --- public/js/ff/transactions/single/common.js | 2 +- public/js/ff/transactions/single/create.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/ff/transactions/single/common.js b/public/js/ff/transactions/single/common.js index d98ac6afce..0fd23dd6ac 100644 --- a/public/js/ff/transactions/single/common.js +++ b/public/js/ff/transactions/single/common.js @@ -158,7 +158,7 @@ function validateCurrencyForTransfer() { $('#source_amount_holder').show().find('.non-selectable-currency-symbol').text(sourceSymbol); if (sourceCurrency === destinationCurrency) { - console.log('Both accounts accept ' + sourceCurrency); + console.log('Both accounts accept #' + sourceCurrency); $('#destination_amount_holder').hide(); $('#amount_holder').hide(); return; diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index 61806b786f..56109f855c 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -68,7 +68,7 @@ function updateNativeCurrency() { $('.currency-option[data-id="' + nativeCurrencyId + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); - $('select[name="source_account_id"]').focus(); + //$('select[name="source_account_id"]').focus(); validateCurrencyForTransfer(); } From 5fb73bdb01fabd0557d770f8f2dff898df58a1f1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 7 May 2017 19:24:53 +0200 Subject: [PATCH 004/103] Fix bug #642 --- app/Http/Controllers/TransactionController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 0dedf135d1..1e838c5dec 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -119,7 +119,7 @@ class TransactionController extends Controller $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount(); $collector->removeFilter(InternalTransferFilter::class); $journals = $collector->getPaginatedJournals(); - $journals->setPath('/budgets/list/no-budget'); + $journals->setPath('/transactions/' . $what); $count = $journals->getCollection()->count(); if ($count === 0) { $start->subDay(); @@ -179,10 +179,10 @@ class TransactionController extends Controller return $this->redirectToAccount($journal); } - $events = $tasker->getPiggyBankEvents($journal); - $transactions = $tasker->getTransactionsOverview($journal); - $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); - $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; + $events = $tasker->getPiggyBankEvents($journal); + $transactions = $tasker->getTransactionsOverview($journal); + $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; $foreignCurrency = null; if ($journal->hasMeta('foreign_currency_id')) { From 17fb6983d89a501c9ef7851fc2210d0ecabbd0ee Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 7 May 2017 19:45:40 +0200 Subject: [PATCH 005/103] Fix issue #637 with cash accounts. --- .../Transaction/ConvertController.php | 14 ++++++++++ .../Account/AccountRepository.php | 13 +++++++++ .../Account/AccountRepositoryInterface.php | 5 ++++ resources/views/transactions/convert.twig | 28 +++++++++++++++---- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 72e4209a51..8853a3dbaa 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -182,6 +182,12 @@ class ConvertController extends Controller break; case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five + if ($data['destination_account_expense'] === '') { + // destination is a cash account. + $destination = $accountRepository->getCashAccount(); + + return $destination; + } $data = [ 'name' => $data['destination_account_expense'], 'accountType' => 'expense', @@ -221,6 +227,14 @@ class ConvertController extends Controller throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six + + if ($data['source_account_revenue'] === '') { + // destination is a cash account. + $destination = $accountRepository->getCashAccount(); + + return $destination; + } + $data = [ 'name' => $data['source_account_revenue'], 'accountType' => 'revenue', diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index a9e74aa9ef..423efcd23e 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -250,6 +250,19 @@ class AccountRepository implements AccountRepositoryInterface return $result; } + /** + * @return Account + */ + public function getCashAccount(): Account + { + $type = AccountType::where('type', AccountType::CASH)->first(); + $account = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1] + ); + + return $account; + } + /** * Returns the date of the very last transaction in this account. * diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index f51c398f0b..e444fcada0 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -36,6 +36,11 @@ interface AccountRepositoryInterface */ public function count(array $types): int; + /** + * @return Account + */ + public function getCashAccount(): Account; + /** * Moved here from account CRUD. * diff --git a/resources/views/transactions/convert.twig b/resources/views/transactions/convert.twig index ca5203c47f..33f5c08daf 100644 --- a/resources/views/transactions/convert.twig +++ b/resources/views/transactions/convert.twig @@ -23,12 +23,23 @@ {# in case of withdrawal #} {% if sourceType.type == "Withdrawal" %} {{ ExpandedForm.staticText('source_account_asset', ''~sourceAccount.name~'') }} - {{ ExpandedForm.staticText('destination_account_expense', ''~destinationAccount.name~'') }} + {# if destination is cash, show (cash) #} + {% if destinationAccount.accountType.type == "Cash account" %} + {{ ExpandedForm.staticText('destination_account_expense', '(cash)') }} + {% else %} + {{ ExpandedForm.staticText('destination_account_expense', ''~destinationAccount.name~'') }} + {% endif %} + {% endif %} {# in case of deposit #} {% if sourceType.type == "Deposit" %} - {{ ExpandedForm.staticText('source_account_revenue', ''~sourceAccount.name~'') }} + {# if source is cash, show (cash) #} + {% if sourceAccount.accountType.type == "Cash account" %} + {{ ExpandedForm.staticText('source_account_revenue', '(cash)') }} + {% else %} + {{ ExpandedForm.staticText('source_account_revenue', ''~sourceAccount.name~'') }} + {% endif %} {{ ExpandedForm.staticText('destination_account_asset', ''~destinationAccount.name~'') }} {% endif %} @@ -55,8 +66,11 @@ {{ 'convert_please_set_revenue_source'|_ }}

- - {{ ExpandedForm.text('source_account_revenue', destinationAccount.name) }} + {% if destinationAccount.accountType.type == "Cash account" %} + {{ ExpandedForm.text('source_account_revenue', '') }} + {% else %} + {{ ExpandedForm.text('source_account_revenue', destinationAccount.name) }} + {% endif %} {% endif %} {# TWO #} @@ -103,7 +117,11 @@

- {{ ExpandedForm.text('destination_account_expense', destinationAccount.name) }} + {% if sourceAccount.accountType.type == "Cash account" %} + {{ ExpandedForm.text('destination_account_expense', '') }} + {% else %} + {{ ExpandedForm.text('destination_account_expense', destinationAccount.name) }} + {% endif %} {% endif %} From 0c44fe6ce0d5c3f29e413b059a5a3df083416890 Mon Sep 17 00:00:00 2001 From: Enrico Lamperti Date: Sun, 7 May 2017 21:24:07 -0300 Subject: [PATCH 006/103] Add translations for Spanish language --- resources/lang/es_ES/auth.php | 28 +++++++++ resources/lang/es_ES/breadcrumbs.php | 41 +++++++++++++ resources/lang/es_ES/csv.php | 80 ++++++++++++++++++++++++ resources/lang/es_ES/demo.php | 24 ++++++++ resources/lang/es_ES/passwords.php | 19 ++++++ resources/lang/es_ES/validation.php | 91 ++++++++++++++++++++++++++++ 6 files changed, 283 insertions(+) create mode 100644 resources/lang/es_ES/auth.php create mode 100644 resources/lang/es_ES/breadcrumbs.php create mode 100644 resources/lang/es_ES/csv.php create mode 100644 resources/lang/es_ES/demo.php create mode 100644 resources/lang/es_ES/passwords.php create mode 100644 resources/lang/es_ES/validation.php diff --git a/resources/lang/es_ES/auth.php b/resources/lang/es_ES/auth.php new file mode 100644 index 0000000000..45d765e4fa --- /dev/null +++ b/resources/lang/es_ES/auth.php @@ -0,0 +1,28 @@ + 'Las credenciales no coinciden con los registros.', + 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor reintente en :seconds segundos.', + +]; diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php new file mode 100644 index 0000000000..211e6a3037 --- /dev/null +++ b/resources/lang/es_ES/breadcrumbs.php @@ -0,0 +1,41 @@ + 'Inicio', + 'edit_currency' => 'Editar moneda ":name"', + 'delete_currency' => 'Eliminar moneda ":name"', + 'newPiggyBank' => 'Crear nueva alcancía', + 'edit_piggyBank' => 'Editar alcancía ":name"', + 'preferences' => 'Preferencias', + 'profile' => 'Perfil', + 'changePassword' => 'Cambiar contraseña', + 'bills' => 'Facturas', + 'newBill' => 'Nueva factura', + 'edit_bill' => 'Editar factura ":name"', + 'delete_bill' => 'Eliminar factura ":name"', + 'reports' => 'Reportes', + 'searchResult' => 'Buscar ":query"', + 'withdrawal_list' => 'Expensas', + 'deposit_list' => 'Ganancia, ingresos y depósitos', + 'transfer_list' => 'Transferencias', + 'transfers_list' => 'Transferencias', + 'create_withdrawal' => 'Crear nueva extracción', + 'create_deposit' => 'Crear nuevo depósito', + 'create_transfer' => 'Crear nueva transferencia', + 'edit_journal' => 'Editar transacción ":description"', + 'delete_journal' => 'Eliminar transacción ":description"', + 'tags' => 'Etiquetas', + 'createTag' => 'Crear nueva etiqueta', + 'edit_tag' => 'Editar etiqueta ":tag"', + 'delete_tag' => 'Eliminar etiqueta ":tag"', +]; diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php new file mode 100644 index 0000000000..47b611f81a --- /dev/null +++ b/resources/lang/es_ES/csv.php @@ -0,0 +1,80 @@ + 'Configurar su importación', + 'import_configure_intro' => 'Hay algunas opciones para su importación desde CSV. Por facor indique si su CSV contiene encabezados en la primera fila, y cuál es el formato de fecha utilizado. ¡Puede requerir un poco de experimentación! El delimitador de campos es usualmente ",", pero también puede ser ";". Verifíquelo cuidadosamente.', + 'import_configure_form' => 'Opciones básicas de importación desde CSV', + 'header_help' => 'Marque aquí si el CSV contiene títulos de columna en la primera fila', + 'date_help' => 'Formato de fecha y hora en el CSV. Siga el formato que esta página indica. El valor por defecto interpretará fechas que se vean así: :dateExample.', + 'delimiter_help' => 'Elija el delimitador de campos del archivo de entrada. Si no está seguro, la coma es la opción más segura.', + 'import_account_help' => 'Si el archivo NO contiene información sobre su(s) caja(s) de ahorros seleccion una opción para definir a qué cuenta pertenecen las transacciones del CSV.', + 'upload_not_writeable' => 'El texto en gris indica un directorio. Debe tener permiso de escritura. Por favor verifíquelo.', + + // roles + 'column_roles_title' => 'Definir roles de las columnas', + 'column_roles_table' => 'Tabla', + 'column_name' => 'Nombre de la columna', + 'column_example' => 'Ejemplo de datos de columna', + 'column_role' => 'Significado de los datos de la columna', + 'do_map_value' => 'Mapear estos valores', + 'column' => 'Columna', + 'no_example_data' => 'No hay datos de ejemplo disponibles', + 'store_column_roles' => 'Continuar importación', + 'do_not_map' => '(no mapear)', + 'map_title' => 'Conectar datos de importación con datos de Firefly-III', + 'map_text' => 'En las siguientes tablas el valor de la izquierda muestra información encontrada en el CSV cargado. Es su tarea mapear este valor, si es posible, a un valor ya presente en su base de datos. Firefly respeterá este mapeo. Si no hay un valor hacia el cual mapear o no desea mapear un valor específico, no seleccione ninguno.', + + 'field_value' => 'Valor del campo', + 'field_mapped_to' => 'Mapeado a', + 'store_column_mapping' => 'Guardar mapeo', + + // map things. + + + 'column__ignore' => '(ignorar esta columna)', + 'column_account-iban' => 'Caja de ahorro (CBU)', + 'column_account-id' => 'ID de la caja de ahorro (coincide con Firefly)', + 'column_account-name' => 'Caja de ahorro (nombre)', + 'column_amount' => 'Monto', + 'column_amount-comma-separated' => 'Monto (coma como separador de decimales)', + 'column_bill-id' => 'ID de factura (coincide con Firefly)', + 'column_bill-name' => 'Nombre de factura', + 'column_budget-id' => 'ID de presupuesto (coincide con Firefly)', + 'column_budget-name' => 'Nombre de presupuesto', + 'column_category-id' => 'ID de categoría (coincide con Firefly)', + 'column_category-name' => 'Nombre de categoría', + 'column_currency-code' => 'Código de moneda (ISO 4217)', + 'column_currency-id' => 'ID de moneda (coincide con Firefly)', + 'column_currency-name' => 'Nombre de moneda (coincide con Firefly)', + 'column_currency-symbol' => 'Símbolo de moneda (coincide con Firefly)', + 'column_date-interest' => 'Fecha de cálculo de intereses', + 'column_date-book' => 'Fecha de registro de transacción', + 'column_date-process' => 'Fecha de proceso de transacción', + 'column_date-transaction' => 'Fecha', + 'column_description' => 'Descripción', + 'column_opposing-iban' => 'Cuenta opuesta (CBU)', + 'column_opposing-id' => 'ID de cuenta opuesta (coincide con Firefly)', + 'column_external-id' => 'ID externo', + 'column_opposing-name' => 'Cuenta opuesta (nombre)', + 'column_rabo-debet-credit' => 'Indicador de débito/crédito específico de Rabobank', + 'column_ing-debet-credit' => 'Indicador de débito/crédito específico de ING', + 'column_sepa-ct-id' => 'ID de transferencia de crédito end-to-end de SEPA', + 'column_sepa-ct-op' => 'Transferencia de crédito a cuenta opuesta SEPA', + 'column_sepa-db' => 'Débito directo SEPA', + 'column_tags-comma' => 'Etiquetas (separadas por comas)', + 'column_tags-space' => 'Etiquetas (separadas por espacios)', + 'column_account-number' => 'Caja de ahorro (número de cuenta)', + 'column_opposing-number' => 'Cuenta opuesta (número de cuenta)', +]; diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php new file mode 100644 index 0000000000..e2d7695439 --- /dev/null +++ b/resources/lang/es_ES/demo.php @@ -0,0 +1,24 @@ + 'Lamentablemente no hay textos de ayuda para esta página.', + 'see_help_icon' => 'Sin embargo, el ícono en la esquina superior-derecha puede tener más información.', + 'index' => '¡Bienvenido a Firefly III! En esta página tendrá una vista rápida de sus finanzas. Para más información, mire sus cuentas rarr; Asset Accounts y, claro, las páginas de presupuestos y reportes. O simplemente investigue la aplicación por su cuenta.', + 'accounts-index' => 'Las cajas de ahorro son sus cuentas de banco personales. Las cuentas de gastos contienen sus gastos habituales como compras y salidas con amigos. Las cuentas de ingresos repesentan ingresos de su trabajo u otras fuentes. En esta página puede editarlas o eliminarlas.', + 'budgets-index' => 'This page shows you an overview of your budgets. The top bar shows the amount that is available to be budgeted. This can be customized for any period by clicking the amount on the right. The amount you\'ve actually spent is shown in the bar below. Below that are the expenses per budget and what you\'ve budgeted for them.', + 'reports-index-start' => 'Firefly III supports four types of reports. Read about them by clicking on the -icon in the top right corner.', + 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', + 'currencies-index' => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', + 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', + 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', + 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', + 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', + 'import-configure-configuration' => 'The configuration you see below is correct for the local file.', +]; diff --git a/resources/lang/es_ES/passwords.php b/resources/lang/es_ES/passwords.php new file mode 100644 index 0000000000..1dfc1a738c --- /dev/null +++ b/resources/lang/es_ES/passwords.php @@ -0,0 +1,19 @@ + 'Las contraseñas deben tener al menos seis caracteres y coincidir entre sí.', + 'user' => 'No podemos encontrar un usuario con esa dirección de correo electrónico.', + 'token' => 'Este token para reestablecer la contraseña no es válido.', + 'sent' => 'Te enviamos un correo con el link para reestablecer tu contraseña!', + 'reset' => 'Tu contraseña fue reestablecida!', + 'blocked' => 'Buen intento.', +]; diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php new file mode 100644 index 0000000000..e5ef49397a --- /dev/null +++ b/resources/lang/es_ES/validation.php @@ -0,0 +1,91 @@ + 'This is not a valid CBU.', + 'unique_account_number_for_user' => 'It looks like this account number is already in use.', + 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', + 'rule_trigger_value' => 'This value is invalid for the selected trigger.', + 'rule_action_value' => 'This value is invalid for the selected action.', + 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', + 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', + 'file_attached' => 'Succesfully uploaded file ":name".', + 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', + 'file_too_large' => 'File ":name" is too large.', + 'belongs_to_user' => 'The value of :attribute is unknown', + 'accepted' => 'The :attribute must be accepted.', + 'bic' => 'This is not a valid BIC.', + 'more' => ':attribute must be larger than zero.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'unique_for_user' => 'There already is an entry with this :attribute.', + 'before' => 'The :attribute must be a date before :date.', + 'unique_object_for_user' => 'This name is already in use', + 'unique_account_for_user' => 'This account name is already in use', + 'between.numeric' => 'The :attribute must be between :min and :max.', + 'between.file' => 'The :attribute must be between :min and :max kilobytes.', + 'between.string' => 'The :attribute must be between :min and :max characters.', + 'between.array' => 'The :attribute must have between :min and :max items.', + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'email' => 'The :attribute must be a valid email address.', + 'filled' => 'The :attribute field is required.', + 'exists' => 'The selected :attribute is invalid.', + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max.numeric' => 'The :attribute may not be greater than :max.', + 'max.file' => 'The :attribute may not be greater than :max kilobytes.', + 'max.string' => 'The :attribute may not be greater than :max characters.', + 'max.array' => 'The :attribute may not have more than :max items.', + 'mimes' => 'The :attribute must be a file of type: :values.', + 'min.numeric' => 'The :attribute must be at least :min.', + 'min.file' => 'The :attribute must be at least :min kilobytes.', + 'min.string' => 'The :attribute must be at least :min characters.', + 'min.array' => 'The :attribute must have at least :min items.', + 'not_in' => 'The selected :attribute is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size.numeric' => 'The :attribute must be :size.', + 'size.file' => 'The :attribute must be :size kilobytes.', + 'size.string' => 'The :attribute must be :size characters.', + 'size.array' => 'The :attribute must contain :size items.', + 'unique' => 'The :attribute has already been taken.', + 'string' => 'The :attribute must be a string.', + 'url' => 'The :attribute format is invalid.', + 'timezone' => 'The :attribute must be a valid zone.', + '2fa_code' => 'The :attribute field is invalid.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'file' => 'The :attribute must be a file.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'present' => 'The :attribute field must be present.', + 'amount_zero' => 'The total amount cannot be zero', +]; From 0c4539e4facdc9484042b9df64d6121bd753216c Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 10 May 2017 20:40:31 +0200 Subject: [PATCH 007/103] New translations validation.php (Spanish) --- resources/lang/es_ES/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index addd0e6f39..843a9438eb 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -10,7 +10,7 @@ */ return [ - 'iban' => 'This is not a valid IBAN.', + 'iban' => 'This is not a valid CBU.', 'unique_account_number_for_user' => 'It looks like this account number is already in use.', 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', 'rule_trigger_value' => 'This value is invalid for the selected trigger.', From 1893d1a2c20e46c14a563402f8c5e7e856c4a3e6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 10 May 2017 20:40:37 +0200 Subject: [PATCH 008/103] New translations demo.php (Spanish) --- resources/lang/es_ES/demo.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index e7f8ea934d..ed19589949 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -8,10 +8,10 @@ */ return [ - 'no_demo_text' => 'Sorry, there is no extra demo-explanation text for this page.', - 'see_help_icon' => 'However, the -icon in the top right corner may tell you more.', - 'index' => 'Welcome to Firefly III! On this page you get a quick overview of your finances. For more information, check out Accounts → Asset Accounts and of course the Budgets and Reports pages. Or just take a look around and see where you end up.', - 'accounts-index' => 'Asset accounts are your personal bank accounts. Expense accounts are the accounts you spend money at, such as stores and friends. Revenue accounts are accounts you receive money from, such as your job, the government or other sources of income. On this page you can edit or remove them.', + 'no_demo_text' => 'Lamentablemente no hay textos de ayuda para esta página.', + 'see_help_icon' => 'Sin embargo, el ícono en la esquina superior-derecha puede tener más información.', + 'index' => '¡Bienvenido a Firefly III! En esta página tendrá una vista rápida de sus finanzas. Para más información, mire sus cuentas rarr; Asset Accounts y, claro, las páginas de presupuestos y reportes. O simplemente investigue la aplicación por su cuenta.', + 'accounts-index' => 'Las cajas de ahorro son sus cuentas de banco personales. Las cuentas de gastos contienen sus gastos habituales como compras y salidas con amigos. Las cuentas de ingresos repesentan ingresos de su trabajo u otras fuentes. En esta página puede editarlas o eliminarlas.', 'budgets-index' => 'This page shows you an overview of your budgets. The top bar shows the amount that is available to be budgeted. This can be customized for any period by clicking the amount on the right. The amount you\'ve actually spent is shown in the bar below. Below that are the expenses per budget and what you\'ve budgeted for them.', 'reports-index-start' => 'Firefly III supports four types of reports. Read about them by clicking on the -icon in the top right corner.', 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', From d449c35025397bf3be179c39e53d5f02ac53b093 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 10 May 2017 20:40:39 +0200 Subject: [PATCH 009/103] New translations passwords.php (Spanish) --- resources/lang/es_ES/passwords.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/lang/es_ES/passwords.php b/resources/lang/es_ES/passwords.php index 2e11aa92dc..f61f371a0d 100644 --- a/resources/lang/es_ES/passwords.php +++ b/resources/lang/es_ES/passwords.php @@ -10,10 +10,10 @@ */ return [ - 'password' => 'Passwords must be at least six characters and match the confirmation.', - 'user' => 'We can\'t find a user with that e-mail address.', - 'token' => 'This password reset token is invalid.', - 'sent' => 'We have e-mailed your password reset link!', - 'reset' => 'Your password has been reset!', - 'blocked' => 'Nice try though.', + 'password' => 'Las contraseñas deben tener al menos seis caracteres y coincidir entre sí.', + 'user' => 'No podemos encontrar un usuario con esa dirección de correo electrónico.', + 'token' => 'Este token para reestablecer la contraseña no es válido.', + 'sent' => 'Te enviamos un correo con el link para reestablecer tu contraseña!', + 'reset' => 'Tu contraseña fue reestablecida!', + 'blocked' => 'Buen intento.', ]; \ No newline at end of file From 0009f1f8656a1ca84ffb5ae3fd3e8e8cc91330e3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 10 May 2017 20:40:41 +0200 Subject: [PATCH 010/103] New translations csv.php (Spanish) --- resources/lang/es_ES/csv.php | 114 +++++++++++++++++------------------ 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php index 4424610191..27143be1bc 100644 --- a/resources/lang/es_ES/csv.php +++ b/resources/lang/es_ES/csv.php @@ -13,68 +13,68 @@ declare(strict_types=1); return [ - 'import_configure_title' => 'Configure your import', - 'import_configure_intro' => 'There are some options for your CSV import. Please indicate if your CSV file contains headers on the first column, and what the date format of your date-fields is. That might require some experimentation. The field delimiter is usually a ",", but could also be a ";". Check this carefully.', - 'import_configure_form' => 'Basic CSV import options', - 'header_help' => 'Check this if the first row of your CSV file are the column titles', - 'date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', + 'import_configure_title' => 'Configurar su importación', + 'import_configure_intro' => 'Hay algunas opciones para su importación desde CSV. Por facor indique si su CSV contiene encabezados en la primera fila, y cuál es el formato de fecha utilizado. ¡Puede requerir un poco de experimentación! El delimitador de campos es usualmente ",", pero también puede ser ";". Verifíquelo cuidadosamente.', + 'import_configure_form' => 'Opciones básicas de importación desde CSV', + 'header_help' => 'Marque aquí si el CSV contiene títulos de columna en la primera fila', + 'date_help' => 'Formato de fecha y hora en el CSV. Siga el formato que esta página indica. El valor por defecto interpretará fechas que se vean así: :dateExample.', + 'delimiter_help' => 'Elija el delimitador de campos del archivo de entrada. Si no está seguro, la coma es la opción más segura.', + 'import_account_help' => 'Si el archivo NO contiene información sobre su(s) caja(s) de ahorros seleccion una opción para definir a qué cuenta pertenecen las transacciones del CSV.', + 'upload_not_writeable' => 'El texto en gris indica un directorio. Debe tener permiso de escritura. Por favor verifíquelo.', // roles - 'column_roles_title' => 'Define column roles', - 'column_roles_table' => 'Table', - 'column_name' => 'Name of column', - 'column_example' => 'Column example data', - 'column_role' => 'Column data meaning', - 'do_map_value' => 'Map these values', - 'column' => 'Column', - 'no_example_data' => 'No example data available', - 'store_column_roles' => 'Continue import', - 'do_not_map' => '(do not map)', - 'map_title' => 'Connect import data to Firefly III data', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV 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.', + 'column_roles_title' => 'Definir roles de las columnas', + 'column_roles_table' => 'Tabla', + 'column_name' => 'Nombre de la columna', + 'column_example' => 'Ejemplo de datos de columna', + 'column_role' => 'Significado de los datos de la columna', + 'do_map_value' => 'Mapear estos valores', + 'column' => 'Columna', + 'no_example_data' => 'No hay datos de ejemplo disponibles', + 'store_column_roles' => 'Continuar importación', + 'do_not_map' => '(no mapear)', + 'map_title' => 'Conectar datos de importación con datos de Firefly-III', + 'map_text' => 'En las siguientes tablas el valor de la izquierda muestra información encontrada en el CSV cargado. Es su tarea mapear este valor, si es posible, a un valor ya presente en su base de datos. Firefly respeterá este mapeo. Si no hay un valor hacia el cual mapear o no desea mapear un valor específico, no seleccione ninguno.', - 'field_value' => 'Field value', - 'field_mapped_to' => 'Mapped to', - 'store_column_mapping' => 'Store mapping', + 'field_value' => 'Valor del campo', + 'field_mapped_to' => 'Mapeado a', + 'store_column_mapping' => 'Guardar mapeo', // map things. - 'column__ignore' => '(ignore this column)', - 'column_account-iban' => 'Asset account (IBAN)', - 'column_account-id' => 'Asset account ID (matching Firefly)', - 'column_account-name' => 'Asset account (name)', - 'column_amount' => 'Amount', - 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', - 'column_bill-id' => 'Bill ID (matching Firefly)', - 'column_bill-name' => 'Bill name', - 'column_budget-id' => 'Budget ID (matching Firefly)', - 'column_budget-name' => 'Budget name', - 'column_category-id' => 'Category ID (matching Firefly)', - 'column_category-name' => 'Category name', - 'column_currency-code' => 'Currency code (ISO 4217)', - 'column_currency-id' => 'Currency ID (matching Firefly)', - 'column_currency-name' => 'Currency name (matching Firefly)', - 'column_currency-symbol' => 'Currency symbol (matching Firefly)', - 'column_date-interest' => 'Interest calculation date', - 'column_date-book' => 'Transaction booking date', - 'column_date-process' => 'Transaction process date', - 'column_date-transaction' => 'Date', - 'column_description' => 'Description', - 'column_opposing-iban' => 'Opposing account (IBAN)', - 'column_opposing-id' => 'Opposing account ID (matching Firefly)', - 'column_external-id' => 'External ID', - 'column_opposing-name' => 'Opposing account (name)', - 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'column_sepa-db' => 'SEPA Direct Debet', - '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__ignore' => '(ignorar esta columna)', + 'column_account-iban' => 'Caja de ahorro (CBU)', + 'column_account-id' => 'ID de la caja de ahorro (coincide con Firefly)', + 'column_account-name' => 'Caja de ahorro (nombre)', + 'column_amount' => 'Monto', + 'column_amount-comma-separated' => 'Monto (coma como separador de decimales)', + 'column_bill-id' => 'ID de factura (coincide con Firefly)', + 'column_bill-name' => 'Nombre de factura', + 'column_budget-id' => 'ID de presupuesto (coincide con Firefly)', + 'column_budget-name' => 'Nombre de presupuesto', + 'column_category-id' => 'ID de categoría (coincide con Firefly)', + 'column_category-name' => 'Nombre de categoría', + 'column_currency-code' => 'Código de moneda (ISO 4217)', + 'column_currency-id' => 'ID de moneda (coincide con Firefly)', + 'column_currency-name' => 'Nombre de moneda (coincide con Firefly)', + 'column_currency-symbol' => 'Símbolo de moneda (coincide con Firefly)', + 'column_date-interest' => 'Fecha de cálculo de intereses', + 'column_date-book' => 'Fecha de registro de transacción', + 'column_date-process' => 'Fecha de proceso de transacción', + 'column_date-transaction' => 'Fecha', + 'column_description' => 'Descripción', + 'column_opposing-iban' => 'Cuenta opuesta (CBU)', + 'column_opposing-id' => 'ID de cuenta opuesta (coincide con Firefly)', + 'column_external-id' => 'ID externo', + 'column_opposing-name' => 'Cuenta opuesta (nombre)', + 'column_rabo-debet-credit' => 'Indicador de débito/crédito específico de Rabobank', + 'column_ing-debet-credit' => 'Indicador de débito/crédito específico de ING', + 'column_sepa-ct-id' => 'ID de transferencia de crédito end-to-end de SEPA', + 'column_sepa-ct-op' => 'Transferencia de crédito a cuenta opuesta SEPA', + 'column_sepa-db' => 'Débito directo SEPA', + 'column_tags-comma' => 'Etiquetas (separadas por comas)', + 'column_tags-space' => 'Etiquetas (separadas por espacios)', + 'column_account-number' => 'Caja de ahorro (número de cuenta)', + 'column_opposing-number' => 'Cuenta opuesta (número de cuenta)', ]; \ No newline at end of file From fb8638fe6a8a4a78f3209050d3693a4a833bd050 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 10 May 2017 20:40:42 +0200 Subject: [PATCH 011/103] New translations breadcrumbs.php (Spanish) --- resources/lang/es_ES/breadcrumbs.php | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php index 6bcf9b862d..ddb5479e3f 100644 --- a/resources/lang/es_ES/breadcrumbs.php +++ b/resources/lang/es_ES/breadcrumbs.php @@ -11,31 +11,31 @@ */ return [ - 'home' => 'Home', - 'edit_currency' => 'Edit currency ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'newPiggyBank' => 'Create a new piggy bank', - 'edit_piggyBank' => 'Edit piggy bank ":name"', - 'preferences' => 'Preferences', - 'profile' => 'Profile', - 'changePassword' => 'Change your password', - 'bills' => 'Bills', - 'newBill' => 'New bill', - 'edit_bill' => 'Edit bill ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'reports' => 'Reports', - 'searchResult' => 'Search for ":query"', - 'withdrawal_list' => 'Expenses', - 'deposit_list' => 'Revenue, income and deposits', - 'transfer_list' => 'Transfers', - 'transfers_list' => 'Transfers', - 'create_withdrawal' => 'Create new withdrawal', - 'create_deposit' => 'Create new deposit', - 'create_transfer' => 'Create new transfer', - 'edit_journal' => 'Edit transaction ":description"', - 'delete_journal' => 'Delete transaction ":description"', - 'tags' => 'Tags', - 'createTag' => 'Create new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'delete_tag' => 'Delete tag ":tag"', + 'home' => 'Inicio', + 'edit_currency' => 'Editar moneda ":name"', + 'delete_currency' => 'Eliminar moneda ":name"', + 'newPiggyBank' => 'Crear nueva alcancía', + 'edit_piggyBank' => 'Editar alcancía ":name"', + 'preferences' => 'Preferencias', + 'profile' => 'Perfil', + 'changePassword' => 'Cambiar contraseña', + 'bills' => 'Facturas', + 'newBill' => 'Nueva factura', + 'edit_bill' => 'Editar factura ":name"', + 'delete_bill' => 'Eliminar factura ":name"', + 'reports' => 'Reportes', + 'searchResult' => 'Buscar ":query"', + 'withdrawal_list' => 'Expensas', + 'deposit_list' => 'Ganancia, ingresos y depósitos', + 'transfer_list' => 'Transferencias', + 'transfers_list' => 'Transferencias', + 'create_withdrawal' => 'Crear nueva extracción', + 'create_deposit' => 'Crear nuevo depósito', + 'create_transfer' => 'Crear nueva transferencia', + 'edit_journal' => 'Editar transacción ":description"', + 'delete_journal' => 'Eliminar transacción ":description"', + 'tags' => 'Etiquetas', + 'createTag' => 'Crear nueva etiqueta', + 'edit_tag' => 'Editar etiqueta ":tag"', + 'delete_tag' => 'Eliminar etiqueta ":tag"', ]; \ No newline at end of file From a6305ddea48756c39508ab9131b53c668696fdf9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 10 May 2017 20:40:44 +0200 Subject: [PATCH 012/103] New translations auth.php (Spanish) --- resources/lang/es_ES/auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/auth.php b/resources/lang/es_ES/auth.php index 5d833b3d68..3f4e103f18 100644 --- a/resources/lang/es_ES/auth.php +++ b/resources/lang/es_ES/auth.php @@ -22,7 +22,7 @@ return [ | */ - 'failed' => 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'failed' => 'Las credenciales no coinciden con los registros.', + 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor reintente en :seconds segundos.', ]; \ No newline at end of file From 0f0f91237093ace58c2c04757447f810705edf74 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 12 May 2017 06:21:26 +0200 Subject: [PATCH 013/103] Partial JS focus [skip ci] --- public/js/ff/transactions/single/create.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index 56109f855c..f74263d899 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -68,7 +68,9 @@ function updateNativeCurrency() { $('.currency-option[data-id="' + nativeCurrencyId + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); - //$('select[name="source_account_id"]').focus(); + if (what !== 'transfer') { + $('select[name="source_account_id"]').focus(); + } validateCurrencyForTransfer(); } From f66286105f80c482c2792f5829d5a9afc79da1fe Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 01:20:07 +0200 Subject: [PATCH 014/103] New translations validation.php (Slovenian) --- resources/lang/sl_SI/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/validation.php b/resources/lang/sl_SI/validation.php index 6d181ac1d6..a333e6cfb7 100644 --- a/resources/lang/sl_SI/validation.php +++ b/resources/lang/sl_SI/validation.php @@ -12,7 +12,7 @@ return [ 'iban' => 'To ni veljaven IBAN.', 'unique_account_number_for_user' => 'Kaže, da je ta številka računa že v uporabi.', - 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', + 'deleted_user' => 'Iz varnostnih razlogov ne morete ustvariti uporabnika s takim e-poštnim naslovom.', 'rule_trigger_value' => 'This value is invalid for the selected trigger.', 'rule_action_value' => 'This value is invalid for the selected action.', 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', From 9f9a3ea8fd2142c7607184b75e785c4313aac65a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 01:20:08 +0200 Subject: [PATCH 015/103] New translations breadcrumbs.php (Slovenian) --- resources/lang/sl_SI/breadcrumbs.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/lang/sl_SI/breadcrumbs.php b/resources/lang/sl_SI/breadcrumbs.php index 96f9298b55..051d41b74e 100644 --- a/resources/lang/sl_SI/breadcrumbs.php +++ b/resources/lang/sl_SI/breadcrumbs.php @@ -11,7 +11,7 @@ */ return [ - 'home' => 'Domov', + 'home' => 'Prva stran', 'edit_currency' => 'uredi valuto ":name"', 'delete_currency' => 'izbriši valuto ":name"', 'newPiggyBank' => 'ustvari novega pujska', @@ -19,10 +19,10 @@ return [ 'preferences' => 'nastavitve', 'profile' => 'profil', 'changePassword' => 'spremeni geslo', - 'bills' => 'Bills', - 'newBill' => 'New bill', - 'edit_bill' => 'Edit bill ":name"', - 'delete_bill' => 'Delete bill ":name"', + 'bills' => 'Trajniki', + 'newBill' => 'nov trajnik', + 'edit_bill' => 'uredi trajnik ":name"', + 'delete_bill' => 'izbriši trajnik ":name"', 'reports' => 'Poročila', 'searchResult' => 'rezultati iskanja za ":query"', 'withdrawal_list' => 'stroški', From 368df6694780af74d8a2d8fd313748cb22400941 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 08:40:06 +0200 Subject: [PATCH 016/103] New translations validation.php (Slovenian) --- resources/lang/sl_SI/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/validation.php b/resources/lang/sl_SI/validation.php index a333e6cfb7..f4760a6c28 100644 --- a/resources/lang/sl_SI/validation.php +++ b/resources/lang/sl_SI/validation.php @@ -13,7 +13,7 @@ return [ 'iban' => 'To ni veljaven IBAN.', 'unique_account_number_for_user' => 'Kaže, da je ta številka računa že v uporabi.', 'deleted_user' => 'Iz varnostnih razlogov ne morete ustvariti uporabnika s takim e-poštnim naslovom.', - 'rule_trigger_value' => 'This value is invalid for the selected trigger.', + 'rule_trigger_value' => 'Ta vrednost je neveljavna za izbrani sprožilec.', 'rule_action_value' => 'This value is invalid for the selected action.', 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', From 2eafd3cc15acfd8c581fb35f6cabcc6cfcad1117 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 08:57:43 +0200 Subject: [PATCH 017/103] Should fix #644 --- app/Console/Commands/UpgradeDatabase.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index d313c55ec6..aa2656b712 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -269,9 +269,10 @@ class UpgradeDatabase extends Command $repository = app(CurrencyRepositoryInterface::class); $notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.'; $transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.'; + $driver = DB::connection()->getDriverName(); foreach ($types as $type => $operator) { - $set = TransactionJournal + $query = TransactionJournal ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin( 'transactions', function (JoinClause $join) use ($operator) { $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0'); @@ -280,9 +281,15 @@ class UpgradeDatabase extends Command ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') ->where('transaction_types.type', $type) - ->where('account_meta.name', 'currency_id') - ->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data')) - ->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']); + ->where('account_meta.name', 'currency_id'); + if($driver === 'postgresql') { + $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)')); + } + if($driver !== 'postgresql') { + $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data')); + } + + $set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']); /** @var TransactionJournal $journal */ foreach ($set as $journal) { $expectedCurrency = $repository->find(intval($journal->expected_currency_id)); From 8c3871e8deb1e3ff96430a717d67e0c31c7296bc Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 09:00:09 +0200 Subject: [PATCH 018/103] New translations csv.php (Slovenian) --- resources/lang/sl_SI/csv.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/csv.php b/resources/lang/sl_SI/csv.php index 1fa0f9a61c..5e8b81121d 100644 --- a/resources/lang/sl_SI/csv.php +++ b/resources/lang/sl_SI/csv.php @@ -20,7 +20,7 @@ return [ 'date_help' => 'Formatiranje datuma in časa v vaši CSV datoteki. Uporabite obliko zapisa kot je navedena na tej strani. Privzeta vrednost bo prepoznala datume, ki so videti takole:: dateExample.', 'delimiter_help' => 'Izberi ločilo, ki je uporabljeno za ločevanje med posameznimi stolpci v vaši datoteki. Če niste prepričani, je vejica najbolj pogosta izbira.', 'import_account_help' => 'Če vaša CSV datoteka ne vsebuje informacij o vaših premoženjskih računih, uporabite ta seznam, da izberete kateremu računu pripadajo transakcije v CSV datoteki.', - 'upload_not_writeable' => 'The grey box contains a file path. It should be writeable. Please make sure it is.', + 'upload_not_writeable' => 'Prosim zagotovite, da ima Firefly dovoljenje za pisanje v datoteko, ki je navedena v sivem okvirčku.', // roles 'column_roles_title' => 'doličite pomen stolpcev', From 4410e1bbd77ee0fa421c75a2d8063b75a4e203ad Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 09:10:12 +0200 Subject: [PATCH 019/103] New translations csv.php (Slovenian) --- resources/lang/sl_SI/csv.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/sl_SI/csv.php b/resources/lang/sl_SI/csv.php index 5e8b81121d..4287a60fa8 100644 --- a/resources/lang/sl_SI/csv.php +++ b/resources/lang/sl_SI/csv.php @@ -34,10 +34,10 @@ return [ 'store_column_roles' => 'nadaljuj z uvozom', 'do_not_map' => '(ne poveži)', 'map_title' => 'poveži podatke za uvoz s podatki iz Firefly III', - 'map_text' => 'In the following tables, the left value shows you information found in your uploaded CSV 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.', + 'map_text' => 'Vrednosti na levi v spodnji tabeli prikazujejo podatke iz naložene CSV datoteke. Vaša naloga je, da jim, če je možno, določite obtoječio vrednost iz podatkovne baze. Firefly bo to upošteval pri uvozu. Če v podatkovni bazi ni ustrezne vrednosti, ali vrednosti ne želite določiti ničesar, potem pustite prazno.', - 'field_value' => 'Field value', - 'field_mapped_to' => 'Mapped to', + 'field_value' => 'podatek', + 'field_mapped_to' => 'povezan z', 'store_column_mapping' => 'shrani nastavitve', // map things. @@ -49,7 +49,7 @@ return [ 'column_account-name' => 'premoženjski račun (ime)', 'column_amount' => 'znesek', 'column_amount-comma-separated' => 'znesek (z decimalno vejico)', - 'column_bill-id' => 'Bill ID (matching Firefly)', + 'column_bill-id' => 'ID trajnika (Firefly)', 'column_bill-name' => 'Bill name', 'column_budget-id' => 'ID bugžeta (Firefly)', 'column_budget-name' => 'ime budžeta', From b263047f4e70ca3d379b7e70a69b23e00bbd8d94 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 09:20:21 +0200 Subject: [PATCH 020/103] New translations csv.php (Slovenian) --- resources/lang/sl_SI/csv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/sl_SI/csv.php b/resources/lang/sl_SI/csv.php index 4287a60fa8..fb4718d63d 100644 --- a/resources/lang/sl_SI/csv.php +++ b/resources/lang/sl_SI/csv.php @@ -50,7 +50,7 @@ return [ 'column_amount' => 'znesek', 'column_amount-comma-separated' => 'znesek (z decimalno vejico)', 'column_bill-id' => 'ID trajnika (Firefly)', - 'column_bill-name' => 'Bill name', + 'column_bill-name' => 'Ime trajnika', 'column_budget-id' => 'ID bugžeta (Firefly)', 'column_budget-name' => 'ime budžeta', 'column_category-id' => 'ID Kategorije (Firefly)', @@ -59,7 +59,7 @@ return [ 'column_currency-id' => 'ID valute (Firefly)', 'column_currency-name' => 'ime valute (Firefly)', 'column_currency-symbol' => 'simbol valute (Firefly)', - 'column_date-interest' => 'Interest calculation date', + 'column_date-interest' => 'Datum obračuna obresti', 'column_date-book' => 'datum knjiženja transakcije', 'column_date-process' => 'datum izvedbe transakcije', 'column_date-transaction' => 'datum', From 0d624f021b697d79763e7fb9f91b9eb034cdce98 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 09:30:23 +0200 Subject: [PATCH 021/103] New translations csv.php (Slovenian) --- resources/lang/sl_SI/csv.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/sl_SI/csv.php b/resources/lang/sl_SI/csv.php index fb4718d63d..7afe32c5da 100644 --- a/resources/lang/sl_SI/csv.php +++ b/resources/lang/sl_SI/csv.php @@ -70,9 +70,9 @@ return [ 'column_opposing-name' => 'ime ciljnega računa', 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', 'column_ing-debet-credit' => 'ING specific debet/credit indicator', - 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', - 'column_sepa-db' => 'SEPA Direct Debet', + 'column_sepa-ct-id' => 'SEPA številka transakcije', + 'column_sepa-ct-op' => 'SEPA protiračun', + 'column_sepa-db' => 'SEPA direktna obremenitev', 'column_tags-comma' => 'značke (ločene z vejicami)', 'column_tags-space' => 'značke (ločene s presledki)', 'column_account-number' => 'premoženjski račun (številka računa)', From 0eb56537132fea9de7b4823ff5a3ed45c2498458 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 10:00:08 +0200 Subject: [PATCH 022/103] New translations csv.php (Slovenian) --- resources/lang/sl_SI/csv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/sl_SI/csv.php b/resources/lang/sl_SI/csv.php index 7afe32c5da..6f5ca5cac9 100644 --- a/resources/lang/sl_SI/csv.php +++ b/resources/lang/sl_SI/csv.php @@ -68,8 +68,8 @@ return [ 'column_opposing-id' => 'protiračun (firefly)', 'column_external-id' => 'zunanja ID številka', 'column_opposing-name' => 'ime ciljnega računa', - 'column_rabo-debet-credit' => 'Rabobank specific debet/credit indicator', - 'column_ing-debet-credit' => 'ING specific debet/credit indicator', + 'column_rabo-debet-credit' => 'Poseben indikator za Rabobank', + 'column_ing-debet-credit' => 'Poseben indikator za banko ING', 'column_sepa-ct-id' => 'SEPA številka transakcije', 'column_sepa-ct-op' => 'SEPA protiračun', 'column_sepa-db' => 'SEPA direktna obremenitev', From 613eb7522c494c4b8218037a90596cd53b6ad9ad Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 10:00:10 +0200 Subject: [PATCH 023/103] New translations pagination.php (Slovenian) --- resources/lang/sl_SI/pagination.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/pagination.php b/resources/lang/sl_SI/pagination.php index 4eeab21dee..a6dd04e76c 100644 --- a/resources/lang/sl_SI/pagination.php +++ b/resources/lang/sl_SI/pagination.php @@ -11,7 +11,7 @@ return [ - 'previous' => '« Previous', + 'previous' => '« Prejšnja stran', 'next' => 'Next »', ]; \ No newline at end of file From 79154bba25cfeb2232d3d4f5cd0df488b691c4f9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 10:10:06 +0200 Subject: [PATCH 024/103] New translations pagination.php (Slovenian) --- resources/lang/sl_SI/pagination.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/pagination.php b/resources/lang/sl_SI/pagination.php index a6dd04e76c..943de4aa40 100644 --- a/resources/lang/sl_SI/pagination.php +++ b/resources/lang/sl_SI/pagination.php @@ -12,6 +12,6 @@ return [ 'previous' => '« Prejšnja stran', - 'next' => 'Next »', + 'next' => 'Naslednja stran »', ]; \ No newline at end of file From e072f8350736d786bca3aa0ce840d6e59effc5fe Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 14 May 2017 10:10:08 +0200 Subject: [PATCH 025/103] New translations list.php (Slovenian) --- resources/lang/sl_SI/list.php | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/resources/lang/sl_SI/list.php b/resources/lang/sl_SI/list.php index 90625d54e6..88a8d15cef 100644 --- a/resources/lang/sl_SI/list.php +++ b/resources/lang/sl_SI/list.php @@ -10,29 +10,29 @@ */ return [ - 'buttons' => 'Buttons', - 'icon' => 'Icon', + 'buttons' => 'Gumbi', + 'icon' => 'Ikona', 'id' => 'ID', - 'create_date' => 'Created at', - 'update_date' => 'Updated at', - 'balance_before' => 'Balance before', - 'balance_after' => 'Balance after', - 'name' => 'Name', - 'role' => 'Role', - 'currentBalance' => 'Current balance', - 'active' => 'Is active?', - 'lastActivity' => 'Last activity', - 'balanceDiff' => 'Balance difference between :start and :end', + 'create_date' => 'Ustvarjeno', + 'update_date' => 'Posodobljeno', + 'balance_before' => 'Stanje prej', + 'balance_after' => 'Stanje potem', + 'name' => 'ime', + 'role' => 'vloga', + 'currentBalance' => 'trenutno stanje', + 'active' => 'Aktiviran?', + 'lastActivity' => 'zadnja aktivnost', + 'balanceDiff' => 'Razlika stanja med :start in :end', 'matchedOn' => 'Matched on', 'matchesOn' => 'Matched on', - 'account_type' => 'Account type', - 'created_at' => 'Created at', - 'new_balance' => 'New balance', - 'account' => 'Account', - 'matchingAmount' => 'Amount', + 'account_type' => 'vrsta računa', + 'created_at' => 'ustvarjeno', + 'new_balance' => 'novo stanje', + 'account' => 'račun', + 'matchingAmount' => 'znesek', 'lastMatch' => 'Last match', - 'split_number' => 'Split #', - 'destination' => 'Destination', + 'split_number' => 'Delitev #', + 'destination' => 'Ciljni račun', 'source' => 'Source', 'next_expected_match' => 'Next expected match', 'automatch' => 'Auto match?', From b995a1d0918dcf790cb57e515a440f93f5670e63 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 15 May 2017 08:50:08 +0200 Subject: [PATCH 026/103] New translations list.php (Slovenian) --- resources/lang/sl_SI/list.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/lang/sl_SI/list.php b/resources/lang/sl_SI/list.php index 88a8d15cef..10c62690d3 100644 --- a/resources/lang/sl_SI/list.php +++ b/resources/lang/sl_SI/list.php @@ -23,26 +23,26 @@ return [ 'active' => 'Aktiviran?', 'lastActivity' => 'zadnja aktivnost', 'balanceDiff' => 'Razlika stanja med :start in :end', - 'matchedOn' => 'Matched on', - 'matchesOn' => 'Matched on', + 'matchedOn' => 'Povezano', + 'matchesOn' => 'Povezano', 'account_type' => 'vrsta računa', 'created_at' => 'ustvarjeno', 'new_balance' => 'novo stanje', 'account' => 'račun', 'matchingAmount' => 'znesek', - 'lastMatch' => 'Last match', + 'lastMatch' => 'Nazadnje povezano', 'split_number' => 'Delitev #', 'destination' => 'Ciljni račun', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', + 'source' => 'Izvor', + 'next_expected_match' => 'Naslenje pričakovano ujemanje', + 'automatch' => 'Samodejno poveži?', + 'repeat_freq' => 'Ponovitev', + 'description' => 'Opis', + 'amount' => 'Znesek', 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', + 'date' => 'datum', + 'interest_date' => 'datum obresti', + 'book_date' => 'datum knjiženja', 'process_date' => 'Processing date', 'due_date' => 'Due date', 'payment_date' => 'Payment date', From 438ce5c3db092e5867aea1bb5282bee540e46740 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 15 May 2017 09:00:35 +0200 Subject: [PATCH 027/103] New translations list.php (Slovenian) --- resources/lang/sl_SI/list.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/sl_SI/list.php b/resources/lang/sl_SI/list.php index 10c62690d3..0b17aa148c 100644 --- a/resources/lang/sl_SI/list.php +++ b/resources/lang/sl_SI/list.php @@ -43,8 +43,8 @@ return [ 'date' => 'datum', 'interest_date' => 'datum obresti', 'book_date' => 'datum knjiženja', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', + 'process_date' => 'datum izvedbe', + 'due_date' => 'rok plačila', 'payment_date' => 'Payment date', 'invoice_date' => 'Invoice date', 'interal_reference' => 'Internal reference', From f1028dbaed3059d75d65dc76576fbca1d3b205c8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 15 May 2017 09:10:14 +0200 Subject: [PATCH 028/103] New translations list.php (Slovenian) --- resources/lang/sl_SI/list.php | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/resources/lang/sl_SI/list.php b/resources/lang/sl_SI/list.php index 0b17aa148c..0537531876 100644 --- a/resources/lang/sl_SI/list.php +++ b/resources/lang/sl_SI/list.php @@ -39,34 +39,34 @@ return [ 'repeat_freq' => 'Ponovitev', 'description' => 'Opis', 'amount' => 'Znesek', - 'internal_reference' => 'Internal reference', + 'internal_reference' => 'Notranji sklic', 'date' => 'datum', 'interest_date' => 'datum obresti', 'book_date' => 'datum knjiženja', 'process_date' => 'datum izvedbe', 'due_date' => 'rok plačila', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', + 'payment_date' => 'Datum plačila', + 'invoice_date' => 'datum računa', + 'interal_reference' => 'Notranji sklic', + 'notes' => 'Zabeležke', + 'from' => 'Pošiljatelj', + 'piggy_bank' => 'Pujsek', + 'to' => 'Prejemnik', + 'budget' => 'Budžet', + 'category' => 'Kategorija', + 'bill' => 'Trajnik', + 'withdrawal' => 'Odhodek', + 'deposit' => 'Prihodek', + 'transfer' => 'Prenos', + 'type' => 'Vrsta', + 'completed' => 'Dokončano', 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', - 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_activated' => 'Is activated', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', + 'paid_current_period' => 'Plačano v tem obdobju', + 'email' => 'E-pošta', + 'registered_at' => 'Registriran pri', + 'is_activated' => 'Je v uporabi', + 'is_blocked' => 'Je blokiran', + 'is_admin' => 'Je administrator', 'has_two_factor' => 'Has 2FA', 'confirmed_from' => 'Confirmed from', 'registered_from' => 'Registered from', From a102f7044e44e8484fb1fb7dd8e9787a2d50d788 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 15 May 2017 09:20:09 +0200 Subject: [PATCH 029/103] New translations list.php (Slovenian) --- resources/lang/sl_SI/list.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/sl_SI/list.php b/resources/lang/sl_SI/list.php index 0537531876..35b6fc1a5c 100644 --- a/resources/lang/sl_SI/list.php +++ b/resources/lang/sl_SI/list.php @@ -64,11 +64,11 @@ return [ 'paid_current_period' => 'Plačano v tem obdobju', 'email' => 'E-pošta', 'registered_at' => 'Registriran pri', - 'is_activated' => 'Je v uporabi', + 'is_activated' => 'Je aktiviran', 'is_blocked' => 'Je blokiran', 'is_admin' => 'Je administrator', - 'has_two_factor' => 'Has 2FA', - 'confirmed_from' => 'Confirmed from', + 'has_two_factor' => 'Ima dvostopenjsko overovitev', + 'confirmed_from' => 'Potrjen od', 'registered_from' => 'Registered from', 'blocked_code' => 'Block code', 'domain' => 'Domain', From 98bb0731df533f5882a8a55a0ccb61c1f5585d7a Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 16 May 2017 15:10:12 +0200 Subject: [PATCH 030/103] New translations list.php (Slovenian) --- resources/lang/sl_SI/list.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/resources/lang/sl_SI/list.php b/resources/lang/sl_SI/list.php index 35b6fc1a5c..a5812ba409 100644 --- a/resources/lang/sl_SI/list.php +++ b/resources/lang/sl_SI/list.php @@ -69,21 +69,21 @@ return [ 'is_admin' => 'Je administrator', 'has_two_factor' => 'Ima dvostopenjsko overovitev', 'confirmed_from' => 'Potrjen od', - 'registered_from' => 'Registered from', - 'blocked_code' => 'Block code', - 'domain' => 'Domain', - 'registration_attempts' => 'Registration attempts', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', + 'registered_from' => 'Registriran', + 'blocked_code' => 'Koda vzroka blokade', + 'domain' => 'Domena', + 'registration_attempts' => 'Poskusov registriranja', + 'source_account' => 'Izvorni račun', + 'destination_account' => 'Ciljni račun', - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', + 'accounts_count' => 'Število računov', + 'journals_count' => 'Število transakcij', + 'attachments_count' => 'Število prilog', + 'bills_count' => 'Število trajnikov', + 'categories_count' => 'Število kategorij', + 'export_jobs_count' => 'Število izvozov', + 'import_jobs_count' => 'Število uvozov', + 'budget_count' => 'Število budžetov', + 'rule_and_groups_count' => 'Število pravil in skupin pravil', + 'tags_count' => 'Število značk', ]; \ No newline at end of file From 6b3b19632ac09486ec9c29ee70a7793a69010b5f Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 16 May 2017 15:10:15 +0200 Subject: [PATCH 031/103] New translations help.php (Slovenian) --- resources/lang/sl_SI/help.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/help.php b/resources/lang/sl_SI/help.php index 61210ffe41..a411c5d146 100644 --- a/resources/lang/sl_SI/help.php +++ b/resources/lang/sl_SI/help.php @@ -12,7 +12,7 @@ return [ // tour! - 'main-content-title' => 'Welcome to Firefly III', + 'main-content-title' => 'Dobrodošli na Firefly III', 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', 'sidebar-toggle-title' => 'Sidebar to create stuff', 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', From 2a7ba1893acc53a38a02047f97106afda9abfc3d Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 16 May 2017 15:10:18 +0200 Subject: [PATCH 032/103] New translations form.php (Slovenian) --- resources/lang/sl_SI/form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/sl_SI/form.php b/resources/lang/sl_SI/form.php index 300054df31..4c24c5c20b 100644 --- a/resources/lang/sl_SI/form.php +++ b/resources/lang/sl_SI/form.php @@ -12,7 +12,7 @@ return [ // new user: - 'bank_name' => 'Bank name', + 'bank_name' => 'Naziv banke', 'bank_balance' => 'Balance', 'savings_balance' => 'Savings balance', 'credit_card_limit' => 'Credit card limit', From ded0df93032389e025d0c08eb53a705d1b6e06eb Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:00:06 +0200 Subject: [PATCH 033/103] New translations config.php (Spanish) --- resources/lang/es_ES/config.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 resources/lang/es_ES/config.php diff --git a/resources/lang/es_ES/config.php b/resources/lang/es_ES/config.php new file mode 100644 index 0000000000..9067645275 --- /dev/null +++ b/resources/lang/es_ES/config.php @@ -0,0 +1,23 @@ + 'es, Spanish, es_ES, es_ES.utf8, es_ES.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Semana %W, %Y', + 'quarter_of_year' => '%B %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + +]; \ No newline at end of file From 1f41f27e89092ca418da398d2fbe489f784061dd Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:00:11 +0200 Subject: [PATCH 034/103] New translations breadcrumbs.php (Spanish) --- resources/lang/es_ES/breadcrumbs.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php index 211e6a3037..91d26f164b 100644 --- a/resources/lang/es_ES/breadcrumbs.php +++ b/resources/lang/es_ES/breadcrumbs.php @@ -25,11 +25,11 @@ return [ 'delete_bill' => 'Eliminar factura ":name"', 'reports' => 'Reportes', 'searchResult' => 'Buscar ":query"', - 'withdrawal_list' => 'Expensas', + 'withdrawal_list' => 'Gastos', 'deposit_list' => 'Ganancia, ingresos y depósitos', 'transfer_list' => 'Transferencias', 'transfers_list' => 'Transferencias', - 'create_withdrawal' => 'Crear nueva extracción', + 'create_withdrawal' => 'Crear nuevo retiro', 'create_deposit' => 'Crear nuevo depósito', 'create_transfer' => 'Crear nueva transferencia', 'edit_journal' => 'Editar transacción ":description"', @@ -38,4 +38,4 @@ return [ 'createTag' => 'Crear nueva etiqueta', 'edit_tag' => 'Editar etiqueta ":tag"', 'delete_tag' => 'Eliminar etiqueta ":tag"', -]; +]; \ No newline at end of file From 0608fd77323c4195e6bd2d1d414d635fa29cfc78 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:10:06 +0200 Subject: [PATCH 035/103] New translations demo.php (Spanish) --- resources/lang/es_ES/demo.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index e2d7695439..57bb9cdcb0 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -12,13 +12,13 @@ return [ 'see_help_icon' => 'Sin embargo, el ícono en la esquina superior-derecha puede tener más información.', 'index' => '¡Bienvenido a Firefly III! En esta página tendrá una vista rápida de sus finanzas. Para más información, mire sus cuentas rarr; Asset Accounts y, claro, las páginas de presupuestos y reportes. O simplemente investigue la aplicación por su cuenta.', 'accounts-index' => 'Las cajas de ahorro son sus cuentas de banco personales. Las cuentas de gastos contienen sus gastos habituales como compras y salidas con amigos. Las cuentas de ingresos repesentan ingresos de su trabajo u otras fuentes. En esta página puede editarlas o eliminarlas.', - 'budgets-index' => 'This page shows you an overview of your budgets. The top bar shows the amount that is available to be budgeted. This can be customized for any period by clicking the amount on the right. The amount you\'ve actually spent is shown in the bar below. Below that are the expenses per budget and what you\'ve budgeted for them.', - 'reports-index-start' => 'Firefly III supports four types of reports. Read about them by clicking on the -icon in the top right corner.', + 'budgets-index' => 'Esta página le muestra una visión general de sus presupuestos. La barra superior muestra la cantidad que está disponible para ser presupuestado. Esto se puede personalizar para cualquier período haciendo clic en la cantidad a la derecha. La cantidad que has gastado hasta ahora se muestra en la barra de abajo. Debajo están los gastos por presupuesto y lo que ha presupuestado para ellos.', + 'reports-index-start' => 'Firefly III admite cuatro tipos de informes. Lea sobre ellos haciendo clic en el icono en la esquina superior derecha.', 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', - 'currencies-index' => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', - 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', - 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', + 'currencies-index' => 'Firefly III admite múltiples monedas. A pesar de que la moneda por defecto es el Euro, se puede seleccionar el Dólar de EE.UU, y muchas otras monedas. Como se puede ver se ha incluido una pequeña selección de monedas, pero puedes agregar tu propia moneda si lo deseas. Sin embargo, cambiar la moneda predeterminada no cambiará la moneda de las transacciones existentes: Firefly III admite el uso de varias monedas al mismo tiempo.', + 'transactions-index' => 'Estos gastos, depósitos y transferencias no son particularmente imaginativos. Se han generado automáticamente.', + 'piggy-banks-index' => 'Como puede ver, hay tres alcancías. Utilice los botones más y menos para influir en la cantidad de dinero en cada alcancía. Haga clic en el nombre de la alcancía para ver la administración de cada una.', 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', - 'import-configure-configuration' => 'The configuration you see below is correct for the local file.', -]; + 'import-configure-configuration' => 'La configuración que ves a continuación es correcta para el archivo local.', +]; \ No newline at end of file From 3eba3167fd3f9bf23cb7da2544088051ce23f9d7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:20:07 +0200 Subject: [PATCH 036/103] New translations firefly.php (Spanish) --- resources/lang/es_ES/firefly.php | 1063 ++++++++++++++++++++++++++++++ 1 file changed, 1063 insertions(+) create mode 100644 resources/lang/es_ES/firefly.php diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php new file mode 100644 index 0000000000..0c136971fd --- /dev/null +++ b/resources/lang/es_ES/firefly.php @@ -0,0 +1,1063 @@ + 'traducción incompleta', + 'close' => 'Cerrar', + 'actions' => 'Acciones', + 'edit' => 'Editar', + 'delete' => 'Eliminar', + 'welcomeBack' => '¿Qué está tocando?', + 'everything' => 'Todo', + 'customRange' => 'Rango personalizado', + 'apply' => 'Aplicar', + 'cancel' => 'Cancelar', + 'from' => 'Desde', + 'to' => 'Hasta', + 'showEverything' => 'Mostrar todo', + 'never' => 'Nunca', + 'search_results_for' => 'Resultados de la búsqueda para %{query}', + 'advanced_search' => 'Búsqueda avanzada', + 'advanced_search_intro' => 'Hay varios modificadores que puedes utilizar en tu búsqueda para acotar los resultados. Si usas alguna de estas, la búsqueda regresará sólo las transacciones. Por favor, has clic en el -icono para obtener más información.', + 'bounced_error' => 'El mensaje enviado al: correo electrónico no ha sido recibido, así que no hay acceso para usted.', + 'deleted_error' => 'Las credenciales no coinciden con los registros.', + 'general_blocked_error' => 'Tu cuenta ha sido creada. Ahora puedes iniciar sesión.', + 'expired_error' => 'Su cuenta ha caducado y ya no puede ser utilizado.', + 'removed_amount' => 'Eliminado: cantidad', + 'added_amount' => 'Agregado: cantidad', + 'asset_account_role_help' => 'Las opciones extras como resultado de su elección se pueden establecer más tarde.', + 'Opening balance' => 'Balance inicial', + 'create_new_stuff' => 'Crear nueva etiqueta', + 'new_withdrawal' => 'Nuevo retiro', + 'new_deposit' => 'Nuevo depósito', + 'new_transfer' => 'Nueva transferencia', + 'new_asset_account' => 'Nueva cuenta de activo', + 'new_expense_account' => 'Nueva cuenta de gastos', + 'new_revenue_account' => 'Nueva cuenta de ingresos', + '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?', + 'flash_success' => '¡Operación correcta!', + 'flash_info' => 'Mensaje', + 'flash_warning' => '¡Atención!', + 'flash_error' => '¡Error!', + 'flash_info_multiple' => 'Hay un mensaje | Hay: mensajes de la cuenta', + 'flash_error_multiple' => 'Hay un error | Hay: contar errores', + 'net_worth' => 'Valor Neto', + 'route_has_no_help' => 'There is no help for this route.', + 'help_may_not_be_your_language' => 'This help text is in English. It is not yet available in your language', + 'two_factor_welcome' => 'Hello, :user!', + 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', + 'two_factor_code_here' => 'Enter code here', + 'two_factor_title' => 'Two factor authentication', + 'authenticate' => 'Authenticate', + 'two_factor_forgot_title' => 'Lost two factor authentication', + 'two_factor_forgot' => 'I forgot my two-factor thing.', + 'two_factor_lost_header' => 'Lost your two factor authentication?', + 'two_factor_lost_intro' => 'Unfortunately, this is not something you can reset from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions.', + 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', + 'warning_much_data' => ':days days of data may take a while to load.', + 'registered' => 'You have registered successfully!', + 'search' => 'Search', + 'search_found_accounts' => 'Found :count account(s) for your query.', + 'search_found_categories' => 'Found :count category(ies) for your query.', + 'search_found_budgets' => 'Found :count budget(s) for your query.', + 'search_found_tags' => 'Found :count tag(s) for your query.', + 'search_found_transactions' => 'Found :count transaction(s) for your query.', + 'results_limited' => 'The results are limited to :count entries.', + 'tagbalancingAct' => 'Balancing act', + 'tagadvancePayment' => 'Advance payment', + 'tagnothing' => '', + '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.', + 'Savings account' => 'Savings account', + 'Credit card' => 'Credit card', + 'source_accounts' => 'Source account(s)', + 'destination_accounts' => 'Destination account(s)', + 'user_id_is' => 'Your user id is :user', + 'field_supports_markdown' => 'This field supports Markdown.', + 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', + 'nothing_to_display' => 'There are no transactions to show you', + 'show_all_no_filter' => 'Show all transactions without grouping them by date.', + 'expenses_by_category' => 'Expenses by category', + 'expenses_by_budget' => 'Expenses by budget', + 'income_by_category' => 'Income by category', + 'expenses_by_asset_account' => 'Expenses by asset account', + 'expenses_by_expense_account' => 'Expenses by expense account', + 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', + 'sum_of_expenses' => 'Sum of expenses', + 'sum_of_income' => 'Sum of income', + 'total_sum' => 'Total sum', + 'spent_in_specific_budget' => 'Spent in budget ":budget"', + 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', + 'left_in_budget_limit' => 'Left to spend according to budgeting', + 'cannot_change_demo' => 'You cannot change the password of the demonstration account.', + 'cannot_delete_demo' => 'You cannot remove the demonstration account.', + 'cannot_reset_demo_user' => 'You cannot reset the password of the demonstration account', + 'per_period' => 'Per period', + 'all_periods' => 'All periods', + 'current_period' => 'Current period', + 'show_the_current_period_and_overview' => 'Show the current period and overview', + 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', + 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', + 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', + 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'budget_in_period_breadcrumb' => 'Between :start and :end', + 'clone_withdrawal' => 'Clone this withdrawal', + 'clone_deposit' => 'Clone this deposit', + 'clone_transfer' => 'Clone this transfer', + 'transaction_journal_other_options' => 'Other options', + 'multi_select_no_selection' => 'None selected', + 'multi_select_all_selected' => 'All selected', + 'multi_select_filter_placeholder' => 'Find..', + 'between_dates_breadcrumb' => 'Between :start and :end', + 'all_journals_without_budget' => 'All transactions without a budget', + 'journals_without_budget' => 'Transactions without a budget', + 'all_journals_without_category' => 'All transactions without a category', + 'journals_without_category' => 'Transactions without a category', + 'all_journals_for_account' => 'All transactions for account :name', + 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', + 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', + 'transferred' => 'Transferred', + 'all_withdrawal' => 'All expenses', + 'all_transactions' => 'All transactions', + 'title_withdrawal_between' => 'All expenses between :start and :end', + 'all_deposit' => 'All revenue', + 'title_deposit_between' => 'All revenue between :start and :end', + 'all_transfers' => 'All transfers', + 'title_transfers_between' => 'All transfers between :start and :end', + 'all_transfer' => 'All transfers', + 'all_journals_for_tag' => 'All transactions for tag ":tag"', + 'title_transfer_between' => 'All transfers between :start and :end', + 'all_journals_for_category' => 'All transactions for category :name', + 'all_journals_for_budget' => 'All transactions for budget :name', + 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', + 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', + 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', + 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', + 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', + 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', + + // repeat frequencies: + 'repeat_freq_yearly' => 'yearly', + 'repeat_freq_monthly' => 'monthly', + 'weekly' => 'weekly', + 'quarterly' => 'quarterly', + 'half-year' => 'every half year', + 'yearly' => 'yearly', + // account confirmation: + 'confirm_account_header' => 'Please confirm your account', + 'confirm_account_intro' => 'An email has been sent to the address you used during your registration. Please check it out for further instructions. If you did not get this message, you can have Firefly send it again.', + 'confirm_account_resend_email' => 'Send me the confirmation message I need to activate my account.', + 'account_is_confirmed' => 'Your account has been confirmed!', + 'invalid_activation_code' => 'It seems the code you are using is not valid, or has expired.', + 'confirm_account_is_resent_header' => 'The confirmation has been resent', + 'confirm_account_is_resent_text' => 'The confirmation message has been resent. If you still did not receive the confirmation message, please contact the site owner at :owner or check the log files to see what went wrong.', + 'confirm_account_is_resent_go_home' => 'Go to the index page of Firefly', + 'confirm_account_not_resent_header' => 'Something went wrong :(', + 'confirm_account_not_resent_intro' => 'The confirmation message has been not resent. If you still did not receive the confirmation message, please contact the site owner at :owner instead. Possibly, you have tried to resend the activation message too often. You can have Firefly III try to resend the confirmation message every hour.', + 'confirm_account_not_resent_go_home' => 'Go to the index page of Firefly', + + // export data: + 'import_and_export' => 'Import and export', + 'export_data' => 'Export data', + 'export_data_intro' => 'For backup purposes, when migrating to another system or when migrating to another Firefly III installation.', + 'export_format' => 'Export format', + 'export_format_csv' => 'Comma separated values (CSV file)', + 'export_format_mt940' => 'MT940 compatible format', + 'export_included_accounts' => 'Export transactions from these accounts', + 'include_old_uploads_help' => 'Firefly III does not throw away the original CSV files you have imported in the past. You can include them in your export.', + 'do_export' => 'Export', + 'export_status_never_started' => 'The export has not started yet', + 'export_status_make_exporter' => 'Creating exporter thing...', + 'export_status_collecting_journals' => 'Collecting your transactions...', + 'export_status_collected_journals' => 'Collected your transactions!', + 'export_status_converting_to_export_format' => 'Converting your transactions...', + 'export_status_converted_to_export_format' => 'Converted your transactions!', + 'export_status_creating_journal_file' => 'Creating the export file...', + 'export_status_created_journal_file' => 'Created the export file!', + 'export_status_collecting_attachments' => 'Collecting all your attachments...', + 'export_status_collected_attachments' => 'Collected all your attachments!', + 'export_status_collecting_old_uploads' => 'Collecting all your previous uploads...', + 'export_status_collected_old_uploads' => 'Collected all your previous uploads!', + 'export_status_creating_config_file' => 'Creating a configuration file...', + 'export_status_created_config_file' => 'Created a configuration file!', + 'export_status_creating_zip_file' => 'Creating a zip file...', + 'export_status_created_zip_file' => 'Created a zip file!', + 'export_status_finished' => 'Export has succesfully finished! Yay!', + 'export_data_please_wait' => 'Please wait...', + 'attachment_explanation' => 'The file called \':attachment_name\' (#:attachment_id) was originally uploaded to :type \':description\' (#:journal_id) dated :date for the amount of :amount.', + + // rules + 'rules' => 'Rules', + 'rules_explanation' => 'Here you can manage rules. Rules are triggered when a transaction is created or updated. Then, if the transaction has certain properties (called "triggers") Firefly will execute the "actions". Combined, you can make Firefly respond in a certain way to new transactions.', + 'rule_name' => 'Name of rule', + 'rule_triggers' => 'Rule triggers when', + 'rule_actions' => 'Rule will', + 'new_rule' => 'New rule', + 'new_rule_group' => 'New rule group', + 'rule_priority_up' => 'Give rule more priority', + 'rule_priority_down' => 'Give rule less priority', + 'make_new_rule_group' => 'Make new rule group', + 'store_new_rule_group' => 'Store new rule group', + 'created_new_rule_group' => 'New rule group ":title" stored!', + 'updated_rule_group' => 'Successfully updated rule group ":title".', + 'edit_rule_group' => 'Edit rule group ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'deleted_rule_group' => 'Deleted rule group ":title"', + 'update_rule_group' => 'Update rule group', + 'no_rules_in_group' => 'There are no rules in this group', + '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"', + 'rule_help_stop_processing' => 'When you check this box, later rules in this group will not be executed.', + 'rule_help_active' => 'Inactive rules will never fire.', + 'stored_new_rule' => 'Stored new rule with title ":title"', + 'deleted_rule' => 'Deleted rule with title ":title"', + 'store_new_rule' => 'Store new rule', + 'updated_rule' => 'Updated rule with title ":title"', + 'default_rule_group_name' => 'Default rules', + 'default_rule_group_description' => 'All your rules not in a particular group.', + 'default_rule_name' => 'Your first default rule', + 'default_rule_description' => 'This rule is an example. You can safely delete it.', + 'default_rule_trigger_description' => 'The Man Who Sold the World', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Bought the world from ', + 'default_rule_action_set_category' => 'Large expenses', + 'trigger' => 'Trigger', + 'trigger_value' => 'Trigger on value', + 'stop_processing_other_triggers' => 'Stop processing other triggers', + 'add_rule_trigger' => 'Add new trigger', + 'action' => 'Action', + 'action_value' => 'Action value', + 'stop_executing_other_actions' => 'Stop executing other actions', + 'add_rule_action' => 'Add new action', + 'edit_rule' => 'Edit rule ":title"', + 'delete_rule' => 'Delete rule ":title"', + 'update_rule' => 'Update rule', + 'test_rule_triggers' => 'See matching transactions', + 'warning_transaction_subset' => 'For performance reasons this list is limited to :max_num_transactions and may only show a subset of matching transactions', + 'warning_no_matching_transactions' => 'No matching transactions found. Please note that for performance reasons, only the last :num_transactions transactions have been checked.', + 'warning_no_valid_triggers' => 'No valid triggers provided.', + 'execute_on_existing_transactions' => 'Execute for existing transactions', + 'rule_group_select_transactions' => 'Execute rule group ":title" on existing transactions', + 'execute_on_existing_transactions_intro' => 'When a rule or group has been changed or added, you can execute it for existing transactions', + 'execute_on_existing_transactions_short' => 'Existing transactions', + 'executed_group_on_existing_transactions' => 'Executed group ":title" for existing transactions', + 'execute_group_on_existing_transactions' => 'Execute group ":title" for existing transactions', + 'include_transactions_from_accounts' => 'Include transactions from these accounts', + 'execute' => 'Execute', + + // actions and triggers + 'rule_trigger_user_action' => 'User action is ":trigger_value"', + 'rule_trigger_from_account_starts' => 'Source account starts with ":trigger_value"', + 'rule_trigger_from_account_ends' => 'Source account ends with ":trigger_value"', + 'rule_trigger_from_account_is' => 'Source account is ":trigger_value"', + 'rule_trigger_from_account_contains' => 'Source account contains ":trigger_value"', + 'rule_trigger_to_account_starts' => 'Destination account starts with ":trigger_value"', + 'rule_trigger_to_account_ends' => 'Destination account ends with ":trigger_value"', + 'rule_trigger_to_account_is' => 'Destination account is ":trigger_value"', + 'rule_trigger_to_account_contains' => 'Destination account contains ":trigger_value"', + 'rule_trigger_transaction_type' => 'Transaction is of type ":trigger_value"', + 'rule_trigger_category_is' => 'Category is ":trigger_value"', + 'rule_trigger_amount_less' => 'Amount is less than :trigger_value', + 'rule_trigger_amount_exactly' => 'Amount is :trigger_value', + 'rule_trigger_amount_more' => 'Amount is more than :trigger_value', + 'rule_trigger_description_starts' => 'Description starts with ":trigger_value"', + 'rule_trigger_description_ends' => 'Description ends with ":trigger_value"', + 'rule_trigger_description_contains' => 'Description contains ":trigger_value"', + 'rule_trigger_description_is' => 'Description is ":trigger_value"', + 'rule_trigger_from_account_starts_choice' => 'Source account starts with..', + 'rule_trigger_from_account_ends_choice' => 'Source account ends with..', + 'rule_trigger_from_account_is_choice' => 'Source account is..', + 'rule_trigger_from_account_contains_choice' => 'Source account contains..', + 'rule_trigger_to_account_starts_choice' => 'Destination account starts with..', + 'rule_trigger_to_account_ends_choice' => 'Destination account ends with..', + 'rule_trigger_to_account_is_choice' => 'Destination account is..', + 'rule_trigger_to_account_contains_choice' => 'Destination account contains..', + 'rule_trigger_transaction_type_choice' => 'Transaction is of type..', + 'rule_trigger_amount_less_choice' => 'Amount is less than..', + 'rule_trigger_amount_exactly_choice' => 'Amount is..', + 'rule_trigger_amount_more_choice' => 'Amount is more than..', + 'rule_trigger_description_starts_choice' => 'Description starts with..', + 'rule_trigger_description_ends_choice' => 'Description ends with..', + 'rule_trigger_description_contains_choice' => 'Description contains..', + 'rule_trigger_description_is_choice' => 'Description is..', + 'rule_trigger_category_is_choice' => 'Category is..', + 'rule_trigger_budget_is_choice' => 'Budget is..', + 'rule_trigger_tag_is_choice' => '(A) tag is..', + 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', + 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', + 'rule_trigger_store_journal' => 'When a transaction is created', + 'rule_trigger_update_journal' => 'When a transaction is updated', + 'rule_action_set_category' => 'Set category to ":action_value"', + 'rule_action_clear_category' => 'Clear category', + 'rule_action_set_budget' => 'Set budget to ":action_value"', + 'rule_action_clear_budget' => 'Clear budget', + 'rule_action_add_tag' => 'Add tag ":action_value"', + 'rule_action_remove_tag' => 'Remove tag ":action_value"', + 'rule_action_remove_all_tags' => 'Remove all tags', + 'rule_action_set_description' => 'Set description to ":action_value"', + 'rule_action_append_description' => 'Append description with ":action_value"', + 'rule_action_prepend_description' => 'Prepend description with ":action_value"', + 'rule_action_set_category_choice' => 'Set category to..', + 'rule_action_clear_category_choice' => 'Clear any category', + 'rule_action_set_budget_choice' => 'Set budget to..', + 'rule_action_clear_budget_choice' => 'Clear any budget', + 'rule_action_add_tag_choice' => 'Add tag..', + 'rule_action_remove_tag_choice' => 'Remove tag..', + 'rule_action_remove_all_tags_choice' => 'Remove all tags', + 'rule_action_set_description_choice' => 'Set description to..', + 'rule_action_append_description_choice' => 'Append description with..', + 'rule_action_prepend_description_choice' => 'Prepend description with..', + 'rule_action_set_source_account_choice' => 'Set source account to...', + 'rule_action_set_source_account' => 'Set source account to :action_value', + 'rule_action_set_destination_account_choice' => 'Set destination account to...', + 'rule_action_set_destination_account' => 'Set destination account to :action_value', + + // tags + 'store_new_tag' => 'Store new tag', + 'update_tag' => 'Update tag', + 'no_location_set' => 'No location set.', + 'meta_data' => 'Meta data', + 'location' => 'Location', + + // preferences + '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_1D' => 'One day', + 'pref_1W' => 'One week', + 'pref_1M' => 'One month', + 'pref_3M' => 'Three months (quarter)', + 'pref_6M' => 'Six months', + 'pref_1Y' => 'One year', + 'pref_languages' => 'Languages', + 'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?', + 'pref_custom_fiscal_year' => 'Fiscal year settings', + 'pref_custom_fiscal_year_label' => 'Enabled', + 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', + 'pref_fiscal_year_start_label' => 'Fiscal year start date', + 'pref_two_factor_auth' => '2-step verification', + 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Enable 2-step verification', + 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', + 'pref_two_factor_auth_remove_it' => 'Don\'t forget to remove the account from your authentication app!', + 'pref_two_factor_auth_code' => 'Verify code', + 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', + 'pref_two_factor_auth_reset_code' => 'Reset verification code', + 'pref_two_factor_auth_remove_code' => 'Remove verification code', + 'pref_two_factor_auth_remove_will_disable' => '(this will also disable two-factor authentication)', + 'pref_save_settings' => 'Save settings', + 'saved_preferences' => 'Preferences saved!', + 'preferences_general' => 'General', + 'preferences_frontpage' => 'Home screen', + 'preferences_security' => 'Security', + 'preferences_layout' => 'Layout', + 'pref_home_show_deposits' => 'Show deposits on the home screen', + 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', + 'pref_home_do_show_deposits' => 'Yes, show them', + 'successful_count' => 'of which :count successful', + 'transaction_page_size_title' => 'Page size', + 'transaction_page_size_help' => 'Any list of transactions shows at most this many transactions', + 'transaction_page_size_label' => 'Page size', + 'between_dates' => '(:start and :end)', + 'pref_optional_fields_transaction' => 'Optional fields for transactions', + 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', + 'optional_tj_date_fields' => 'Date fields', + 'optional_tj_business_fields' => 'Business fields', + 'optional_tj_attachment_fields' => 'Attachment fields', + 'pref_optional_tj_interest_date' => 'Interest date', + 'pref_optional_tj_book_date' => 'Book date', + 'pref_optional_tj_process_date' => 'Processing date', + 'pref_optional_tj_due_date' => 'Due date', + 'pref_optional_tj_payment_date' => 'Payment date', + 'pref_optional_tj_invoice_date' => 'Invoice date', + 'pref_optional_tj_internal_reference' => 'Internal reference', + 'pref_optional_tj_notes' => 'Notes', + 'pref_optional_tj_attachments' => 'Attachments', + 'optional_field_meta_dates' => 'Dates', + 'optional_field_meta_business' => 'Business', + 'optional_field_attachments' => 'Attachments', + 'optional_field_meta_data' => 'Optional meta data', + + + // profile: + 'change_your_password' => 'Change your password', + 'delete_account' => 'Delete account', + 'current_password' => 'Current password', + 'new_password' => 'New password', + 'new_password_again' => 'New password (again)', + 'delete_your_account' => 'Delete your account', + 'delete_your_account_help' => 'Deleting your account will also delete any accounts, transactions, anything you might have saved into Firefly III. It\'ll be GONE.', + 'delete_your_account_password' => 'Enter your password to continue.', + 'password' => 'Password', + 'are_you_sure' => 'Are you sure? You cannot undo this.', + 'delete_account_button' => 'DELETE your account', + 'invalid_current_password' => 'Invalid current password!', + 'password_changed' => 'Password changed!', + 'should_change' => 'The idea is to change your password.', + 'invalid_password' => 'Invalid password!', + + + // attachments + 'nr_of_attachments' => 'One attachment|:count attachments', + 'attachments' => 'Attachments', + 'edit_attachment' => 'Edit attachment ":name"', + 'update_attachment' => 'Update attachment', + 'delete_attachment' => 'Delete attachment ":name"', + 'attachment_deleted' => 'Deleted attachment ":name"', + 'attachment_updated' => 'Updated attachment ":name"', + 'upload_max_file_size' => 'Maximum file size: :size', + + // tour: + 'prev' => 'Prev', + 'next' => 'Next', + 'end-tour' => 'End tour', + 'pause' => 'Pause', + + // transaction index + 'title_expenses' => 'Expenses', + 'title_withdrawal' => 'Expenses', + 'title_revenue' => 'Revenue / income', + 'title_deposit' => 'Revenue / income', + 'title_transfer' => 'Transfers', + 'title_transfers' => 'Transfers', + + // convert stuff: + 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal', + 'convert_is_already_type_Deposit' => 'This transaction is already a deposit', + 'convert_is_already_type_Transfer' => 'This transaction is already a transfer', + 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', + 'convert_to_Deposit' => 'Convert ":description" to a deposit', + 'convert_to_Transfer' => 'Convert ":description" to a transfer', + 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', + 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', + 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', + 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', + 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', + 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', + 'transaction_journal_convert_options' => 'Convert this transaction', + 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', + 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', + 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', + 'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer', + 'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit', + 'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal', + 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.', + 'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.', + 'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.', + 'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.', + 'convert_explanation_withdrawal_deposit' => 'If you convert this withdrawal into a deposit, :amount will be deposited into :sourceName instead of taken from it.', + 'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from :sourceName to a new asset account, instead of being paid to :destinationName.', + 'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from :destinationName instead of added to it.', + 'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into :destinationName.', + 'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from :sourceName to a new destination as an expense, instead of to :destinationName as a transfer.', + 'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account :destinationName instead of being transferred there.', + 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', + 'converted_to_Deposit' => 'The transaction has been converted to a deposit', + 'converted_to_Transfer' => 'The transaction has been converted to a transfer', + + + // create new stuff: + 'create_new_withdrawal' => 'Create new withdrawal', + 'create_new_deposit' => 'Create new deposit', + 'create_new_transfer' => 'Create new transfer', + 'create_new_asset' => 'Create new asset account', + 'create_new_expense' => 'Create new expense account', + 'create_new_revenue' => 'Create new revenue account', + 'create_new_piggy_bank' => 'Create new piggy bank', + 'create_new_bill' => 'Create new bill', + + // currencies: + 'create_currency' => 'Create a new currency', + 'store_currency' => 'Store new currency', + 'update_currency' => 'Update currency', + 'new_default_currency' => ':name is now the default currency.', + 'cannot_delete_currency' => 'Cannot delete :name because it is still in use.', + 'deleted_currency' => 'Currency :name deleted', + 'created_currency' => 'Currency :name created', + 'updated_currency' => 'Currency :name updated', + 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', + 'currencies_intro' => 'Firefly III supports various currencies which you can set and enable here.', + 'make_default_currency' => 'make default', + 'default_currency' => 'default', + + // new user: + 'submit' => 'Submit', + 'getting_started' => 'Getting started', + 'to_get_started' => 'To get started with Firefly, please enter your current bank\'s name, and the balance of your checking account:', + 'savings_balance_text' => 'If you have a savings account, please enter the current balance of your savings account:', + 'cc_balance_text' => 'If you have a credit card, please enter your credit card\'s limit.', + 'stored_new_account_new_user' => 'Yay! Your new account has been stored.', + 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', + + // forms: + 'mandatoryFields' => 'Mandatory fields', + 'optionalFields' => 'Optional fields', + 'options' => 'Options', + + // budgets: + 'create_new_budget' => 'Create a new budget', + 'store_new_budget' => 'Store new budget', + 'stored_new_budget' => 'Stored new budget ":name"', + 'available_between' => 'Available between :start and :end', + 'transactionsWithoutBudget' => 'Expenses without budget', + 'transactions_no_budget' => 'Expenses without budget between :start and :end', + 'spent_between' => 'Spent between :start and :end', + 'createBudget' => 'New budget', + 'inactiveBudgets' => 'Inactive budgets', + 'without_budget_between' => 'Transactions without a budget between :start and :end', + 'budget_in_month' => ':name in :month', + 'delete_budget' => 'Delete budget ":name"', + 'deleted_budget' => 'Deleted budget ":name"', + 'edit_budget' => 'Edit budget ":name"', + 'updated_budget' => 'Updated budget ":name"', + 'update_amount' => 'Update amount', + 'update_budget' => 'Update budget', + 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', + + // bills: + 'matching_on' => 'Matching on', + 'between_amounts' => 'between :low and :high.', + 'repeats' => 'Repeats', + 'connected_journals' => 'Connected transactions', + 'auto_match_on' => 'Automatically matched by Firefly', + 'auto_match_off' => 'Not automatically matched by Firefly', + 'next_expected_match' => 'Next expected match', + 'delete_bill' => 'Delete bill ":name"', + 'deleted_bill' => 'Deleted bill ":name"', + 'edit_bill' => 'Edit bill ":name"', + 'more' => 'More', + 'rescan_old' => 'Rescan old transactions', + 'update_bill' => 'Update bill', + 'updated_bill' => 'Updated bill ":name"', + 'store_new_bill' => 'Store new bill', + 'stored_new_bill' => 'Stored new bill ":name"', + 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.', + 'rescanned_bill' => 'Rescanned everything.', + 'average_bill_amount_year' => 'Average bill amount (:year)', + 'average_bill_amount_overall' => 'Average bill amount (overall)', + 'not_or_not_yet' => 'Not (yet)', + 'not_expected_period' => 'Not expected this period', + // accounts: + 'details_for_asset' => 'Details for asset account ":name"', + 'details_for_expense' => 'Details for expense account ":name"', + 'details_for_revenue' => 'Details for revenue account ":name"', + 'details_for_cash' => 'Details for cash account ":name"', + 'store_new_asset_account' => 'Store new asset account', + 'store_new_expense_account' => 'Store new expense account', + 'store_new_revenue_account' => 'Store new revenue account', + 'edit_asset_account' => 'Edit asset account ":name"', + 'edit_expense_account' => 'Edit expense account ":name"', + 'edit_revenue_account' => 'Edit revenue account ":name"', + 'delete_asset_account' => 'Delete asset account ":name"', + 'delete_expense_account' => 'Delete expense account ":name"', + 'delete_revenue_account' => 'Delete revenue account ":name"', + 'asset_deleted' => 'Successfully deleted asset account ":name"', + 'expense_deleted' => 'Successfully deleted expense account ":name"', + 'revenue_deleted' => 'Successfully deleted revenue account ":name"', + 'update_asset_account' => 'Update asset account', + 'update_expense_account' => 'Update expense account', + 'update_revenue_account' => 'Update revenue account', + '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', + 'asset_accounts' => 'Asset accounts', + 'expense_accounts' => 'Expense accounts', + 'revenue_accounts' => 'Revenue accounts', + 'cash_accounts' => 'Cash accounts', + 'Cash account' => 'Cash account', + 'account_type' => 'Account type', + 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', + 'stored_new_account' => 'New account ":name" stored!', + 'updated_account' => 'Updated account ":name"', + 'credit_card_options' => 'Credit card options', + 'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".', + 'no_data_for_chart' => 'There is not enough information (yet) to generate this chart.', + 'select_more_than_one_account' => 'Please select more than one account', + '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', + 'from_to' => 'From :start to :end', + 'from_to_breadcrumb' => 'from :start to :end', + 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', + + // categories: + 'new_category' => 'New category', + 'create_new_category' => 'Create a new category', + 'without_category' => 'Without a category', + 'update_category' => 'Update category', + 'updated_category' => 'Updated category ":name"', + 'categories' => 'Categories', + 'edit_category' => 'Edit category ":name"', + 'no_category' => '(no category)', + 'category' => 'Category', + 'delete_category' => 'Delete category ":name"', + 'deleted_category' => 'Deleted category ":name"', + 'store_category' => 'Store new category', + 'stored_category' => 'Stored new category ":name"', + 'without_category_between' => 'Without category between :start and :end', + + // transactions: + 'update_withdrawal' => 'Update withdrawal', + 'update_deposit' => 'Update deposit', + 'update_transfer' => 'Update transfer', + 'updated_withdrawal' => 'Updated withdrawal ":description"', + 'updated_deposit' => 'Updated deposit ":description"', + 'updated_transfer' => 'Updated transfer ":description"', + 'delete_withdrawal' => 'Delete withdrawal ":description"', + 'delete_deposit' => 'Delete deposit ":description"', + 'delete_transfer' => 'Delete transfer ":description"', + 'deleted_withdrawal' => 'Successfully deleted withdrawal ":description"', + 'deleted_deposit' => 'Successfully deleted deposit ":description"', + 'deleted_transfer' => 'Successfully deleted transfer ":description"', + 'stored_journal' => 'Successfully created new transaction ":description"', + 'select_transactions' => 'Select transactions', + 'stop_selection' => 'Stop selecting transactions', + 'edit_selected' => 'Edit selected', + 'delete_selected' => 'Delete selected', + 'mass_delete_journals' => 'Delete a number of transactions', + 'mass_edit_journals' => 'Edit a number of transactions', + 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', + 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', + 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', + + + // new user: + 'welcome' => 'Welcome to Firefly!', + + // home page: + 'yourAccounts' => 'Your accounts', + 'budgetsAndSpending' => 'Budgets and spending', + 'savings' => 'Savings', + 'markAsSavingsToContinue' => 'Mark your asset accounts as "Savings account" to fill this panel', + 'createPiggyToContinue' => 'Create piggy banks to fill this panel.', + 'newWithdrawal' => 'New expense', + 'newDeposit' => 'New deposit', + 'newTransfer' => 'New transfer', + 'moneyIn' => 'Money in', + 'moneyOut' => 'Money out', + 'billsToPay' => 'Bills to pay', + 'billsPaid' => 'Bills paid', + 'divided' => 'divided', + 'toDivide' => 'left to divide', + + // menu and titles, should be recycled as often as possible: + 'currency' => 'Currency', + 'preferences' => 'Preferences', + 'logout' => 'Logout', + 'searchPlaceholder' => 'Search...', + 'dashboard' => 'Dashboard', + 'currencies' => 'Currencies', + 'accounts' => 'Accounts', + 'Asset account' => 'Asset account', + 'Default account' => 'Asset account', + 'Expense account' => 'Expense account', + 'Revenue account' => 'Revenue account', + 'Initial balance account' => 'Initial balance account', + 'budgets' => 'Budgets', + 'tags' => 'Tags', + 'reports' => 'Reports', + 'transactions' => 'Transactions', + 'expenses' => 'Expenses', + 'income' => 'Revenue / income', + 'transfers' => 'Transfers', + 'moneyManagement' => 'Money management', + 'piggyBanks' => 'Piggy banks', + 'bills' => 'Bills', + 'withdrawal' => 'Withdrawal', + 'deposit' => 'Deposit', + 'account' => 'Account', + 'transfer' => 'Transfer', + 'Withdrawal' => 'Withdrawal', + 'Deposit' => 'Deposit', + 'Transfer' => 'Transfer', + 'bill' => 'Bill', + 'yes' => 'Yes', + 'no' => 'No', + 'amount' => 'Amount', + 'overview' => 'Overview', + 'saveOnAccount' => 'Save on account', + 'unknown' => 'Unknown', + 'daily' => 'Daily', + 'monthly' => 'Monthly', + 'profile' => 'Profile', + 'errors' => 'Errors', + + // reports: + 'report_default' => 'Default financial report between :start and :end', + 'report_audit' => 'Transaction history overview between :start and :end', + 'report_category' => 'Category report between :start and :end', + 'report_budget' => 'Budget report between :start and :end', + 'report_tag' => 'Tag report between :start and :end', + 'quick_link_reports' => 'Quick links', + 'quick_link_default_report' => 'Default financial report', + 'quick_link_audit_report' => 'Transaction history overview', + 'report_this_month_quick' => 'Current month, all accounts', + 'report_this_year_quick' => 'Current year, all accounts', + 'report_this_fiscal_year_quick' => 'Current fiscal year, all accounts', + 'report_all_time_quick' => 'All-time, all accounts', + 'reports_can_bookmark' => 'Remember that reports can be bookmarked.', + 'incomeVsExpenses' => 'Income vs. expenses', + 'accountBalances' => 'Account balances', + 'balanceStartOfYear' => 'Balance at start of year', + 'balanceEndOfYear' => 'Balance at end of year', + 'balanceStartOfMonth' => 'Balance at start of month', + 'balanceEndOfMonth' => 'Balance at end of month', + 'balanceStart' => 'Balance at start of period', + 'balanceEnd' => 'Balance at end of period', + 'reportsOwnAccounts' => 'Reports for your own accounts', + 'reportsOwnAccountsAndShared' => 'Reports for your own accounts and shared accounts', + 'splitByAccount' => 'Split by account', + 'balancedByTransfersAndTags' => 'Balanced by transfers and tags', + 'coveredWithTags' => 'Covered with tags', + 'leftUnbalanced' => 'Left unbalanced', + 'expectedBalance' => 'Expected balance', + 'outsideOfBudgets' => 'Outside of budgets', + 'leftInBudget' => 'Left in budget', + 'sumOfSums' => 'Sum of sums', + 'noCategory' => '(no category)', + 'notCharged' => 'Not charged (yet)', + 'inactive' => 'Inactive', + 'active' => 'Active', + 'difference' => 'Difference', + 'in' => 'In', + 'out' => 'Out', + 'topX' => 'top :number', + 'show_full_list' => 'Show entire list', + 'show_only_top' => 'Show only top :number', + 'sum_of_year' => 'Sum of year', + 'sum_of_years' => 'Sum of years', + 'average_of_year' => 'Average of year', + 'average_of_years' => 'Average of years', + 'categories_earned_in_year' => 'Categories (by earnings)', + 'categories_spent_in_year' => 'Categories (by spendings)', + 'report_type' => 'Report type', + 'report_type_default' => 'Default financial report', + 'report_type_audit' => 'Transaction history overview (audit)', + 'report_type_category' => 'Category report', + 'report_type_budget' => 'Budget report', + 'report_type_tag' => 'Tag report', + 'report_type_meta-history' => 'Categories, budgets and bills overview', + 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', + 'report_included_accounts' => 'Included accounts', + 'report_date_range' => 'Date range', + 'report_preset_ranges' => 'Pre-set ranges', + 'shared' => 'Shared', + 'fiscal_year' => 'Fiscal year', + 'income_entry' => 'Income from account ":name" between :start and :end', + 'expense_entry' => 'Expenses to account ":name" between :start and :end', + 'category_entry' => 'Expenses in category ":name" between :start and :end', + 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', + 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', + 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', + 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', + 'reports_extra_options' => 'Extra options', + 'report_has_no_extra_options' => 'This report has no extra options', + 'reports_submit' => 'View report', + 'end_after_start_date' => 'End date of report must be after start date.', + 'select_category' => 'Select category(ies)', + 'select_budget' => 'Select budget(s).', + 'select_tag' => 'Select tag(s).', + 'income_per_category' => 'Income per category', + 'expense_per_category' => 'Expense per category', + 'expense_per_budget' => 'Expense per budget', + 'income_per_account' => 'Income per account', + 'expense_per_account' => 'Expense per account', + 'expense_per_tag' => 'Expense per tag', + 'income_per_tag' => 'Income per tag', + 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', + 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', + 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', + 'include_income_not_in_category' => 'Included income not in the selected category(ies)', + 'include_income_not_in_account' => 'Included income not in the selected account(s)', + 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', + 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', + 'everything_else' => 'Everything else', + 'income_and_expenses' => 'Income and expenses', + 'spent_average' => 'Spent (average)', + 'income_average' => 'Income (average)', + 'transaction_count' => 'Transaction count', + 'average_spending_per_account' => 'Average spending per account', + 'average_income_per_account' => 'Average income per account', + 'total' => 'Total', + 'description' => 'Description', + 'sum_of_period' => 'Sum of period', + 'average_in_period' => 'Average in period', + 'account_role_defaultAsset' => 'Default asset account', + 'account_role_sharedAsset' => 'Shared asset account', + 'account_role_savingAsset' => 'Savings account', + 'account_role_ccAsset' => 'Credit card', + + // charts: + 'chart' => 'Chart', + 'dayOfMonth' => 'Day of the month', + 'month' => 'Month', + 'budget' => 'Budget', + 'spent' => 'Spent', + 'spent_in_budget' => 'Spent in budget', + 'left_to_spend' => 'Left to spend', + 'earned' => 'Earned', + 'overspent' => 'Overspent', + 'left' => 'Left', + 'no_budget' => '(no budget)', + 'max-amount' => 'Maximum amount', + 'min-amount' => 'Minumum amount', + 'journal-amount' => 'Current bill entry', + 'name' => 'Name', + 'date' => 'Date', + 'paid' => 'Paid', + 'unpaid' => 'Unpaid', + 'day' => 'Day', + 'budgeted' => 'Budgeted', + 'period' => 'Period', + 'balance' => 'Balance', + 'summary' => 'Summary', + 'sum' => 'Sum', + 'average' => 'Average', + 'balanceFor' => 'Balance for :name', + + // piggy banks: + 'add_money_to_piggy' => 'Add money to piggy bank ":name"', + 'piggy_bank' => 'Piggy bank', + 'new_piggy_bank' => 'New piggy bank', + 'store_piggy_bank' => 'Store new piggy bank', + 'stored_piggy_bank' => 'Store new piggy bank ":name"', + 'account_status' => 'Account status', + 'left_for_piggy_banks' => 'Left for piggy banks', + 'sum_of_piggy_banks' => 'Sum of piggy banks', + 'saved_so_far' => 'Saved so far', + 'left_to_save' => 'Left to save', + 'suggested_amount' => 'Suggested monthly amount to save', + 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', + 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', + 'add' => 'Add', + + 'remove' => 'Remove', + 'max_amount_add' => 'The maximum amount you can add is', + 'max_amount_remove' => 'The maximum amount you can remove is', + 'update_piggy_button' => 'Update piggy bank', + 'update_piggy_title' => 'Update piggy bank ":name"', + 'updated_piggy_bank' => 'Updated piggy bank ":name"', + 'details' => 'Details', + 'events' => 'Events', + 'target_amount' => 'Target amount', + 'start_date' => 'Start date', + 'target_date' => 'Target date', + 'no_target_date' => 'No target date', + 'todo' => 'to do', + 'table' => 'Table', + 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', + 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', + 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', + 'delete_piggy_bank' => 'Delete piggy bank ":name"', + 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', + 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', + 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', + 'added_amount_to_piggy' => 'Added :amount to ":name"', + 'removed_amount_from_piggy' => 'Removed :amount from ":name"', + 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".', + + // tags + 'regular_tag' => 'Just a regular tag.', + 'balancing_act' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.', + 'advance_payment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.', + 'delete_tag' => 'Delete tag ":tag"', + 'deleted_tag' => 'Deleted tag ":tag"', + 'new_tag' => 'Make new tag', + 'edit_tag' => 'Edit tag ":tag"', + 'updated_tag' => 'Updated tag ":tag"', + 'created_tag' => 'Tag ":tag" has been created!', + 'no_year' => 'No year set', + 'no_month' => 'No month set', + 'tag_title_nothing' => 'Default tags', + 'tag_title_balancingAct' => 'Balancing act tags', + 'tag_title_advancePayment' => 'Advance payment tags', + 'tags_introduction' => 'Usually tags are singular words, designed to quickly band items together using things like expensive, bill or for-party. In Firefly III, tags can have more properties such as a date, description and location. This allows you to join transactions together in a more meaningful way. For example, you could make a tag called Christmas dinner with friends and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion, perhaps with multiple transactions.', + 'tags_group' => 'Tags group transactions together, which makes it possible to store reimbursements (in case you front money for others) and other "balancing acts" where expenses are summed up (the payments on your new TV) or where expenses and deposits are cancelling each other out (buying something with saved money). It\'s all up to you. Using tags the old-fashioned way is of course always possible.', + 'tags_start' => 'Create a tag to get started or enter tags when creating new transactions.', + + 'transaction_journal_information' => 'Transaction information', + 'transaction_journal_meta' => 'Meta information', + 'total_amount' => 'Total amount', + 'number_of_decimals' => 'Number of decimals', + + // administration + 'administration' => 'Administration', + 'user_administration' => 'User administration', + 'list_all_users' => 'All users', + 'all_users' => 'All users', + 'instance_configuration' => 'Configuration', + 'firefly_instance_configuration' => 'Configuration options for Firefly III', + 'setting_single_user_mode' => 'Single user mode', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as wel, assuming they can reach it (when it is connected to the internet).', + 'store_configuration' => 'Store configuration', + 'single_user_administration' => 'User administration for :email', + 'edit_user' => 'Edit user :email', + 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', + 'user_data_information' => 'User data', + 'user_information' => 'User information', + 'total_size' => 'total size', + 'budget_or_budgets' => 'budget(s)', + 'budgets_with_limits' => 'budget(s) with configured amount', + 'rule_or_rules' => 'rule(s)', + 'rulegroup_or_groups' => 'rule group(s)', + 'setting_must_confirm_account' => 'Account confirmation', + 'setting_must_confirm_account_explain' => 'When this setting is enabled, users must activate their account before it can be used.', + 'configuration_updated' => 'The configuration has been updated', + 'setting_is_demo_site' => 'Demo site', + 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', + 'setting_send_email_notifications' => 'Send email notifications', + 'setting_send_email_explain' => 'Firefly III can send you email notifications about certain events. They will be sent to :site_owner. This email address can be set in the .env file.', + 'block_code_bounced' => 'Email message(s) bounced', + 'block_code_expired' => 'Demo account expired', + 'no_block_code' => 'No reason for block or user not blocked', + + + // split a transaction: + 'transaction_meta_data' => 'Transaction meta-data', + 'transaction_dates' => 'Transaction dates', + 'splits' => 'Splits', + 'split_title_withdrawal' => 'Split your new withdrawal', + 'split_intro_one_withdrawal' => 'Firefly supports the "splitting" of a withdrawal.', + 'split_intro_two_withdrawal' => 'It means that the amount of money you\'ve spent is divided between several destination expense accounts, budgets or categories.', + 'split_intro_three_withdrawal' => 'For example: you could split your :total groceries so you pay :split_one from your "daily groceries" budget and :split_two from your "cigarettes" budget.', + 'split_table_intro_withdrawal' => 'Split your withdrawal in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_withdrawal' => 'Store splitted withdrawal', + 'update_splitted_withdrawal' => 'Update splitted withdrawal', + 'split_title_deposit' => 'Split your new deposit', + 'split_intro_one_deposit' => 'Firefly supports the "splitting" of a deposit.', + 'split_intro_two_deposit' => 'It means that the amount of money you\'ve earned is divided between several source revenue accounts or categories.', + 'split_intro_three_deposit' => 'For example: you could split your :total salary so you get :split_one as your base salary and :split_two as a reimbursment for expenses made.', + 'split_table_intro_deposit' => 'Split your deposit in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_deposit' => 'Store splitted deposit', + 'split_title_transfer' => 'Split your new transfer', + 'split_intro_one_transfer' => 'Firefly supports the "splitting" of a transfer.', + 'split_intro_two_transfer' => 'It means that the amount of money you\'re moving is divided between several categories or piggy banks.', + 'split_intro_three_transfer' => 'For example: you could split your :total move so you get :split_one in one piggy bank and :split_two in another.', + 'split_table_intro_transfer' => 'Split your transfer in as many things as you want. By default the transaction will not split, there is just one entry. Add as many splits as you want to, below. Remember that you should not deviate from your total amount. If you do, Firefly will warn you but not correct you.', + 'store_splitted_transfer' => 'Store splitted transfer', + 'add_another_split' => 'Add another split', + 'split-transactions' => 'Split transactions', + 'split-new-transaction' => 'Split a new transaction', + 'do_split' => 'Do a split', + 'split_this_withdrawal' => 'Split this withdrawal', + 'split_this_deposit' => 'Split this deposit', + 'split_this_transfer' => 'Split this transfer', + 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', + 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', + + // import + 'configuration_file_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.', + 'import_data_index' => 'Index', + 'import_file_type_csv' => 'CSV (comma separated values)', + 'import_file_type_help' => 'Select the type of file you will upload', + 'import_start' => 'Start the import', + 'configure_import' => 'Further configure your import', + 'import_finish_configuration' => 'Finish configuration', + 'settings_for_import' => 'Settings', + 'import_status' => 'Import status', + 'import_status_text' => 'The import is currently running, or will start momentarily.', + 'import_complete' => 'Import configuration complete!', + 'import_complete_text' => 'The import is ready to start. All the configuration you needed to do has been done. Please download the configuration file. It will help you with the import should it not go as planned. To actually run the import, you can either execute the following command in your console, or run the web-based import. Depending on your configuration, the console import will give you more feedback.', + 'import_download_config' => 'Download configuration', + 'import_start_import' => 'Start import', + 'import_data' => 'Import data', + 'import_data_full' => 'Import data into Firefly III', + 'import' => 'Import', + 'import_file_help' => 'Select your file', + 'import_status_settings_complete' => 'The import is ready to start.', + 'import_status_import_complete' => 'The import has completed.', + 'import_status_import_running' => 'The import is currently running. Please be patient.', + 'import_status_header' => 'Import status and progress', + 'import_status_errors' => 'Import errors', + 'import_status_report' => 'Import report', + 'import_finished' => 'Import has finished', + 'import_error_single' => 'An error has occured during the import.', + 'import_error_multi' => 'Some errors occured during the import.', + 'import_error_fatal' => 'There was an error during the import routine. Please check the log files. The error seems to be:', + 'import_error_timeout' => 'The import seems to have timed out. If this error persists, please import your data using the console command.', + 'import_double' => 'Row #:row: This row has been imported before, and is stored in :description.', + 'import_finished_all' => 'The import has finished. Please check out the results below.', + 'import_with_key' => 'Import with key \':key\'', + 'import_share_configuration' => 'Please consider downloading your configuration and sharing it at the import configuration center. This will allow other users of Firefly III to import their files more easily.', + 'import_finished_report' => 'The import has finished. Please note any errors in the block above this line. All transactions imported during this particular session have been tagged, and you can check them out below. ', + 'import_finished_link' => 'The transactions imported can be found in tag :tag.', + 'need_at_least_one_account' => 'You need at least one asset account to be able to create piggy banks', + 'see_help_top_right' => 'For more information, please check out the help pages using the icon in the top right corner of the page.', + 'bread_crumb_import_complete' => 'Import ":key" complete', + 'bread_crumb_configure_import' => 'Configure import ":key"', + 'bread_crumb_import_finished' => 'Import ":key" finished', + 'import_finished_intro' => 'The import has finished! You can now see the new transactions in Firefly.', + 'import_finished_text_without_link' => 'It seems there is no tag that points to all your imported transactions. Please look for your imported data in the menu on the left, under "Transactions".', + 'import_finished_text_with_link' => 'You can find a list of your imported transactions on the page of the tag that was created for this import.', + + // sandstorm.io errors and messages: + 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', + + // empty lists? no objects? instructions: + 'no_transactions_in_period' => 'There are no transactions in this period.', + 'no_accounts_title_asset' => 'Let\'s create an asset account!', + 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', + 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', + 'no_accounts_create_asset' => 'Create an asset account', + 'no_accounts_title_expense' => 'Let\'s create an expense account!', + 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', + 'no_accounts_imperative_expense' => 'Expense 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_expense' => 'Create an expense account', + 'no_accounts_title_revenue' => 'Let\'s create a revenue account!', + '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' => 'Expense 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_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:', + 'no_budgets_create_default' => 'Create a budget', + 'no_categories_title_default' => 'Let\'s create a category!', + 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', + 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', + 'no_categories_create_default' => 'Create a category', + 'no_tags_title_default' => 'Let\'s create a tag!', + 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', + 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', + 'no_tags_create_default' => 'Create a tag', + 'no_transactions_title_withdrawal' => 'Let\'s create an expense!', + 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', + 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', + 'no_transactions_create_withdrawal' => 'Create an expense', + 'no_transactions_title_deposit' => 'Let\'s create some income!', + 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', + 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', + 'no_transactions_create_deposit' => 'Create a deposit', + 'no_transactions_title_transfers' => 'Let\'s create a transfer!', + 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', + 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', + 'no_transactions_create_transfers' => 'Create a transfer', + 'no_piggies_title_default' => 'Let\'s create a piggy bank!', + 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', + 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', + 'no_piggies_create_default' => 'Create a new piggy bank', + 'no_bills_title_default' => 'Let\'s create a bill!', + 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent of insurance.', + 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', + 'no_bills_create_default' => 'Create a bill', + + +]; \ No newline at end of file From 4adcbf9e48e9b283d8a9dc7f7184ba293c1b98ef Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:20:11 +0200 Subject: [PATCH 037/103] New translations demo.php (Spanish) --- resources/lang/es_ES/demo.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/es_ES/demo.php b/resources/lang/es_ES/demo.php index 57bb9cdcb0..f033a24a0b 100644 --- a/resources/lang/es_ES/demo.php +++ b/resources/lang/es_ES/demo.php @@ -14,11 +14,11 @@ return [ 'accounts-index' => 'Las cajas de ahorro son sus cuentas de banco personales. Las cuentas de gastos contienen sus gastos habituales como compras y salidas con amigos. Las cuentas de ingresos repesentan ingresos de su trabajo u otras fuentes. En esta página puede editarlas o eliminarlas.', 'budgets-index' => 'Esta página le muestra una visión general de sus presupuestos. La barra superior muestra la cantidad que está disponible para ser presupuestado. Esto se puede personalizar para cualquier período haciendo clic en la cantidad a la derecha. La cantidad que has gastado hasta ahora se muestra en la barra de abajo. Debajo están los gastos por presupuesto y lo que ha presupuestado para ellos.', 'reports-index-start' => 'Firefly III admite cuatro tipos de informes. Lea sobre ellos haciendo clic en el icono en la esquina superior derecha.', - 'reports-index-examples' => 'Be sure to check out these examples: a monthly financial overview, a yearly financial overview and a budget overview.', + 'reports-index-examples' => 'Asegúrese de revisar estos ejemplos: un resumen financiero mensual , un resumen financiero anual y una vista general del presupuesto.', 'currencies-index' => 'Firefly III admite múltiples monedas. A pesar de que la moneda por defecto es el Euro, se puede seleccionar el Dólar de EE.UU, y muchas otras monedas. Como se puede ver se ha incluido una pequeña selección de monedas, pero puedes agregar tu propia moneda si lo deseas. Sin embargo, cambiar la moneda predeterminada no cambiará la moneda de las transacciones existentes: Firefly III admite el uso de varias monedas al mismo tiempo.', 'transactions-index' => 'Estos gastos, depósitos y transferencias no son particularmente imaginativos. Se han generado automáticamente.', 'piggy-banks-index' => 'Como puede ver, hay tres alcancías. Utilice los botones más y menos para influir en la cantidad de dinero en cada alcancía. Haga clic en el nombre de la alcancía para ver la administración de cada una.', - 'import-index' => 'Of course, any CSV file can be imported into Firefly III ', - 'import-configure-security' => 'Because of security concerns, your upload has been replaced with a local file.', + 'import-index' => 'Por supuesto, cualquier archivo CSV puede ser importado en Firefly III', + 'import-configure-security' => 'Debido a problemas de seguridad, su subida se ha sustituido por un archivo local.', 'import-configure-configuration' => 'La configuración que ves a continuación es correcta para el archivo local.', ]; \ No newline at end of file From 61535bf4b8e0c4cb1682bb2f56fa7a8c75078e6c Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:30:08 +0200 Subject: [PATCH 038/103] New translations firefly.php (Spanish) --- resources/lang/es_ES/firefly.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 0c136971fd..3a45d7aa57 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -53,14 +53,14 @@ return [ 'flash_info_multiple' => 'Hay un mensaje | Hay: mensajes de la cuenta', 'flash_error_multiple' => 'Hay un error | Hay: contar errores', 'net_worth' => 'Valor Neto', - 'route_has_no_help' => 'There is no help for this route.', - 'help_may_not_be_your_language' => 'This help text is in English. It is not yet available in your language', - 'two_factor_welcome' => 'Hello, :user!', - 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', - 'two_factor_code_here' => 'Enter code here', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', + 'route_has_no_help' => 'No hay datos en este registro.', + 'help_may_not_be_your_language' => 'Este texto de ayuda está en inglés. Aún no está disponible en tu idioma', + 'two_factor_welcome' => 'Hola %{user}!', + 'two_factor_enter_code' => 'Para continuar, introduce tu código de autenticación de dos pasos. La aplicación puede generarlo para usted.', + 'two_factor_code_here' => 'Ingresar código aquí', + 'two_factor_title' => 'Autenticación en dos pasos', + 'authenticate' => 'Autentificar', + 'two_factor_forgot_title' => 'Autenticación en dos pasos perdida', 'two_factor_forgot' => 'I forgot my two-factor thing.', 'two_factor_lost_header' => 'Lost your two factor authentication?', 'two_factor_lost_intro' => 'Unfortunately, this is not something you can reset from the web interface. You have two choices.', From 70cd8ffb721392240da5aa9ef98e67f815596d5f Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:30:10 +0200 Subject: [PATCH 039/103] New translations validation.php (Spanish) --- resources/lang/es_ES/validation.php | 42 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index e5ef49397a..81a33dac21 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -11,26 +11,26 @@ return [ 'iban' => 'This is not a valid CBU.', - 'unique_account_number_for_user' => 'It looks like this account number is already in use.', - 'deleted_user' => 'Due to security constraints, you cannot register using this email address.', - 'rule_trigger_value' => 'This value is invalid for the selected trigger.', - 'rule_action_value' => 'This value is invalid for the selected action.', - 'invalid_domain' => 'Due to security constraints, you cannot register from this domain.', - 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', - 'file_attached' => 'Succesfully uploaded file ":name".', - 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', - 'file_too_large' => 'File ":name" is too large.', - 'belongs_to_user' => 'The value of :attribute is unknown', - 'accepted' => 'The :attribute must be accepted.', - 'bic' => 'This is not a valid BIC.', - 'more' => ':attribute must be larger than zero.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'unique_for_user' => 'There already is an entry with this :attribute.', + 'unique_account_number_for_user' => 'Parece que este número de cuenta ya está en uso.', + 'deleted_user' => 'Debido a restricciones de seguridad, no se puede registrar utilizando esta dirección de correo electrónico.', + 'rule_trigger_value' => 'Este valor es válido para el disparador seleccionado.', + 'rule_action_value' => 'Este valor es inválido para la acción seleccionado.', + 'invalid_domain' => 'Debido a restricciones de seguridad, no se puede registrar utilizando este dominio.', + 'file_already_attached' => 'Archivo ":name" ya ha sido añadido para este objeto.', + 'file_attached' => 'Archivo subido correctamente ": nombre".', + 'file_invalid_mime' => 'Archivo ":name" es de tipo": mime" que no es aceptado como una nueva carga.', + 'file_too_large' => 'Archivo ":name" es demasiado grande.', + 'belongs_to_user' => 'El valor de :attribute es desconocido', + 'accepted' => 'El :attribute debe ser aceptado.', + 'bic' => 'Esto no es un BIC válido.', + 'more' => ':attribute debe ser mayor que cero.', + 'active_url' => 'El campo :attribute no es una URL válida.', + 'after' => 'El campo :attribute debe ser una fecha posterior a :date.', + 'alpha' => 'El campo :attribute sólo puede contener letras.', + 'alpha_dash' => 'El campo :attribute sólo puede contener letras, números y guiones.', + 'alpha_num' => 'El campo :attribute sólo puede contener letras y números.', + 'array' => 'El campo :attribute debe ser un arreglo.', + 'unique_for_user' => 'Ya hay una entrada con esto :attribute.', 'before' => 'The :attribute must be a date before :date.', 'unique_object_for_user' => 'This name is already in use', 'unique_account_for_user' => 'This account name is already in use', @@ -88,4 +88,4 @@ return [ 'in_array' => 'The :attribute field does not exist in :other.', 'present' => 'The :attribute field must be present.', 'amount_zero' => 'The total amount cannot be zero', -]; +]; \ No newline at end of file From 0fad9d4ac74cc9d6cb08de4cfac5c9aeaaeff014 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:40:07 +0200 Subject: [PATCH 040/103] New translations validation.php (Spanish) --- resources/lang/es_ES/validation.php | 48 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 81a33dac21..d2ccbc2c92 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -31,30 +31,30 @@ return [ 'alpha_num' => 'El campo :attribute sólo puede contener letras y números.', 'array' => 'El campo :attribute debe ser un arreglo.', 'unique_for_user' => 'Ya hay una entrada con esto :attribute.', - 'before' => 'The :attribute must be a date before :date.', - 'unique_object_for_user' => 'This name is already in use', - 'unique_account_for_user' => 'This account name is already in use', - 'between.numeric' => 'The :attribute must be between :min and :max.', - 'between.file' => 'The :attribute must be between :min and :max kilobytes.', - 'between.string' => 'The :attribute must be between :min and :max characters.', - 'between.array' => 'The :attribute must have between :min and :max items.', - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'email' => 'The :attribute must be a valid email address.', - 'filled' => 'The :attribute field is required.', - 'exists' => 'The selected :attribute is invalid.', - 'image' => 'The :attribute must be an image.', - 'in' => 'The selected :attribute is invalid.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'json' => 'The :attribute must be a valid JSON string.', - 'max.numeric' => 'The :attribute may not be greater than :max.', - 'max.file' => 'The :attribute may not be greater than :max kilobytes.', + 'before' => 'El campo :attribute debe contener una fecha anterior a :date.', + 'unique_object_for_user' => 'Este nombre ya está en uso', + 'unique_account_for_user' => 'Este nombre cuenta ya está en uso', + 'between.numeric' => 'El atributo :attribute debe estar entre :min y :max.', + 'between.file' => 'El atributo :attribute debe estar entre :min y :max kilobytes.', + 'between.string' => 'El atributo :attribute debe estar entre :min y :max caracteres.', + 'between.array' => 'El atributo :attribute debe estar entre :min y :max items.', + 'boolean' => 'El campo :attribute debe ser verdadero o falso.', + 'confirmed' => 'La confirmación de :attribute no coincide.', + 'date' => 'El campo :attribute no es una fecha válida.', + 'date_format' => 'El campo :attribute no corresponde con el formato :format.', + 'different' => 'Los campos :attribute y :other han de ser diferentes.', + 'digits' => 'El campo :attribute debe contener un número de :digits dígitos.', + 'digits_between' => 'El campo :attribute debe contener entre :min y :max dígitos.', + 'email' => 'El campo :attribute no corresponde con una dirección de e-mail válida.', + 'filled' => 'El campo :attribute es requerido.', + 'exists' => 'El campo :attribute seleccionado no es correcto.', + 'image' => 'El campo :attribute debe ser una imagen.', + 'in' => 'El campo :attribute seleccionado es inválido.', + 'integer' => 'El campo :attribute debe ser un entero.', + 'ip' => 'El campo :attribute debe contener una dirección IP válida.', + 'json' => 'El campo :attribute debe ser una cadena JSON válida.', + 'max.numeric' => 'El campo :attribute no puede ser mayor que :max.', + 'max.file' => 'El campo :attribute no puede ser mayor :max de kilobytes.', 'max.string' => 'The :attribute may not be greater than :max characters.', 'max.array' => 'The :attribute may not have more than :max items.', 'mimes' => 'The :attribute must be a file of type: :values.', From c3ff69d1478b3c2dc5efdf97fa9e875056a5c0f8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:40:09 +0200 Subject: [PATCH 041/103] New translations list.php (Spanish) --- resources/lang/es_ES/list.php | 89 +++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 resources/lang/es_ES/list.php diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php new file mode 100644 index 0000000000..df53bfd0c9 --- /dev/null +++ b/resources/lang/es_ES/list.php @@ -0,0 +1,89 @@ + 'Botones', + 'icon' => 'Icono', + 'id' => 'ID', + 'create_date' => 'Fecha de creación', + 'update_date' => 'Fecha de modificación', + 'balance_before' => 'Balance antes de ', + 'balance_after' => 'Balance después de la', + 'name' => 'Nombre', + 'role' => 'Rol', + 'currentBalance' => 'Balance actual', + 'active' => '¿Está Activo?', + 'lastActivity' => 'Actividad más reciente', + 'balanceDiff' => 'Diferencia de saldo entre :start y :end', + 'matchedOn' => 'Comparado en', + 'matchesOn' => 'Comparado en', + 'account_type' => 'Tipo de cuenta', + 'created_at' => 'Fecha de creación', + 'new_balance' => 'New balance', + 'account' => 'Account', + 'matchingAmount' => 'Amount', + 'lastMatch' => 'Last match', + 'split_number' => 'Split #', + 'destination' => 'Destination', + 'source' => 'Source', + 'next_expected_match' => 'Next expected match', + 'automatch' => 'Auto match?', + 'repeat_freq' => 'Repeats', + 'description' => 'Description', + 'amount' => 'Amount', + 'internal_reference' => 'Internal reference', + 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', + 'due_date' => 'Due date', + 'payment_date' => 'Payment date', + 'invoice_date' => 'Invoice date', + 'interal_reference' => 'Internal reference', + 'notes' => 'Notes', + 'from' => 'From', + 'piggy_bank' => 'Piggy bank', + 'to' => 'To', + 'budget' => 'Budget', + 'category' => 'Category', + 'bill' => 'Bill', + 'withdrawal' => 'Withdrawal', + 'deposit' => 'Deposit', + 'transfer' => 'Transfer', + 'type' => 'Type', + 'completed' => 'Completed', + 'iban' => 'IBAN', + 'paid_current_period' => 'Paid this period', + 'email' => 'Email', + 'registered_at' => 'Registered at', + 'is_activated' => 'Is activated', + 'is_blocked' => 'Is blocked', + 'is_admin' => 'Is admin', + 'has_two_factor' => 'Has 2FA', + 'confirmed_from' => 'Confirmed from', + 'registered_from' => 'Registered from', + 'blocked_code' => 'Block code', + 'domain' => 'Domain', + 'registration_attempts' => 'Registration attempts', + 'source_account' => 'Source account', + 'destination_account' => 'Destination account', + + 'accounts_count' => 'Number of accounts', + 'journals_count' => 'Number of transactions', + 'attachments_count' => 'Number of attachments', + 'bills_count' => 'Number of bills', + 'categories_count' => 'Number of categories', + 'export_jobs_count' => 'Number of export jobs', + 'import_jobs_count' => 'Number of import jobs', + 'budget_count' => 'Number of budgets', + 'rule_and_groups_count' => 'Number of rules and rule groups', + 'tags_count' => 'Number of tags', +]; \ No newline at end of file From 94939ea3d31b88f0c67f96cb7c4ce6a3304bc6a4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:40:13 +0200 Subject: [PATCH 042/103] New translations firefly.php (Portuguese, Brazilian) --- resources/lang/pt_BR/firefly.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 021c1955ca..7d7ee85b8c 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -26,8 +26,8 @@ return [ 'showEverything' => 'Mostrar tudo', 'never' => 'Nunca', 'search_results_for' => 'Pesquisar resultados por ":query"', - 'advanced_search' => 'Advanced search', - 'advanced_search_intro' => 'There are several modifiers that you can use in your search to narrow down the results. If you use any of these, the search will only return transactions. Please click the -icon for more information.', + 'advanced_search' => 'Busca avançada', + 'advanced_search_intro' => 'Existem vários modificadores que você pode usar em sua busca para limitar os resultados. Se você usar qualquer um destes, a busca vai apenas transações de retorno. Por favor, clique no -ícone para obter mais informações.', 'bounced_error' => 'A mensagem enviado para :email ressaltado, não tem acesso para você.', 'deleted_error' => 'Estas credenciais não correspondem aos nossos registros.', 'general_blocked_error' => 'Sua conta foi desativada, você não pode entrar.', @@ -69,12 +69,12 @@ return [ 'warning_much_data' => ':days dias de dados podem demorar um pouco para carregar.', 'registered' => 'Você se registrou com sucesso!', 'search' => 'Pesquisa', - 'search_found_accounts' => 'Found :count account(s) for your query.', - 'search_found_categories' => 'Found :count category(ies) for your query.', - 'search_found_budgets' => 'Found :count budget(s) for your query.', - 'search_found_tags' => 'Found :count tag(s) for your query.', - 'search_found_transactions' => 'Found :count transaction(s) for your query.', - 'results_limited' => 'The results are limited to :count entries.', + 'search_found_accounts' => 'Encontrado: contar conta (s) para sua consulta.', + 'search_found_categories' => 'Encontrado: contar categoria(s) para sua consulta.', + 'search_found_budgets' => 'Encontrado: contagem despesa(s) para a sua consulta.', + 'search_found_tags' => 'Encontrado: contar Tag (s) para sua consulta.', + 'search_found_transactions' => 'Encontrado: contagem de transação para a sua consulta.', + 'results_limited' => 'Os resultados são limitados a: contar as entradas.', 'tagbalancingAct' => 'Saldo', 'tagadvancePayment' => 'Pagamento antecipado', 'tagnothing' => '', From 1e9dacb6e4fff823c49660ea4f68cb3b2fe278eb Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:40:16 +0200 Subject: [PATCH 043/103] New translations pagination.php (Spanish) --- resources/lang/es_ES/pagination.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 resources/lang/es_ES/pagination.php diff --git a/resources/lang/es_ES/pagination.php b/resources/lang/es_ES/pagination.php new file mode 100644 index 0000000000..832406ce27 --- /dev/null +++ b/resources/lang/es_ES/pagination.php @@ -0,0 +1,17 @@ + '« Anterior', + 'next' => 'Siguiente »', + +]; \ No newline at end of file From 1bd82d71a26fee4581aef210233bc63d6562faee Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 25 May 2017 16:50:09 +0200 Subject: [PATCH 044/103] New translations list.php (Spanish) --- resources/lang/es_ES/list.php | 116 +++++++++++++++++----------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index df53bfd0c9..924c3cbf74 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -23,67 +23,67 @@ return [ 'active' => '¿Está Activo?', 'lastActivity' => 'Actividad más reciente', 'balanceDiff' => 'Diferencia de saldo entre :start y :end', - 'matchedOn' => 'Comparado en', - 'matchesOn' => 'Comparado en', + 'matchedOn' => 'Encontrado en', + 'matchesOn' => 'Encontrado en', 'account_type' => 'Tipo de cuenta', 'created_at' => 'Fecha de creación', - 'new_balance' => 'New balance', - 'account' => 'Account', - 'matchingAmount' => 'Amount', - 'lastMatch' => 'Last match', - 'split_number' => 'Split #', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', - 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', + 'new_balance' => 'Nuevo balance', + 'account' => 'Cuenta', + 'matchingAmount' => 'Monto', + 'lastMatch' => 'Última coincidencia', + 'split_number' => 'División #', + 'destination' => 'Destino', + 'source' => 'Origen', + 'next_expected_match' => 'Próxima coincidencia esperada', + 'automatch' => '¿Buscar coincidencia automaticamente?', + 'repeat_freq' => 'Repetición:', + 'description' => 'Descripción', + 'amount' => 'Monto', + 'internal_reference' => 'Referencia interna', + 'date' => 'Fecha', + 'interest_date' => 'Tasa de interés', + 'book_date' => 'Libro fecha', + 'process_date' => 'Fecha de procesamiento', + 'due_date' => 'Fecha de vencimiento', + 'payment_date' => 'Fecha de pago', + 'invoice_date' => 'Fecha de facturación', + 'interal_reference' => 'Referencia interna', + 'notes' => 'Notas', + 'from' => 'Desde', + 'piggy_bank' => 'Alcancilla', + 'to' => 'Hasta', + 'budget' => 'Presupuesto', + 'category' => 'Categoría', + 'bill' => 'Factura', + 'withdrawal' => 'Retiro', + 'deposit' => 'Depósito', + 'transfer' => 'Trasferencia', + 'type' => 'Tipo', + 'completed' => 'Completado', 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', + 'paid_current_period' => 'Pagado este período', 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_activated' => 'Is activated', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', - 'has_two_factor' => 'Has 2FA', - 'confirmed_from' => 'Confirmed from', - 'registered_from' => 'Registered from', - 'blocked_code' => 'Block code', - 'domain' => 'Domain', - 'registration_attempts' => 'Registration attempts', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', + 'registered_at' => 'Registrado el', + 'is_activated' => 'Está activado', + 'is_blocked' => 'Está bloqueado', + 'is_admin' => '¿Es el administrador?', + 'has_two_factor' => 'Tiene 2FA', + 'confirmed_from' => 'Confirmado desde', + 'registered_from' => 'Registrado desde', + 'blocked_code' => 'Bloque de código', + 'domain' => 'Dominio', + 'registration_attempts' => 'Intentos de registro', + 'source_account' => 'Cuenta origen', + 'destination_account' => 'Cuenta destino', - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', + 'accounts_count' => 'Número de cuentas', + 'journals_count' => 'Número de transacciones', + 'attachments_count' => 'Núm. de datos adjuntos', + 'bills_count' => 'Número de facturas', + 'categories_count' => 'Número de categorías', + 'export_jobs_count' => 'Número de operaciones de exportación', + 'import_jobs_count' => 'Número de operaciones de importación', + 'budget_count' => 'Número de presupuestos', + 'rule_and_groups_count' => 'Número de reglas y grupos de reglas', + 'tags_count' => 'Número de etiquetas', ]; \ No newline at end of file From 01fedc0bf8d1d71608dc1d4b613865f156517318 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 06:45:38 +0200 Subject: [PATCH 045/103] Fix for #593, as inspired by @nhaarman. --- .../Controllers/Chart/BudgetController.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 189c1e2dea..8a0d923231 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -31,6 +31,7 @@ use Illuminate\Support\Collection; use Navigation; use Preferences; use Response; +use Steam; /** * Class BudgetController @@ -320,12 +321,12 @@ class BudgetController extends Controller ['label' => strval(trans('firefly.overspent')), 'entries' => [], 'type' => 'bar',], ]; - /** @var Budget $budget */ foreach ($budgets as $budget) { // get relevant repetitions: $limits = $this->repository->getBudgetLimits($budget, $start, $end); $expenses = $this->getExpensesForBudget($limits, $budget, $start, $end); + foreach ($expenses as $name => $row) { $chartData[0]['entries'][$name] = $row['spent']; $chartData[1]['entries'][$name] = $row['left']; @@ -529,9 +530,7 @@ class BudgetController extends Controller $rows = $this->spentInPeriodMulti($budget, $limits); foreach ($rows as $name => $row) { if (bccomp($row['spent'], '0') !== 0 || bccomp($row['left'], '0') !== 0) { - $return[$name]['spent'] = bcmul($row['spent'], '-1'); - $return[$name]['left'] = $row['left']; - $return[$name]['overspent'] = bcmul($row['overspent'], '-1'); + $return[$name] = $row; } } unset($rows, $row); @@ -563,6 +562,7 @@ class BudgetController extends Controller /** @var BudgetLimit $budgetLimit */ foreach ($limits as $budgetLimit) { $expenses = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $budgetLimit->start_date, $budgetLimit->end_date); + $expenses = Steam::positive($expenses); if ($limits->count() > 1) { $name = $budget->name . ' ' . trans( @@ -578,10 +578,14 @@ class BudgetController extends Controller * left: amount of budget limit min spent, or 0 when < 0. * spent: spent, or amount of budget limit when > amount */ - $amount = $budgetLimit->amount; - $left = bccomp(bcadd($amount, $expenses), '0') < 1 ? '0' : bcadd($amount, $expenses); - $spent = bccomp($expenses, $amount) === 1 ? $expenses : bcmul($amount, '-1'); - $overspent = bccomp(bcadd($amount, $expenses), '0') < 1 ? bcadd($amount, $expenses) : '0'; + $amount = $budgetLimit->amount; + $leftInLimit = bcsub($amount, $expenses); + $hasOverspent = bccomp($leftInLimit, '0') === -1; + + $left = $hasOverspent ? '0' : bcsub($amount, $expenses); + $spent = $hasOverspent ? $amount : $expenses; + $overspent = $hasOverspent ? Steam::positive($leftInLimit) : '0'; + $return[$name] = [ 'left' => $left, 'overspent' => $overspent, From 2b1ab5c6ef9689bdd6c02eae9e89be6e916616fa Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 07:05:42 +0200 Subject: [PATCH 046/103] Fixed edit of multi currency transaction, ##651 --- app/Repositories/Journal/JournalRepository.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 98ab2dafb5..ef4820ac2e 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -259,18 +259,9 @@ class JournalRepository implements JournalRepositoryInterface $journal->description = $data['description']; $journal->date = $data['date']; $accounts = $this->storeAccounts($journal->transactionType, $data); + $data = $this->verifyNativeAmount($data, $accounts); $amount = strval($data['amount']); - if ($data['currency_id'] !== $journal->transaction_currency_id) { - // user has entered amount in foreign currency. - // amount in "our" currency is $data['exchanged_amount']: - $amount = strval($data['exchanged_amount']); - // other values must be stored as well: - $data['original_amount'] = $data['amount']; - $data['original_currency_id'] = $data['currency_id']; - - } - // unlink all categories, recreate them: $journal->categories()->detach(); $journal->budgets()->detach(); From 8cdc1f00140e4b67bc26fd5c69efaa5118aaae37 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 12:59:14 +0200 Subject: [PATCH 047/103] Rename several twig files. --- app/Http/Controllers/RuleController.php | 2 +- resources/views/accounts/show.twig | 2 +- resources/views/bills/show.twig | 2 +- resources/views/budgets/no-budget.twig | 2 +- resources/views/budgets/show.twig | 2 +- resources/views/categories/no-category.twig | 2 +- resources/views/categories/show.twig | 2 +- resources/views/index.twig | 2 +- .../list/{journals-tiny-tasker.twig => journals-tiny.twig} | 0 resources/views/list/{journals-tasker.twig => journals.twig} | 0 .../views/popup/list/{journals-tasker.twig => journals.twig} | 0 resources/views/popup/report/balance-amount.twig | 2 +- resources/views/popup/report/budget-spent-amount.twig | 2 +- resources/views/popup/report/category-entry.twig | 2 +- resources/views/popup/report/expense-entry.twig | 2 +- resources/views/popup/report/income-entry.twig | 2 +- resources/views/reports/audit/report.twig | 2 +- .../{journals-audit-tasker.twig => journals-audit.twig} | 0 resources/views/tags/show.twig | 2 +- resources/views/transactions/index.twig | 2 +- 20 files changed, 16 insertions(+), 16 deletions(-) rename resources/views/list/{journals-tiny-tasker.twig => journals-tiny.twig} (100%) rename resources/views/list/{journals-tasker.twig => journals.twig} (100%) rename resources/views/popup/list/{journals-tasker.twig => journals.twig} (100%) rename resources/views/reports/partials/{journals-audit-tasker.twig => journals-audit.twig} (100%) diff --git a/app/Http/Controllers/RuleController.php b/app/Http/Controllers/RuleController.php index a7247b7705..ba4dc6d208 100644 --- a/app/Http/Controllers/RuleController.php +++ b/app/Http/Controllers/RuleController.php @@ -306,7 +306,7 @@ class RuleController extends Controller } // Return json response - $view = view('list.journals-tiny-tasker', ['transactions' => $matchingTransactions])->render(); + $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); return Response::json(['html' => $view, 'warning' => $warning]); } diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index bedb9ee721..40da0b960c 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -86,7 +86,7 @@

{{ 'transactions'|_ }}

- {% include 'list.journals-tasker' with {sorting:true, hideBills:true, hideBudgets: true, hideCategories: true} %} + {% include 'list.journals' with {sorting:true, hideBills:true, hideBudgets: true, hideCategories: true} %} {% if periods.count > 0 %}

diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index 2b01b46c29..7e81359214 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -106,7 +106,7 @@

{{ 'connected_journals'|_ }}

- {% include 'list.journals-tasker' %} + {% include 'list.journals' %}
diff --git a/resources/views/budgets/no-budget.twig b/resources/views/budgets/no-budget.twig index fd23bde560..c2311cb332 100644 --- a/resources/views/budgets/no-budget.twig +++ b/resources/views/budgets/no-budget.twig @@ -22,7 +22,7 @@

{{ subTitle }}

- {% include 'list.journals-tasker' with {'journals': journals,'hideBudgets': true} %} + {% include 'list.journals' with {'journals': journals,'hideBudgets': true} %} {% if periods.count > 0 %}

diff --git a/resources/views/budgets/show.twig b/resources/views/budgets/show.twig index 3e598b1d6e..824efb1ce2 100644 --- a/resources/views/budgets/show.twig +++ b/resources/views/budgets/show.twig @@ -88,7 +88,7 @@

{{ 'transactions'|_ }}

- {% include 'list.journals-tasker' with {hideBudgets:true, hideBills:true} %} + {% include 'list.journals' with {hideBudgets:true, hideBills:true} %} {% if budgetLimit %}

diff --git a/resources/views/categories/no-category.twig b/resources/views/categories/no-category.twig index c6ebc131f4..0037b77257 100644 --- a/resources/views/categories/no-category.twig +++ b/resources/views/categories/no-category.twig @@ -22,7 +22,7 @@

{{ subTitle }}

- {% include 'list.journals-tasker' with {'journals': journals, 'hideCategories':true} %} + {% include 'list.journals' with {'journals': journals, 'hideCategories':true} %} {% if periods.count > 0 %}

diff --git a/resources/views/categories/show.twig b/resources/views/categories/show.twig index 186ad996de..9a1648d3da 100644 --- a/resources/views/categories/show.twig +++ b/resources/views/categories/show.twig @@ -65,7 +65,7 @@

{{ 'transactions'|_ }}

- {% include 'list.journals-tasker' with {hideCategories: true} %} + {% include 'list.journals' with {hideCategories: true} %} {% if periods.count > 0 %}

diff --git a/resources/views/index.twig b/resources/views/index.twig index 7dd28358b9..6cd8914de2 100644 --- a/resources/views/index.twig +++ b/resources/views/index.twig @@ -85,7 +85,7 @@ {% if data[0].count > 0 %}

- {% include 'list.journals-tiny-tasker' with {'transactions': data[0],'account': data[1]} %} + {% include 'list.journals-tiny' with {'transactions': data[0],'account': data[1]} %}
{% else %}
diff --git a/resources/views/list/journals-tiny-tasker.twig b/resources/views/list/journals-tiny.twig similarity index 100% rename from resources/views/list/journals-tiny-tasker.twig rename to resources/views/list/journals-tiny.twig diff --git a/resources/views/list/journals-tasker.twig b/resources/views/list/journals.twig similarity index 100% rename from resources/views/list/journals-tasker.twig rename to resources/views/list/journals.twig diff --git a/resources/views/popup/list/journals-tasker.twig b/resources/views/popup/list/journals.twig similarity index 100% rename from resources/views/popup/list/journals-tasker.twig rename to resources/views/popup/list/journals.twig diff --git a/resources/views/popup/report/balance-amount.twig b/resources/views/popup/report/balance-amount.twig index 1f5bd820bb..426aab6d07 100644 --- a/resources/views/popup/report/balance-amount.twig +++ b/resources/views/popup/report/balance-amount.twig @@ -10,7 +10,7 @@
- {% include 'list/journals-tasker' %} + {% include 'list/journals' %} {% if periods %}

diff --git a/resources/views/transactions/index.twig b/resources/views/transactions/index.twig index 4a72c0cb2f..65bfbf64e7 100644 --- a/resources/views/transactions/index.twig +++ b/resources/views/transactions/index.twig @@ -22,7 +22,7 @@

{{ subTitle }}

- {% include 'list.journals-tasker' with {'journals': journals} %} + {% include 'list.journals' with {'journals': journals} %} {% if periods.count > 0 %}

From ec1507d644d348c7682530ba0be04899d8ece354 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 12:59:27 +0200 Subject: [PATCH 048/103] Add some documentation [skip ci] --- app/Generator/Chart/Basic/GeneratorInterface.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Generator/Chart/Basic/GeneratorInterface.php b/app/Generator/Chart/Basic/GeneratorInterface.php index 92b1b29d93..31deeb0a30 100644 --- a/app/Generator/Chart/Basic/GeneratorInterface.php +++ b/app/Generator/Chart/Basic/GeneratorInterface.php @@ -22,10 +22,15 @@ interface GeneratorInterface { /** - * Will generate a (ChartJS) compatible array from the given input. Expects this format: + * Will generate a Chart JS compatible array from the given input. Expects this format + * + * Will take labels for all from first set. * * 0: [ * 'label' => 'label of set', + * 'type' => bar or line, optional + * 'yAxisID' => ID of yAxis, optional, will not be included when unused. + * 'fill' => if to fill a line? optional, will not be included when unused. * 'entries' => * [ * 'label-of-entry' => 'value' @@ -33,12 +38,16 @@ interface GeneratorInterface * ] * 1: [ * 'label' => 'label of another set', + * 'type' => bar or line, optional + * 'yAxisID' => ID of yAxis, optional, will not be included when unused. + * 'fill' => if to fill a line? optional, will not be included when unused. * 'entries' => * [ * 'label-of-entry' => 'value' * ] * ] * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five. * * @param array $data * From c05f34437114fba7ad9c8d75f48a52ac1e1765ae Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 13:00:09 +0200 Subject: [PATCH 049/103] Code clean up [skip ci] --- app/Http/Controllers/Transaction/SingleController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 27c7cdba77..eef9bd7ab4 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -250,8 +250,8 @@ class SingleController extends Controller 'source_account_name' => $sourceAccounts->first()->edit_name, 'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_name' => $destinationAccounts->first()->edit_name, - 'amount' => $journal->amountPositive(), - 'currency' => $journal->transactionCurrency, + 'amount' => $journal->amountPositive(), + 'currency' => $journal->transactionCurrency, // new custom fields: 'due_date' => $journal->dateAsString('due_date'), @@ -261,8 +261,8 @@ class SingleController extends Controller 'notes' => $journal->getMeta('notes'), // exchange rate fields - 'native_amount' => $journal->amountPositive(), - 'native_currency' => $journal->transactionCurrency, + 'native_amount' => $journal->amountPositive(), + 'native_currency' => $journal->transactionCurrency, ]; // if user has entered a foreign currency, update some fields From 74664afa682b79099f4e02eb569207f05c7f259a Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 13:00:24 +0200 Subject: [PATCH 050/103] Was not able to remove opening balance. --- .../Account/AccountRepository.php | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 423efcd23e..122e8f7d5c 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -466,7 +466,12 @@ class AccountRepository implements AccountRepositoryInterface */ protected function storeInitialBalance(Account $account, array $data): TransactionJournal { - $amount = $data['openingBalance']; + $amount = strval($data['openingBalance']); + + if (bccomp($amount, '0') === 0) { + return new TransactionJournal; + } + $name = $data['name']; $currencyId = $data['currency_id']; $opposing = $this->storeOpposingAccount($name); @@ -487,12 +492,12 @@ class AccountRepository implements AccountRepositoryInterface $firstAccount = $account; $secondAccount = $opposing; $firstAmount = $amount; - $secondAmount = $amount * -1; + $secondAmount = bcmul($amount, '-1'); if ($data['openingBalance'] < 0) { $firstAccount = $opposing; $secondAccount = $account; - $firstAmount = $amount * -1; + $firstAmount = bcmul($amount, '-1'); $secondAmount = $amount; } @@ -606,9 +611,15 @@ class AccountRepository implements AccountRepositoryInterface protected function updateOpeningBalanceJournal(Account $account, TransactionJournal $journal, array $data): bool { $date = $data['openingBalanceDate']; - $amount = $data['openingBalance']; + $amount = strval($data['openingBalance']); $currencyId = intval($data['currency_id']); + if (bccomp($amount, '0') === 0) { + $journal->delete(); + + return true; + } + // update date: $journal->date = $date; $journal->transaction_currency_id = $currencyId; @@ -621,7 +632,7 @@ class AccountRepository implements AccountRepositoryInterface $transaction->save(); } if ($account->id != $transaction->account_id) { - $transaction->amount = $amount * -1; + $transaction->amount = bcmul($amount, '-1'); $transaction->save(); } } @@ -631,6 +642,7 @@ class AccountRepository implements AccountRepositoryInterface } + /** * @param array $data * @@ -638,9 +650,7 @@ class AccountRepository implements AccountRepositoryInterface */ protected function validOpeningBalanceData(array $data): bool { - if (isset($data['openingBalance']) && isset($data['openingBalanceDate']) - && bccomp(strval($data['openingBalance']), '0') !== 0 - ) { + if (isset($data['openingBalance']) && isset($data['openingBalanceDate'])) { Log::debug('Array has valid opening balance data.'); return true; From 8273f467b6511f40e90d327ed0f78ad5cbd0abc0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 13:00:43 +0200 Subject: [PATCH 051/103] Refactor some JS functions. --- public/js/ff/charts.js | 6 +++--- public/js/ff/reports/category/month.js | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/public/js/ff/charts.js b/public/js/ff/charts.js index 35b99f0b00..fa0c8f049a 100644 --- a/public/js/ff/charts.js +++ b/public/js/ff/charts.js @@ -75,7 +75,7 @@ function lineChart(URI, container) { "use strict"; var colorData = true; - var options = defaultChartOptions; + var options = $.extend(true, {}, defaultChartOptions); var chartType = 'line'; drawAChart(URI, container, chartType, options, colorData); @@ -188,7 +188,7 @@ function columnChart(URI, container) { "use strict"; console.log('Going to draw column chart for ' + URI + ' in ' + container); var colorData = true; - var options = defaultChartOptions; + var options = $.extend(true, {}, defaultChartOptions); var chartType = 'bar'; drawAChart(URI, container, chartType, options, colorData); @@ -224,7 +224,7 @@ function pieChart(URI, container) { "use strict"; var colorData = false; - var options = defaultPieOptions; + var options = $.extend(true, {}, defaultPieOptions); var chartType = 'pie'; drawAChart(URI, container, chartType, options, colorData); diff --git a/public/js/ff/reports/category/month.js b/public/js/ff/reports/category/month.js index 84f3444d9b..e803ff3de7 100644 --- a/public/js/ff/reports/category/month.js +++ b/public/js/ff/reports/category/month.js @@ -15,19 +15,19 @@ $(function () { drawChart(); $('#categories-in-pie-chart-checked').on('change', function () { - redrawPieChart('categories-in-pie-chart', categoryIncomeUri); + redrawPieChart(categoryIncomeUri, 'categories-in-pie-chart'); }); $('#categories-out-pie-chart-checked').on('change', function () { - redrawPieChart('categories-out-pie-chart', categoryExpenseUri); + redrawPieChart(categoryExpenseUri, 'categories-out-pie-chart'); }); $('#accounts-in-pie-chart-checked').on('change', function () { - redrawPieChart('accounts-in-pie-chart', accountIncomeUri); + redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart'); }); $('#accounts-out-pie-chart-checked').on('change', function () { - redrawPieChart('accounts-out-pie-chart', accountExpenseUri); + redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart'); }); }); @@ -40,15 +40,17 @@ function drawChart() { doubleYChart(mainUri, 'in-out-chart'); // draw pie chart of income, depending on "show other transactions too": - redrawPieChart('categories-in-pie-chart', categoryIncomeUri); - redrawPieChart('categories-out-pie-chart', categoryExpenseUri); - redrawPieChart('accounts-in-pie-chart', accountIncomeUri); - redrawPieChart('accounts-out-pie-chart', accountExpenseUri); + redrawPieChart(categoryIncomeUri, 'categories-in-pie-chart'); + redrawPieChart(categoryExpenseUri, 'categories-out-pie-chart'); + redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart'); + redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart'); + stackedColumnChart(expenseAccountTimeUri, 'expense-time-chart'); + stackedColumnChart(revenueAccountTimeUri, 'revenue-time-chart'); } -function redrawPieChart(container, uri) { +function redrawPieChart(uri, container) { "use strict"; var checkbox = $('#' + container + '-checked'); From e1aebbe12b550cd91cd4891a3028d1b52e8837a2 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 13:01:24 +0200 Subject: [PATCH 052/103] Remove non-existing charts. --- public/js/ff/reports/category/month.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/js/ff/reports/category/month.js b/public/js/ff/reports/category/month.js index e803ff3de7..b07941a944 100644 --- a/public/js/ff/reports/category/month.js +++ b/public/js/ff/reports/category/month.js @@ -45,9 +45,6 @@ function drawChart() { redrawPieChart(accountIncomeUri, 'accounts-in-pie-chart'); redrawPieChart(accountExpenseUri, 'accounts-out-pie-chart'); - stackedColumnChart(expenseAccountTimeUri, 'expense-time-chart'); - stackedColumnChart(revenueAccountTimeUri, 'revenue-time-chart'); - } function redrawPieChart(uri, container) { From 4ff5f33966a8480820e6a32ebc5c3debef3c84d0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 2 Jun 2017 13:01:43 +0200 Subject: [PATCH 053/103] Prep for solid multi-currency configuration. --- .../2017_06_02_105232_changes_for_v450.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 database/migrations/2017_06_02_105232_changes_for_v450.php diff --git a/database/migrations/2017_06_02_105232_changes_for_v450.php b/database/migrations/2017_06_02_105232_changes_for_v450.php new file mode 100644 index 0000000000..f0f39c839d --- /dev/null +++ b/database/migrations/2017_06_02_105232_changes_for_v450.php @@ -0,0 +1,42 @@ +decimal('foreign_amount', 22, 12)->after('amount'); + } + ); + + // add foreign transaction currency id to transactions (is nullable): + Schema::table( + 'transactions', function (Blueprint $table) { + $table->integer('foreign_currency_id', false, true)->after('foreign_amount')->nullable(); + $table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null'); + } + ); + } +} From 0868aac750cd7919c971df386be60675f8993bae Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 08:48:40 +0200 Subject: [PATCH 054/103] Small update for 4.5.0 SQL update. --- database/migrations/2017_06_02_105232_changes_for_v450.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2017_06_02_105232_changes_for_v450.php b/database/migrations/2017_06_02_105232_changes_for_v450.php index f0f39c839d..1f44bedf52 100644 --- a/database/migrations/2017_06_02_105232_changes_for_v450.php +++ b/database/migrations/2017_06_02_105232_changes_for_v450.php @@ -27,14 +27,14 @@ class ChangesForV450 extends Migration // add "foreign_amount" to transactions Schema::table( 'transactions', function (Blueprint $table) { - $table->decimal('foreign_amount', 22, 12)->after('amount'); + $table->decimal('foreign_amount', 22, 12)->nullable()->after('amount'); } ); // add foreign transaction currency id to transactions (is nullable): Schema::table( 'transactions', function (Blueprint $table) { - $table->integer('foreign_currency_id', false, true)->after('foreign_amount')->nullable(); + $table->integer('foreign_currency_id', false, true)->default(null)->after('foreign_amount')->nullable(); $table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null'); } ); From d37b46effc500f469ea3b2954dac3b2913586361 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 08:48:54 +0200 Subject: [PATCH 055/103] Database upgrade routine. --- app/Console/Commands/UpgradeDatabase.php | 52 ++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index aa2656b712..2c3a20f3a0 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -32,6 +32,7 @@ use Illuminate\Database\QueryException; use Log; use Preferences; use Schema; +use Steam; /** * Class UpgradeDatabase @@ -72,9 +73,54 @@ class UpgradeDatabase extends Command $this->repairPiggyBanks(); $this->updateAccountCurrencies(); $this->updateJournalCurrencies(); + $this->currencyInfoToTransactions(); $this->info('Firefly III database is up to date.'); } + /** + * Moves the currency id info to the transaction instead of the journal. + */ + private function currencyInfoToTransactions() + { + $count = 0; + $expanded = 0; + $set = TransactionJournal::with('transactions')->get(); + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + /** @var Transaction $transaction */ + foreach ($journal->transactions as $transaction) { + if (is_null($transaction->transaction_currency_id)) { + $transaction->transaction_currency_id = $journal->transaction_currency_id; + $transaction->save(); + $count++; + } + } + + + // read and use the foreign amounts when present. + if ($journal->hasMeta('foreign_amount')) { + $amount = Steam::positive($journal->getMeta('foreign_amount')); + + // update both transactions: + foreach ($journal->transactions as $transaction) { + $transaction->foreign_amount = $amount; + if (bccomp($transaction->amount, '0') === -1) { + // update with negative amount: + $transaction->foreign_amount = bcmul($amount, '-1'); + } + // set foreign currency id: + $transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id')); + $transaction->save(); + } + $journal->deleteMeta('foreign_amount'); + $journal->deleteMeta('foreign_currency_id'); + } + + } + + $this->line(sprintf('Updated currency information for %d transactions', $count)); + } + /** * Migrate budget repetitions to new format. */ @@ -269,7 +315,7 @@ class UpgradeDatabase extends Command $repository = app(CurrencyRepositoryInterface::class); $notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.'; $transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.'; - $driver = DB::connection()->getDriverName(); + $driver = DB::connection()->getDriverName(); foreach ($types as $type => $operator) { $query = TransactionJournal @@ -282,10 +328,10 @@ class UpgradeDatabase extends Command ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') ->where('transaction_types.type', $type) ->where('account_meta.name', 'currency_id'); - if($driver === 'postgresql') { + if ($driver === 'postgresql') { $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)')); } - if($driver !== 'postgresql') { + if ($driver !== 'postgresql') { $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data')); } From 4ce4c3138c3d36164b3e6a7b887d57cd31b1fd88 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 08:49:22 +0200 Subject: [PATCH 056/103] Update export routine so currency information is taken from the transaction. --- app/Export/Collector/JournalExportCollector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Export/Collector/JournalExportCollector.php b/app/Export/Collector/JournalExportCollector.php index 175b405f79..b0e04e115e 100644 --- a/app/Export/Collector/JournalExportCollector.php +++ b/app/Export/Collector/JournalExportCollector.php @@ -303,7 +303,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') ->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id') ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id') - ->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id') + ->leftJoin('transaction_currencies', 'transactions.transaction_currency_id', '=', 'transaction_currencies.id') ->whereIn('transactions.account_id', $accountIds) ->where('transaction_journals.user_id', $this->job->user_id) ->where('transaction_journals.date', '>=', $this->start->format('Y-m-d')) @@ -338,7 +338,7 @@ class JournalExportCollector extends BasicCollector implements CollectorInterfac 'transaction_journals.encrypted as journal_encrypted', 'transaction_journals.transaction_type_id', 'transaction_types.type as transaction_type', - 'transaction_journals.transaction_currency_id', + 'transactions.transaction_currency_id', 'transaction_currencies.code AS transaction_currency_code', ] From 771ebde295a23548321a4ba98059602e4159886f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 08:49:37 +0200 Subject: [PATCH 057/103] Update journal collector so currency information is taken from the transaction. --- app/Helpers/Collector/JournalCollector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index 56d53d7446..a85535d907 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -60,7 +60,6 @@ class JournalCollector implements JournalCollectorInterface 'transaction_journals.description', 'transaction_journals.date', 'transaction_journals.encrypted', - 'transaction_currencies.code as transaction_currency_code', 'transaction_types.type as transaction_type_type', 'transaction_journals.bill_id', 'bills.name as bill_name', @@ -71,6 +70,7 @@ class JournalCollector implements JournalCollectorInterface 'transactions.account_id', 'transactions.identifier', 'transactions.transaction_journal_id', + 'transaction_currencies.code as transaction_currency_code', 'accounts.name as account_name', 'accounts.encrypted as account_encrypted', 'account_types.type as account_type', @@ -484,11 +484,11 @@ class JournalCollector implements JournalCollectorInterface Log::debug('journalCollector::startQuery'); /** @var EloquentBuilder $query */ $query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transaction_journals.transaction_currency_id') ->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id') ->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id') ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id') + ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transactions.transaction_currency_id') ->whereNull('transactions.deleted_at') ->whereNull('transaction_journals.deleted_at') ->where('transaction_journals.user_id', $this->user->id) From 82e74a2afd98af278bf82fa31ef81339623cc27a Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:39:16 +0200 Subject: [PATCH 058/103] Big update to properly support multi currencies. --- .../Transaction/MassController.php | 42 +++- .../Transaction/SingleController.php | 40 ++-- .../Transaction/SplitController.php | 33 +-- .../Controllers/TransactionController.php | 11 +- app/Import/ImportStorage.php | 1 + app/Import/ImportValidator.php | 2 + app/Models/Transaction.php | 33 ++- app/Models/TransactionJournal.php | 41 ---- app/Providers/FireflyServiceProvider.php | 3 +- .../Account/AccountRepository.php | 2 + .../Journal/JournalRepository.php | 192 +++++++++++------- app/Repositories/Journal/JournalTasker.php | 53 +++-- app/Support/Amount.php | 55 +---- app/Support/Binder/JournalList.php | 9 +- .../Models/TransactionJournalTrait.php | 8 + app/Support/Twig/Account.php | 63 ------ app/Support/Twig/AmountFormat.php | 109 ++++++++++ app/Support/Twig/General.php | 41 ---- app/Support/Twig/Transaction.php | 75 ------- database/factories/ModelFactory.php | 1 + public/js/ff/transactions/single/edit.js | 5 +- public/js/ff/transactions/split/edit.js | 20 ++ resources/lang/en_US/firefly.php | 1 + resources/views/list/journals-tiny.twig | 6 +- resources/views/list/journals.twig | 11 +- resources/views/popup/list/journals.twig | 6 +- .../reports/partials/journals-audit.twig | 6 +- .../search/partials/transactions-large.twig | 13 +- .../views/search/partials/transactions.twig | 6 +- resources/views/transactions/mass-delete.twig | 3 +- resources/views/transactions/mass/edit.twig | 13 +- resources/views/transactions/show.twig | 30 ++- resources/views/transactions/single/edit.twig | 4 +- resources/views/transactions/split/edit.twig | 30 ++- 34 files changed, 470 insertions(+), 498 deletions(-) delete mode 100644 app/Support/Twig/Account.php create mode 100644 app/Support/Twig/AmountFormat.php diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 285a73f3e9..b0205f36af 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -19,6 +19,7 @@ use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; @@ -126,8 +127,7 @@ class MassController extends Controller $budgetRepository = app(BudgetRepositoryInterface::class); $budgets = $budgetRepository->getBudgets(); - // skip transactions that have multiple destinations - // or multiple sources: + // skip transactions that have multiple destinations, multiple sources or are an opening balance. $filtered = new Collection; $messages = []; /** @@ -146,6 +146,10 @@ class MassController extends Controller $messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]); continue; } + if ($journal->transactionType->type === TransactionType::OPENING_BALANCE) { + $messages[] = trans('firefly.cannot_edit_opening_balance'); + continue; + } $filtered->push($journal); } @@ -158,13 +162,21 @@ class MassController extends Controller Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'mass-edit'); - // set some values to be used in the edit routine: + // collect some useful meta data for the mass edit: $filtered->each( function (TransactionJournal $journal) { - $journal->amount = $journal->amountPositive(); - $sources = $journal->sourceAccountList(); - $destinations = $journal->destinationAccountList(); - $journal->transaction_count = $journal->transactions()->count(); + $transaction = $journal->positiveTransaction(); + $currency = $transaction->transactionCurrency; + $journal->amount = floatval($transaction->amount); + $sources = $journal->sourceAccountList(); + $destinations = $journal->destinationAccountList(); + $journal->transaction_count = $journal->transactions()->count(); + $journal->currency_symbol = $currency->symbol; + $journal->transaction_type_type = $journal->transactionType->type; + + $journal->foreign_amount = floatval($transaction->foreign_amount); + $journal->foreign_currency = $transaction->foreignCurrency; + if (!is_null($sources->first())) { $journal->source_account_id = $sources->first()->id; $journal->source_account_name = $sources->first()->editname; @@ -195,6 +207,7 @@ class MassController extends Controller { $journalIds = $request->get('journals'); $count = 0; + if (is_array($journalIds)) { foreach ($journalIds as $journalId) { $journal = $repository->find(intval($journalId)); @@ -208,6 +221,10 @@ class MassController extends Controller $budgetId = $request->get('budget_id')[$journal->id] ?? 0; $category = $request->get('category')[$journal->id]; $tags = $journal->tags->pluck('tag')->toArray(); + $amount = round($request->get('amount')[$journal->id], 12); + $foreignAmount = isset($request->get('foreign_amount')[$journal->id]) ? round($request->get('foreign_amount')[$journal->id], 12) : null; + $foreignCurrencyId = isset($request->get('foreign_currency_id')[$journal->id]) ? + intval($request->get('foreign_currency_id')[$journal->id]) : null; // build data array $data = [ @@ -218,16 +235,20 @@ class MassController extends Controller 'source_account_name' => $sourceAccountName, 'destination_account_id' => intval($destAccountId), 'destination_account_name' => $destAccountName, - 'amount' => round($request->get('amount')[$journal->id], 12), - 'currency_id' => $journal->transaction_currency_id, + 'amount' => $foreignAmount, + 'native_amount' => $amount, + 'source_amount' => $amount, 'date' => new Carbon($request->get('date')[$journal->id]), 'interest_date' => $journal->interest_date, 'book_date' => $journal->book_date, 'process_date' => $journal->process_date, 'budget_id' => intval($budgetId), + 'currency_id' => $foreignCurrencyId, + 'foreign_amount' => $foreignAmount, + 'destination_amount' => $foreignAmount, + //'foreign_currency_id' => $foreignCurrencyId, 'category' => $category, 'tags' => $tags, - ]; // call repository update function. $repository->update($journal, $data); @@ -235,6 +256,7 @@ class MassController extends Controller $count++; } } + } Preferences::mark(); Session::flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count])); diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index eef9bd7ab4..89de28d7d2 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -238,6 +238,7 @@ class SingleController extends Controller $sourceAccounts = $journal->sourceAccountList(); $destinationAccounts = $journal->destinationAccountList(); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; + $pTransaction = $journal->positiveTransaction(); $preFilled = [ 'date' => $journal->dateAsString(), 'interest_date' => $journal->dateAsString('interest_date'), @@ -250,8 +251,6 @@ class SingleController extends Controller 'source_account_name' => $sourceAccounts->first()->edit_name, 'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_name' => $destinationAccounts->first()->edit_name, - 'amount' => $journal->amountPositive(), - 'currency' => $journal->transactionCurrency, // new custom fields: 'due_date' => $journal->dateAsString('due_date'), @@ -260,26 +259,36 @@ class SingleController extends Controller 'interal_reference' => $journal->getMeta('internal_reference'), 'notes' => $journal->getMeta('notes'), - // exchange rate fields - 'native_amount' => $journal->amountPositive(), - 'native_currency' => $journal->transactionCurrency, + // amount fields + 'amount' => $pTransaction->amount, + 'source_amount' => $pTransaction->amount, + 'native_amount' => $pTransaction->amount, + 'destination_amount' => $pTransaction->foreign_amount, + 'currency' => $pTransaction->transactionCurrency, + 'source_currency' => $pTransaction->transactionCurrency, + 'native_currency' => $pTransaction->transactionCurrency, + 'foreign_currency' => !is_null($pTransaction->foreignCurrency) ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency, + 'destination_currency' => !is_null($pTransaction->foreignCurrency) ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency, ]; - // if user has entered a foreign currency, update some fields - $foreignCurrencyId = intval($journal->getMeta('foreign_currency_id')); - if ($foreignCurrencyId > 0) { - // update some fields in pre-filled. - // @codeCoverageIgnoreStart - $preFilled['amount'] = $journal->getMeta('foreign_amount'); - $preFilled['currency'] = $this->currency->find(intval($journal->getMeta('foreign_currency_id'))); - // @codeCoverageIgnoreEnd + // amounts for withdrawals and deposits: + // (amount, native_amount, source_amount, destination_amount) + if (($journal->isWithdrawal() || $journal->isDeposit()) && !is_null($pTransaction->foreign_amount)) { + $preFilled['amount'] = $pTransaction->foreign_amount; + $preFilled['currency'] = $pTransaction->foreignCurrency; } - if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type == AccountType::CASH) { + if ($journal->isTransfer() && !is_null($pTransaction->foreign_amount)) { + $preFilled['destination_amount'] = $pTransaction->foreign_amount; + $preFilled['destination_currency'] = $pTransaction->foreignCurrency; + } + + // fixes for cash accounts: + if ($journal->isWithdrawal() && $destinationAccounts->first()->accountType->type === AccountType::CASH) { $preFilled['destination_account_name'] = ''; } - if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type == AccountType::CASH) { + if ($journal->isDeposit() && $sourceAccounts->first()->accountType->type === AccountType::CASH) { $preFilled['source_account_name'] = ''; } @@ -319,6 +328,7 @@ class SingleController extends Controller return redirect(route('transactions.create', [$request->input('what')]))->withInput(); } + /** @var array $files */ $files = $request->hasFile('attachments') ? $request->file('attachments') : null; $this->attachments->saveAttachmentsForModel($journal, $files); diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index c587129811..e6668bc43e 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -93,7 +93,7 @@ class SplitController extends Controller } $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); - $currencies = ExpandedForm::makeSelectList($this->currencies->get()); + $currencies = $this->currencies->get(); $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); @@ -130,7 +130,6 @@ class SplitController extends Controller */ public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal) { - if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } @@ -179,7 +178,6 @@ class SplitController extends Controller 'journal_source_account_id' => $request->get('journal_source_account_id'), 'journal_source_account_name' => $request->get('journal_source_account_name'), 'journal_destination_account_id' => $request->get('journal_destination_account_id'), - 'currency_id' => $request->get('currency_id'), 'what' => $request->get('what'), 'date' => $request->get('date'), // all custom fields: @@ -218,7 +216,6 @@ class SplitController extends Controller 'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id), 'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name), 'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id), - 'currency_id' => $request->old('currency_id', $journal->transaction_currency_id), 'destinationAccounts' => $destinationAccounts, 'what' => strtolower($journal->transactionTypeStr()), 'date' => $request->old('date', $journal->date), @@ -253,14 +250,22 @@ class SplitController extends Controller /** @var array $transaction */ foreach ($transactions as $index => $transaction) { $set = [ - 'description' => $transaction['description'], - 'source_account_id' => $transaction['source_account_id'], - 'source_account_name' => $transaction['source_account_name'], - 'destination_account_id' => $transaction['destination_account_id'], - 'destination_account_name' => $transaction['destination_account_name'], - 'amount' => round($transaction['destination_amount'], 12), - 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, - 'category' => $transaction['category'], + 'description' => $transaction['description'], + 'source_account_id' => $transaction['source_account_id'], + 'source_account_name' => $transaction['source_account_name'], + 'destination_account_id' => $transaction['destination_account_id'], + 'destination_account_name' => $transaction['destination_account_name'], + 'amount' => round($transaction['destination_amount'], 12), + 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, + 'category' => $transaction['category'], + 'transaction_currency_id' => $transaction['transaction_currency_id'], + 'transaction_currency_code' => $transaction['transaction_currency_code'], + 'transaction_currency_symbol' => $transaction['transaction_currency_symbol'], + 'foreign_amount' => round($transaction['foreign_destination_amount'], 12), + 'foreign_currency_id' => $transaction['foreign_currency_id'], + 'foreign_currency_code' => $transaction['foreign_currency_code'], + 'foreign_currency_symbol' => $transaction['foreign_currency_symbol'], + ]; // set initial category and/or budget: @@ -294,8 +299,12 @@ class SplitController extends Controller 'destination_account_id' => $transaction['destination_account_id'] ?? 0, 'destination_account_name' => $transaction['destination_account_name'] ?? '', 'amount' => round($transaction['amount'] ?? 0, 12), + 'foreign_amount' => !isset($transaction['foreign_amount']) ? null : round($transaction['foreign_amount'] ?? 0, 12), 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0, 'category' => $transaction['category'] ?? '', + 'transaction_currency_id' => intval($transaction['transaction_currency_id']), + 'foreign_currency_id' => $transaction['foreign_currency_id'] ?? null, + ]; } Log::debug(sprintf('Found %d splits in request data.', count($return))); diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 1e838c5dec..5f6f12de2e 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -183,17 +183,8 @@ class TransactionController extends Controller $transactions = $tasker->getTransactionsOverview($journal); $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); $subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"'; - $foreignCurrency = null; - if ($journal->hasMeta('foreign_currency_id')) { - // @codeCoverageIgnoreStart - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $foreignCurrency = $repository->find(intval($journal->getMeta('foreign_currency_id'))); - // @codeCoverageIgnoreEnd - } - - return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'foreignCurrency')); + return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions')); } diff --git a/app/Import/ImportStorage.php b/app/Import/ImportStorage.php index 2d5524fcec..4f39086965 100644 --- a/app/Import/ImportStorage.php +++ b/app/Import/ImportStorage.php @@ -291,6 +291,7 @@ class ImportStorage 'user_id' => $entry->user->id, 'transaction_type_id' => $entry->fields['transaction-type']->id, 'bill_id' => $billId, + // TODO update this transaction currency reference. 'transaction_currency_id' => $entry->fields['currency']->id, 'description' => $entry->fields['description'], 'date' => $entry->fields['date-transaction'], diff --git a/app/Import/ImportValidator.php b/app/Import/ImportValidator.php index 3a3a373b2b..fd9981ec47 100644 --- a/app/Import/ImportValidator.php +++ b/app/Import/ImportValidator.php @@ -74,6 +74,7 @@ class ImportValidator $entry = $this->setOpposingAccount($entry); $entry = $this->cleanDescription($entry); $entry = $this->setTransactionType($entry); + // TODO update this transaction currency reference. $entry = $this->setTransactionCurrency($entry); $newCollection->put($index, $entry); @@ -383,6 +384,7 @@ class ImportValidator */ private function setTransactionCurrency(ImportEntry $entry): ImportEntry { + // TODO update this transaction currency reference. if (is_null($entry->fields['currency'])) { /** @var CurrencyRepositoryInterface $repository */ $repository = app(CurrencyRepositoryInterface::class); diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index ef5deeadcd..0e9ade0b30 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -26,7 +26,6 @@ use Watson\Validating\ValidatingTrait; */ class Transaction extends Model { - /** * The attributes that should be casted to native types. * @@ -42,16 +41,18 @@ class Transaction extends Model 'bill_name_encrypted' => 'boolean', ]; protected $dates = ['created_at', 'updated_at', 'deleted_at']; - protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier']; + protected $fillable = ['account_id', 'transaction_journal_id', 'description', 'amount', 'identifier', 'transaction_currency_id', 'foreign_currency_id','foreign_amount']; protected $hidden = ['encrypted']; protected $rules = [ - 'account_id' => 'required|exists:accounts,id', - 'transaction_journal_id' => 'required|exists:transaction_journals,id', - 'description' => 'between:0,1024', - 'amount' => 'required|numeric', + 'account_id' => 'required|exists:accounts,id', + 'transaction_journal_id' => 'required|exists:transaction_journals,id', + 'transaction_currency_id' => 'required|exists:transaction_currencies,id', + //'foreign_currency_id' => 'exists:transaction_currencies,id', + 'description' => 'between:0,1024', + 'amount' => 'required|numeric', + //'foreign_amount' => 'numeric', ]; - use SoftDeletes, ValidatingTrait; /** * @param Builder $query @@ -74,6 +75,8 @@ class Transaction extends Model return false; } + use SoftDeletes, ValidatingTrait; + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ @@ -160,6 +163,22 @@ class Transaction extends Model $this->attributes['amount'] = strval(round($value, 12)); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function transactionCurrency() + { + return $this->belongsTo('FireflyIII\Models\TransactionCurrency'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function foreignCurrency() + { + return $this->belongsTo('FireflyIII\Models\TransactionCurrency','foreign_currency_id'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 53506bc61d..cc0c00ef45 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -68,7 +68,6 @@ class TransactionJournal extends Model = [ 'user_id' => 'required|exists:users,id', 'transaction_type_id' => 'required|exists:transaction_types,id', - 'transaction_currency_id' => 'required|exists:transaction_currencies,id', 'description' => 'required|between:1,1024', 'completed' => 'required|boolean', 'date' => 'required|date', @@ -299,46 +298,6 @@ class TransactionJournal extends Model return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d 00:00:00')); } - /** - * @param EloquentBuilder $query - */ - public function scopeExpanded(EloquentBuilder $query) - { - // left join transaction type: - if (!self::isJoined($query, 'transaction_types')) { - $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); - } - - // left join transaction currency: - $query->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id'); - - // extend group by: - $query->groupBy( - [ - 'transaction_journals.id', - 'transaction_journals.created_at', - 'transaction_journals.updated_at', - 'transaction_journals.deleted_at', - 'transaction_journals.user_id', - 'transaction_journals.transaction_type_id', - 'transaction_journals.bill_id', - 'transaction_journals.transaction_currency_id', - 'transaction_journals.description', - 'transaction_journals.date', - 'transaction_journals.interest_date', - 'transaction_journals.book_date', - 'transaction_journals.process_date', - 'transaction_journals.order', - 'transaction_journals.tag_count', - 'transaction_journals.encrypted', - 'transaction_journals.completed', - 'transaction_types.type', - 'transaction_currencies.code', - ] - ); - $query->with(['categories', 'budgets', 'attachments', 'bill', 'transactions']); - } - /** * @param EloquentBuilder $query */ diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index ef4890bb7f..c9740f4bcf 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -44,6 +44,7 @@ use FireflyIII\Support\Navigation; use FireflyIII\Support\Preferences; use FireflyIII\Support\Steam; use FireflyIII\Support\Twig\Account; +use FireflyIII\Support\Twig\AmountFormat; use FireflyIII\Support\Twig\General; use FireflyIII\Support\Twig\Journal; use FireflyIII\Support\Twig\PiggyBank; @@ -79,7 +80,7 @@ class FireflyServiceProvider extends ServiceProvider Twig::addExtension(new Translation); Twig::addExtension(new Transaction); Twig::addExtension(new Rule); - Twig::addExtension(new Account); + Twig::addExtension(new AmountFormat); } /** diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 122e8f7d5c..7b594555b8 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -481,6 +481,7 @@ class AccountRepository implements AccountRepositoryInterface [ 'user_id' => $this->user->id, 'transaction_type_id' => $transactionType->id, + // TODO update this transaction currency reference. 'transaction_currency_id' => $currencyId, 'description' => 'Initial balance for "' . $account->name . '"', 'completed' => true, @@ -622,6 +623,7 @@ class AccountRepository implements AccountRepositoryInterface // update date: $journal->date = $date; + // TODO update this transaction currency reference. $journal->transaction_currency_id = $currencyId; $journal->save(); // update transactions: diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index ef4820ac2e..9a28be284b 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -39,7 +39,6 @@ class JournalRepository implements JournalRepositoryInterface { /** @var User */ private $user; - /** @var array */ private $validMetaFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date', 'internal_reference', 'notes', 'foreign_amount', @@ -182,13 +181,12 @@ class JournalRepository implements JournalRepositoryInterface $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); $accounts = $this->storeAccounts($transactionType, $data); $data = $this->verifyNativeAmount($data, $accounts); - $currencyId = $data['currency_id']; $amount = strval($data['amount']); $journal = new TransactionJournal( [ 'user_id' => $this->user->id, 'transaction_type_id' => $transactionType->id, - 'transaction_currency_id' => $currencyId, + 'transaction_currency_id' => $data['currency_id'], // no longer used. 'description' => $data['description'], 'completed' => 0, 'date' => $data['date'], @@ -200,27 +198,32 @@ class JournalRepository implements JournalRepositoryInterface $this->storeCategoryWithJournal($journal, $data['category']); $this->storeBudgetWithJournal($journal, $data['budget_id']); - // store two transactions: $one = [ - 'journal' => $journal, - 'account' => $accounts['source'], - 'amount' => bcmul($amount, '-1'), - 'description' => null, - 'category' => null, - 'budget' => null, - 'identifier' => 0, + 'journal' => $journal, + 'account' => $accounts['source'], + 'amount' => bcmul($amount, '-1'), + 'transaction_currency_id' => $data['currency_id'], + 'foreign_amount' => is_null($data['foreign_amount']) ? null : bcmul(strval($data['foreign_amount']), '-1'), + 'foreign_currency_id' => $data['foreign_currency_id'], + 'description' => null, + 'category' => null, + 'budget' => null, + 'identifier' => 0, ]; $this->storeTransaction($one); $two = [ - 'journal' => $journal, - 'account' => $accounts['destination'], - 'amount' => $amount, - 'description' => null, - 'category' => null, - 'budget' => null, - 'identifier' => 0, + 'journal' => $journal, + 'account' => $accounts['destination'], + 'amount' => $amount, + 'transaction_currency_id' => $data['currency_id'], + 'foreign_amount' => $data['foreign_amount'], + 'foreign_currency_id' => $data['foreign_currency_id'], + 'description' => null, + 'category' => null, + 'budget' => null, + 'identifier' => 0, ]; $this->storeTransaction($two); @@ -256,11 +259,14 @@ class JournalRepository implements JournalRepositoryInterface { // update actual journal: - $journal->description = $data['description']; - $journal->date = $data['date']; - $accounts = $this->storeAccounts($journal->transactionType, $data); - $data = $this->verifyNativeAmount($data, $accounts); - $amount = strval($data['amount']); + $journal->description = $data['description']; + $journal->date = $data['date']; + $accounts = $this->storeAccounts($journal->transactionType, $data); + $data = $this->verifyNativeAmount($data, $accounts); + $data['amount'] = strval($data['amount']); + $data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']); + + var_dump($data); // unlink all categories, recreate them: $journal->categories()->detach(); @@ -269,9 +275,11 @@ class JournalRepository implements JournalRepositoryInterface $this->storeCategoryWithJournal($journal, $data['category']); $this->storeBudgetWithJournal($journal, $data['budget_id']); + // negative because source loses money. + $this->updateSourceTransaction($journal, $accounts['source'], $data); - $this->updateSourceTransaction($journal, $accounts['source'], bcmul($amount, '-1')); // negative because source loses money. - $this->updateDestinationTransaction($journal, $accounts['destination'], $amount); // positive because destination gets money. + // positive because destination gets money. + $this->updateDestinationTransaction($journal, $accounts['destination'], $data); $journal->save(); @@ -308,9 +316,8 @@ class JournalRepository implements JournalRepositoryInterface public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal { // update actual journal: - $journal->transaction_currency_id = $data['currency_id']; - $journal->description = $data['journal_description']; - $journal->date = $data['date']; + $journal->description = $data['journal_description']; + $journal->date = $data['date']; $journal->save(); Log::debug(sprintf('Updated split journal #%d', $journal->id)); @@ -342,6 +349,7 @@ class JournalRepository implements JournalRepositoryInterface // store each transaction. $identifier = 0; Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions']))); + foreach ($data['transactions'] as $transaction) { Log::debug(sprintf('Split journal update split transaction %d', $identifier)); $transaction = $this->appendTransactionData($transaction, $data); @@ -564,30 +572,40 @@ class JournalRepository implements JournalRepositoryInterface $accounts = $this->storeAccounts($journal->transactionType, $transaction); // store transaction one way: - $one = $this->storeTransaction( + $amount = bcmul(strval($transaction['amount']), '-1'); + $foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1'); + $one = $this->storeTransaction( [ - 'journal' => $journal, - 'account' => $accounts['source'], - 'amount' => bcmul(strval($transaction['amount']), '-1'), - 'description' => $transaction['description'], - 'category' => null, - 'budget' => null, - 'identifier' => $identifier, + 'journal' => $journal, + 'account' => $accounts['source'], + 'amount' => $amount, + 'transaction_currency_id' => $transaction['transaction_currency_id'], + 'foreign_amount' => $foreignAmount, + 'foreign_currency_id' => $transaction['foreign_currency_id'], + 'description' => $transaction['description'], + 'category' => null, + 'budget' => null, + 'identifier' => $identifier, ] ); $this->storeCategoryWithTransaction($one, $transaction['category']); $this->storeBudgetWithTransaction($one, $transaction['budget_id']); // and the other way: - $two = $this->storeTransaction( + $amount = strval($transaction['amount']); + $foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']); + $two = $this->storeTransaction( [ - 'journal' => $journal, - 'account' => $accounts['destination'], - 'amount' => strval($transaction['amount']), - 'description' => $transaction['description'], - 'category' => null, - 'budget' => null, - 'identifier' => $identifier, + 'journal' => $journal, + 'account' => $accounts['destination'], + 'amount' => $amount, + 'transaction_currency_id' => $transaction['transaction_currency_id'], + 'foreign_amount' => $foreignAmount, + 'foreign_currency_id' => $transaction['foreign_currency_id'], + 'description' => $transaction['description'], + 'category' => null, + 'budget' => null, + 'identifier' => $identifier, ] ); $this->storeCategoryWithTransaction($two, $transaction['category']); @@ -603,16 +621,27 @@ class JournalRepository implements JournalRepositoryInterface */ private function storeTransaction(array $data): Transaction { + $fields = [ + 'transaction_journal_id' => $data['journal']->id, + 'account_id' => $data['account']->id, + 'amount' => $data['amount'], + 'foreign_amount' => $data['foreign_amount'], + 'transaction_currency_id' => $data['transaction_currency_id'], + 'foreign_currency_id' => $data['foreign_currency_id'], + 'description' => $data['description'], + 'identifier' => $data['identifier'], + ]; + + + if (is_null($data['foreign_currency_id'])) { + unset($fields['foreign_currency_id']); + } + if (is_null($data['foreign_amount'])) { + unset($fields['foreign_amount']); + } + /** @var Transaction $transaction */ - $transaction = Transaction::create( - [ - 'transaction_journal_id' => $data['journal']->id, - 'account_id' => $data['account']->id, - 'amount' => $data['amount'], - 'description' => $data['description'], - 'identifier' => $data['identifier'], - ] - ); + $transaction = Transaction::create($fields); Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id)); @@ -675,22 +704,23 @@ class JournalRepository implements JournalRepositoryInterface /** * @param TransactionJournal $journal * @param Account $account - * @param string $amount + * @param array $data * * @throws FireflyException */ - private function updateDestinationTransaction(TransactionJournal $journal, Account $account, string $amount) + private function updateDestinationTransaction(TransactionJournal $journal, Account $account, array $data) { - // should be one: $set = $journal->transactions()->where('amount', '>', 0)->get(); if ($set->count() != 1) { - throw new FireflyException( - sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount more than zero.', $journal->id, $set->count()) - ); + throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count())); } /** @var Transaction $transaction */ - $transaction = $set->first(); - $transaction->amount = $amount; + $transaction = $set->first(); + $transaction->amount = app('steam')->positive($data['amount']); + $transaction->transaction_currency_id = $data['currency_id']; + $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']); + $transaction->foreign_currency_id = $data['foreign_currency_id']; + $transaction->account_id = $account->id; $transaction->save(); @@ -699,26 +729,24 @@ class JournalRepository implements JournalRepositoryInterface /** * @param TransactionJournal $journal * @param Account $account - * @param string $amount + * @param array $data * * @throws FireflyException */ - private function updateSourceTransaction(TransactionJournal $journal, Account $account, string $amount) + private function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data) { // should be one: $set = $journal->transactions()->where('amount', '<', 0)->get(); if ($set->count() != 1) { - throw new FireflyException( - sprintf('Journal #%d has an unexpected (%d) amount of transactions with an amount less than zero.', $journal->id, $set->count()) - ); + throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count())); } /** @var Transaction $transaction */ - $transaction = $set->first(); - $transaction->amount = $amount; - $transaction->account_id = $account->id; + $transaction = $set->first(); + $transaction->amount = bcmul(app('steam')->positive($data['amount']), '-1'); + $transaction->transaction_currency_id = $data['currency_id']; + $transaction->foreign_amount = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1'); + $transaction->foreign_currency_id = $data['foreign_currency_id']; $transaction->save(); - - } /** @@ -777,8 +805,10 @@ class JournalRepository implements JournalRepositoryInterface private function verifyNativeAmount(array $data, array $accounts): array { /** @var TransactionType $transactionType */ - $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); - $submittedCurrencyId = $data['currency_id']; + $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); + $submittedCurrencyId = $data['currency_id']; + $data['foreign_amount'] = null; + $data['foreign_currency_id'] = null; // which account to check for what the native currency is? $check = 'source'; @@ -803,11 +833,17 @@ class JournalRepository implements JournalRepositoryInterface } break; case TransactionType::TRANSFER: - // source gets the original amount. - $data['amount'] = strval($data['source_amount']); - $data['currency_id'] = intval($accounts['source']->getMeta('currency_id')); - $data['foreign_amount'] = strval($data['destination_amount']); - $data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id')); + $sourceCurrencyId = intval($accounts['source']->getMeta('currency_id')); + $destinationCurrencyId = intval($accounts['destination']->getMeta('currency_id')); + $data['amount'] = strval($data['source_amount']); + $data['currency_id'] = intval($accounts['source']->getMeta('currency_id')); + + if ($sourceCurrencyId !== $destinationCurrencyId) { + // accounts have different id's, save this info: + $data['foreign_amount'] = strval($data['destination_amount']); + $data['foreign_currency_id'] = $destinationCurrencyId; + } + break; default: throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type)); diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index b56abe0db8..bb0c8bcdd2 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -81,6 +81,8 @@ class JournalTasker implements JournalTaskerInterface ->leftJoin('account_types as source_account_types', 'source_accounts.account_type_id', '=', 'source_account_types.id') ->leftJoin('accounts as destination_accounts', 'destination.account_id', '=', 'destination_accounts.id') ->leftJoin('account_types as destination_account_types', 'destination_accounts.account_type_id', '=', 'destination_account_types.id') + ->leftJoin('transaction_currencies as native_currencies', 'transactions.transaction_currency_id', '=', 'native_currencies.id') + ->leftJoin('transaction_currencies as foreign_currencies', 'transactions.foreign_currency_id', '=', 'foreign_currencies.id') ->where('transactions.amount', '<', 0) ->whereNull('transactions.deleted_at') ->get( @@ -91,12 +93,21 @@ class JournalTasker implements JournalTaskerInterface 'source_accounts.encrypted as account_encrypted', 'source_account_types.type as account_type', 'transactions.amount', + 'transactions.foreign_amount', 'transactions.description', 'destination.id as destination_id', 'destination.account_id as destination_account_id', 'destination_accounts.name as destination_account_name', 'destination_accounts.encrypted as destination_account_encrypted', 'destination_account_types.type as destination_account_type', + 'native_currencies.id as transaction_currency_id', + 'native_currencies.code as transaction_currency_code', + 'native_currencies.symbol as transaction_currency_symbol', + + 'foreign_currencies.id as foreign_currency_id', + 'foreign_currencies.code as foreign_currency_code', + 'foreign_currencies.symbol as foreign_currency_symbol', + ] ); @@ -109,23 +120,31 @@ class JournalTasker implements JournalTaskerInterface $budget = $entry->budgets->first(); $category = $entry->categories->first(); $transaction = [ - 'source_id' => $entry->id, - 'source_amount' => $entry->amount, - 'description' => $entry->description, - 'source_account_id' => $entry->account_id, - 'source_account_name' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name), - 'source_account_type' => $entry->account_type, - 'source_account_before' => $sourceBalance, - 'source_account_after' => bcadd($sourceBalance, $entry->amount), - 'destination_id' => $entry->destination_id, - 'destination_amount' => bcmul($entry->amount, '-1'), - 'destination_account_id' => $entry->destination_account_id, - 'destination_account_type' => $entry->destination_account_type, - 'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $entry->destination_account_name), - 'destination_account_before' => $destinationBalance, - 'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')), - 'budget_id' => is_null($budget) ? 0 : $budget->id, - 'category' => is_null($category) ? '' : $category->name, + 'source_id' => $entry->id, + 'source_amount' => $entry->amount, + 'foreign_source_amount' => $entry->foreign_amount, + 'description' => $entry->description, + 'source_account_id' => $entry->account_id, + 'source_account_name' => Steam::decrypt(intval($entry->account_encrypted), $entry->account_name), + 'source_account_type' => $entry->account_type, + 'source_account_before' => $sourceBalance, + 'source_account_after' => bcadd($sourceBalance, $entry->amount), + 'destination_id' => $entry->destination_id, + 'destination_amount' => bcmul($entry->amount, '-1'), + 'foreign_destination_amount' => is_null($entry->foreign_amount) ? null : bcmul($entry->foreign_amount, '-1'), + 'destination_account_id' => $entry->destination_account_id, + 'destination_account_type' => $entry->destination_account_type, + 'destination_account_name' => Steam::decrypt(intval($entry->destination_account_encrypted), $entry->destination_account_name), + 'destination_account_before' => $destinationBalance, + 'destination_account_after' => bcadd($destinationBalance, bcmul($entry->amount, '-1')), + 'budget_id' => is_null($budget) ? 0 : $budget->id, + 'category' => is_null($category) ? '' : $category->name, + 'transaction_currency_id' => $entry->transaction_currency_id, + 'transaction_currency_code' => $entry->transaction_currency_code, + 'transaction_currency_symbol' => $entry->transaction_currency_symbol, + 'foreign_currency_id' => $entry->foreign_currency_id, + 'foreign_currency_code' => $entry->foreign_currency_code, + 'foreign_currency_symbol' => $entry->foreign_currency_symbol, ]; if ($entry->destination_account_type === AccountType::CASH) { $transaction['destination_account_name'] = ''; diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 32e42cf3bb..7d11d68b7d 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -17,6 +17,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Preferences as Prefs; @@ -101,17 +102,6 @@ class Amount return $format; } - /** - * @param string $amount - * @param bool $coloured - * - * @return string - */ - public function format(string $amount, bool $coloured = true): string - { - return $this->formatAnything($this->getDefaultCurrency(), $amount, $coloured); - } - /** * This method will properly format the given number, in color or "black and white", * as a currency, given two things: the currency required and the current locale. @@ -159,49 +149,6 @@ class Amount return $result; } - /** - * Used in many places (unfortunately). - * - * @param string $currencyCode - * @param string $amount - * @param bool $coloured - * - * @return string - */ - public function formatByCode(string $currencyCode, string $amount, bool $coloured = true): string - { - $currency = TransactionCurrency::where('code', $currencyCode)->first(); - - return $this->formatAnything($currency, $amount, $coloured); - } - - /** - * - * @param \FireflyIII\Models\TransactionJournal $journal - * @param bool $coloured - * - * @return string - */ - public function formatJournal(TransactionJournal $journal, bool $coloured = true): string - { - $currency = $journal->transactionCurrency; - - return $this->formatAnything($currency, $journal->amount(), $coloured); - } - - /** - * @param Transaction $transaction - * @param bool $coloured - * - * @return string - */ - public function formatTransaction(Transaction $transaction, bool $coloured = true) - { - $currency = $transaction->transactionJournal->transactionCurrency; - - return $this->formatAnything($currency, strval($transaction->amount), $coloured); - } - /** * @return Collection */ diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php index cc31681323..3b5678f886 100644 --- a/app/Support/Binder/JournalList.php +++ b/app/Support/Binder/JournalList.php @@ -37,15 +37,8 @@ class JournalList implements BinderInterface $ids = explode(',', $value); /** @var \Illuminate\Support\Collection $object */ $object = TransactionJournal::whereIn('transaction_journals.id', $ids) - ->expanded() ->where('transaction_journals.user_id', auth()->user()->id) - ->get( - [ - 'transaction_journals.*', - 'transaction_types.type AS transaction_type_type', - 'transaction_currencies.code AS transaction_currency_code', - ] - ); + ->get(['transaction_journals.*',]); if ($object->count() > 0) { return $object; diff --git a/app/Support/Models/TransactionJournalTrait.php b/app/Support/Models/TransactionJournalTrait.php index c9cea3ec49..0961fafbb4 100644 --- a/app/Support/Models/TransactionJournalTrait.php +++ b/app/Support/Models/TransactionJournalTrait.php @@ -213,6 +213,14 @@ trait TransactionJournalTrait return 0; } + /** + * @return Transaction + */ + public function positiveTransaction(): Transaction + { + return $this->transactions()->where('amount', '>', 0)->first(); + } + /** * @return Collection */ diff --git a/app/Support/Twig/Account.php b/app/Support/Twig/Account.php deleted file mode 100644 index d2a5520505..0000000000 --- a/app/Support/Twig/Account.php +++ /dev/null @@ -1,63 +0,0 @@ -formatAmountByAccount(), - ]; - - } - - /** - * Will return "active" when a part of the route matches the argument. - * ie. "accounts" will match "accounts.index". - * - * @return Twig_SimpleFunction - */ - protected function formatAmountByAccount(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string { - $currencyId = intval($account->getMeta('currency_id')); - if ($currencyId === 0) { - // Format using default currency: - return AmountFacade::format($amount, $coloured); - } - $currency = TransactionCurrency::find($currencyId); - - return AmountFacade::formatAnything($currency, $amount, $coloured); - }, ['is_safe' => ['html']] - ); - } - - -} \ No newline at end of file diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php new file mode 100644 index 0000000000..eb06a98ebf --- /dev/null +++ b/app/Support/Twig/AmountFormat.php @@ -0,0 +1,109 @@ +formatAmount(), + $this->formatAmountPlain(), + ]; + + } + + /** + * {@inheritDoc} + */ + public function getFunctions(): array + { + return [ + $this->formatAmountByAccount(), + ]; + + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName(): string + { + return 'FireflyIII\Support\Twig\AmountFormat'; + } + + /** + * + * @return Twig_SimpleFilter + */ + protected function formatAmount(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'formatAmount', function (string $string): string { + + return app('amount')->format($string); + }, ['is_safe' => ['html']] + ); + } + + /** + * Will format the amount by the currency related to the given account. + * + * @return Twig_SimpleFunction + */ + protected function formatAmountByAccount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string { + $currencyId = intval($account->getMeta('currency_id')); + if ($currencyId === 0) { + // Format using default currency: + return app('amount')->format($amount, $coloured); + } + $currency = TransactionCurrency::find($currencyId); + + return app('amount')->formatAnything($currency, $amount, $coloured); + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFilter + */ + protected function formatAmountPlain(): Twig_SimpleFilter + { + return new Twig_SimpleFilter( + 'formatAmountPlain', function (string $string): string { + + return app('amount')->format($string, false); + }, ['is_safe' => ['html']] + ); + } +} \ No newline at end of file diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 5db0ba95f6..9603e54e44 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -38,9 +38,6 @@ class General extends Twig_Extension public function getFilters(): array { return [ - $this->formatAmount(), - $this->formatAmountPlain(), - $this->formatJournal(), $this->balance(), $this->formatFilesize(), $this->mimeIcon(), @@ -173,33 +170,6 @@ class General extends Twig_Extension ); } - /** - * - * @return Twig_SimpleFilter - */ - protected function formatAmount(): Twig_SimpleFilter - { - return new Twig_SimpleFilter( - 'formatAmount', function (string $string): string { - - return app('amount')->format($string); - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFilter - */ - protected function formatAmountPlain(): Twig_SimpleFilter - { - return new Twig_SimpleFilter( - 'formatAmountPlain', function (string $string): string { - - return app('amount')->format($string, false); - }, ['is_safe' => ['html']] - ); - } - /** * @return Twig_SimpleFilter */ @@ -223,17 +193,6 @@ class General extends Twig_Extension ); } - /** - * @return Twig_SimpleFilter - */ - protected function formatJournal(): Twig_SimpleFilter - { - return new Twig_SimpleFilter( - 'formatJournal', function (TransactionJournal $journal): string { - return app('amount')->formatJournal($journal); - }, ['is_safe' => ['html']] - ); - } /** * @return Twig_SimpleFunction diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php index d4b8e84850..a8d97c97f8 100644 --- a/app/Support/Twig/Transaction.php +++ b/app/Support/Twig/Transaction.php @@ -16,7 +16,6 @@ namespace FireflyIII\Support\Twig; use Amount; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction as TransactionModel; -use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use Steam; use Twig_Extension; @@ -30,49 +29,6 @@ use Twig_SimpleFunction; */ class Transaction extends Twig_Extension { - - /** - * @return Twig_SimpleFunction - */ - public function formatAnything(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'formatAnything', function (TransactionCurrency $currency, string $amount): string { - - return Amount::formatAnything($currency, $amount, true); - - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function formatAnythingPlain(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'formatAnythingPlain', function (TransactionCurrency $currency, string $amount): string { - - return Amount::formatAnything($currency, $amount, false); - - }, ['is_safe' => ['html']] - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function formatByCode(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'formatByCode', function (string $currencyCode, string $amount): string { - - return Amount::formatByCode($currencyCode, $amount, true); - - }, ['is_safe' => ['html']] - ); - } - /** * @return array */ @@ -91,17 +47,13 @@ class Transaction extends Twig_Extension public function getFunctions(): array { $functions = [ - $this->formatAnything(), - $this->formatAnythingPlain(), $this->transactionSourceAccount(), $this->transactionDestinationAccount(), - $this->optionalJournalAmount(), $this->transactionBudgets(), $this->transactionIdBudgets(), $this->transactionCategories(), $this->transactionIdCategories(), $this->splitJournalIndicator(), - $this->formatByCode(), ]; return $functions; @@ -117,33 +69,6 @@ class Transaction extends Twig_Extension return 'transaction'; } - /** - * @return Twig_SimpleFunction - */ - public function optionalJournalAmount(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'optionalJournalAmount', function (int $journalId, string $transactionAmount, string $code, string $type): string { - // get amount of journal: - $amount = strval(TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->where('amount', '<', 0)->sum('amount')); - // display deposit and transfer positive - if ($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) { - $amount = bcmul($amount, '-1'); - } - - // not equal to transaction amount? - if (bccomp($amount, $transactionAmount) !== 0 && bccomp($amount, bcmul($transactionAmount, '-1')) !== 0) { - //$currency = - return sprintf(' (%s)', Amount::formatByCode($code, $amount, true)); - } - - return ''; - - - }, ['is_safe' => ['html']] - ); - } - /** * @return Twig_SimpleFunction */ diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 283a342b20..bd8d30b2a9 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -89,6 +89,7 @@ $factory->define( 'user_id' => 1, 'transaction_type_id' => 1, 'bill_id' => null, + // TODO update this transaction currency reference. 'transaction_currency_id' => 1, 'description' => $faker->words(3, true), 'date' => '2017-01-01', diff --git a/public/js/ff/transactions/single/edit.js b/public/js/ff/transactions/single/edit.js index 17f2d0a5c0..d608568c65 100644 --- a/public/js/ff/transactions/single/edit.js +++ b/public/js/ff/transactions/single/edit.js @@ -38,11 +38,12 @@ function updateInitialPage() { $('#native_amount_holder').hide(); $('#amount_holder').hide(); - if (journalData.native_currency.id === journalData.currency.id) { + + if (journalData.native_currency.id === journalData.destination_currency.id) { $('#exchange_rate_instruction_holder').hide(); $('#destination_amount_holder').hide(); } - if (journalData.native_currency.id !== journalData.currency.id) { + if (journalData.native_currency.id !== journalData.destination_currency.id) { $('#exchange_rate_instruction_holder').show().find('p').text(getTransferExchangeInstructions()); } diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index a91c0a3422..a97739b122 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -166,11 +166,31 @@ function resetSplits() { var input = $(v); input.attr('name', 'transactions[' + i + '][amount]'); }); + + // ends with ][foreign_amount] + $.each($('input[name$="][foreign_amount]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transactions[' + i + '][foreign_amount]'); + }); + + // ends with ][transaction_currency_id] + $.each($('input[name$="][transaction_currency_id]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transactions[' + i + '][transaction_currency_id]'); + }); + + // ends with ][foreign_currency_id] + $.each($('input[name$="][foreign_currency_id]"]'), function (i, v) { + var input = $(v); + input.attr('name', 'transactions[' + i + '][foreign_currency_id]'); + }); + // ends with ][budget_id] $.each($('select[name$="][budget_id]"]'), function (i, v) { var input = $(v); input.attr('name', 'transactions[' + i + '][budget_id]'); }); + // ends with ][category] $.each($('input[name$="][category]"]'), function (i, v) { var input = $(v); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index da1c9bcb43..54f7cbc4ae 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Split this transfer', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import diff --git a/resources/views/list/journals-tiny.twig b/resources/views/list/journals-tiny.twig index 190d28cf37..63306fa102 100644 --- a/resources/views/list/journals-tiny.twig +++ b/resources/views/list/journals-tiny.twig @@ -14,10 +14,8 @@ {{ transaction.description }} {% endif %} - - {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + {# TODO replace with new format code #} + XX.XX {% endfor %} diff --git a/resources/views/list/journals.twig b/resources/views/list/journals.twig index db348d7acb..acd95000bb 100644 --- a/resources/views/list/journals.twig +++ b/resources/views/list/journals.twig @@ -64,14 +64,11 @@ {% if transaction.transaction_type_type == 'Transfer' %} - {{ formatByCode(transaction.transaction_currency_code, steam_positive(transaction.transaction_amount)) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + {# TODO format amount of transaction. #} + {# TODO format: Amount of transaction (amount in foreign) / total (total foreign) #} + XX.XX {% else %} - - {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + XX.XX {% endif %} diff --git a/resources/views/popup/list/journals.twig b/resources/views/popup/list/journals.twig index d6f97dcc59..1177cce1f5 100644 --- a/resources/views/popup/list/journals.twig +++ b/resources/views/popup/list/journals.twig @@ -45,10 +45,8 @@ - - {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + {# TODO replace with new format code #} + XX.XX {{ transaction.date.formatLocalized(monthAndDayFormat) }} diff --git a/resources/views/reports/partials/journals-audit.twig b/resources/views/reports/partials/journals-audit.twig index 0c9fd25adc..48c5430658 100644 --- a/resources/views/reports/partials/journals-audit.twig +++ b/resources/views/reports/partials/journals-audit.twig @@ -59,10 +59,8 @@ {{ transaction.before|formatAmount }} - - {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + {# TODO replace with new format code #} + XX.XX {{ transaction.after|formatAmount }} diff --git a/resources/views/search/partials/transactions-large.twig b/resources/views/search/partials/transactions-large.twig index 7502719e27..ca8c06ac92 100644 --- a/resources/views/search/partials/transactions-large.twig +++ b/resources/views/search/partials/transactions-large.twig @@ -62,17 +62,8 @@ - {% if transaction.transaction_type_type == 'Transfer' %} - - {{ formatByCode(transaction.transaction_currency_code, steam_positive(transaction.transaction_amount)) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} - {% else %} - - {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} - {% endif %} + {# TODO replace with new format code #} + XX.XX diff --git a/resources/views/search/partials/transactions.twig b/resources/views/search/partials/transactions.twig index 587c9b432d..387b5ec07b 100644 --- a/resources/views/search/partials/transactions.twig +++ b/resources/views/search/partials/transactions.twig @@ -39,10 +39,8 @@ - - {{ formatByCode(transaction.transaction_currency_code, transaction.transaction_amount) }} - - {{ optionalJournalAmount(transaction.journal_id, transaction.transaction_amount, transaction.transaction_currency_code, transaction.transaction_type_type) }} + {# TODO replace with new format code #} + XX.XX diff --git a/resources/views/transactions/mass-delete.twig b/resources/views/transactions/mass-delete.twig index f3525c1d6c..2049df92f8 100644 --- a/resources/views/transactions/mass-delete.twig +++ b/resources/views/transactions/mass-delete.twig @@ -43,7 +43,8 @@ {{ journal.description }} - {{ journal|formatJournal }} + {# TODO fix amount display #} + XX.XX {{ journal.date.formatLocalized(monthAndDayFormat) }} diff --git a/resources/views/transactions/mass/edit.twig b/resources/views/transactions/mass/edit.twig index c0c2ccc123..a8bab41a7d 100644 --- a/resources/views/transactions/mass/edit.twig +++ b/resources/views/transactions/mass/edit.twig @@ -47,11 +47,20 @@

- {{ journal.transactionCurrency.symbol }} + {{ journal.currency_symbol }} +
- + {% if journal.foreign_amount %} + {# insert foreign data #} +
+ {{ journal.foreign_currency.symbol }} + + +
+ {% endif %} {# DATE #} diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 3c086d1899..b115ca16c8 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -36,14 +36,9 @@ {{ 'total_amount'|_ }} - {{ journal|formatJournal }} - {% if journal.hasMeta('foreign_amount') %} - {% if journal.transactiontype.type == 'Withdrawal' %} - ({{ formatAnything(foreignCurrency, journal.getMeta('foreign_amount')*-1) }}) - {% else %} - ({{ formatAnything(foreignCurrency, journal.getMeta('foreign_amount')) }}) - {% endif %} - {% endif %} + + {# TODO fix amount display #} + XX.XX @@ -304,9 +299,8 @@ - - {{ formatAnything(journal.transactionCurrency, transaction.source_account_before) }} - ⟶ {{ formatAnything(journal.transactionCurrency, transaction.source_account_after) }} + {# TODO replace with new display: #} + XX.XX {% if transaction.destination_account_type == 'Cash account' %} @@ -317,25 +311,27 @@ - - {{ formatAnything(journal.transactionCurrency, transaction.destination_account_before) }} - ⟶ {{ formatAnything(journal.transactionCurrency, transaction.destination_account_after) }} + {# TODO replace with new format code #} + XX.XX {% if journal.transactiontype.type == 'Deposit' %} - {{ formatAnything(journal.transactionCurrency, transaction.destination_amount) }} + {# TODO replace with new format code #} + XX.XX {% endif %} {% if journal.transactiontype.type == 'Withdrawal' %} - {{ formatAnything(journal.transactionCurrency, transaction.source_amount) }} + {# TODO replace with new format code #} + XX.XX {% endif %} {% if journal.transactiontype.type == 'Transfer' %} - {{ formatAnythingPlain(journal.transactionCurrency, transaction.destination_amount) }} + {# TODO replace with new format code #} + XX.XX {% endif %} diff --git a/resources/views/transactions/single/edit.twig b/resources/views/transactions/single/edit.twig index 4ef0d2ceaa..fc09562117 100644 --- a/resources/views/transactions/single/edit.twig +++ b/resources/views/transactions/single/edit.twig @@ -64,9 +64,9 @@ {{ ExpandedForm.nonSelectableAmount('native_amount', data.native_amount, {currency: data.native_currency}) }} - {{ ExpandedForm.nonSelectableAmount('source_amount', data.native_amount, {currency: data.native_currency }) }} + {{ ExpandedForm.nonSelectableAmount('source_amount', data.source_amount, {currency: data.source_currency }) }} - {{ ExpandedForm.nonSelectableAmount('destination_amount', data.amount, {currency: data.currency }) }} + {{ ExpandedForm.nonSelectableAmount('destination_amount', data.destination_amount, {currency: data.destination_currency }) }} {# ALWAYS SHOW DATE #} {{ ExpandedForm.date('date',data['date']) }} diff --git a/resources/views/transactions/split/edit.twig b/resources/views/transactions/split/edit.twig index e78dffb4ab..a0420fe4c8 100644 --- a/resources/views/transactions/split/edit.twig +++ b/resources/views/transactions/split/edit.twig @@ -40,9 +40,6 @@ {# DESCRIPTION IS ALWAYS AVAILABLE #} {{ ExpandedForm.text('journal_description', journal.description) }} - {# CURRENCY IS NEW FOR SPLIT JOURNALS #} - {{ ExpandedForm.select('currency_id', currencies, preFilled.currency_id) }} - {# show source if withdrawal or transfer #} {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} {{ ExpandedForm.select('journal_source_account_id', assetAccounts, preFilled.journal_source_account_id) }} @@ -59,6 +56,7 @@ {% endif %} {# TOTAL AMOUNT IS STATIC TEXT #} + {# TODO this does not reflect the actual currency (currencies) #} {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} @@ -204,7 +202,7 @@ {{ trans('list.source') }} {% endif %} - {{ trans('list.amount') }} + {{ trans('list.amount') }} {# only withdrawal has budget #} {% if preFilled.what == 'withdrawal' %} @@ -234,7 +232,7 @@ {% endif %} - + {# deposit has several source names #} {% if preFilled.what == 'deposit' %} {% endif %} - + {# two fields for amount #} - +
+
{{ transaction.transaction_currency_symbol }}
+ +
+ + + {# foreign amount #} + + {% if transaction.foreign_amount != null %} +
+
{{ transaction.foreign_currency_symbol }}
+ +
+ + {% endif %} {% if preFilled.what == 'withdrawal' %} From 3464ab1527f1a1c3fdff416ff5f88d19375fe7af Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:13 +0200 Subject: [PATCH 059/103] New translations firefly.php (Spanish) --- resources/lang/es_ES/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 3a45d7aa57..4d6a6fd94e 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Split this transfer', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import From 6c8e10255b99ace30780d70e01d71698ce00debe Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:17 +0200 Subject: [PATCH 060/103] New translations form.php (Spanish) --- resources/lang/es_ES/form.php | 189 ++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 resources/lang/es_ES/form.php diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php new file mode 100644 index 0000000000..20be49e75a --- /dev/null +++ b/resources/lang/es_ES/form.php @@ -0,0 +1,189 @@ + 'Bank name', + 'bank_balance' => 'Balance', + 'savings_balance' => 'Savings balance', + 'credit_card_limit' => 'Credit card limit', + 'automatch' => 'Match automatically', + 'skip' => 'Skip', + 'name' => 'Name', + 'active' => 'Active', + 'amount_min' => 'Minimum amount', + 'amount_max' => 'Maximum amount', + 'match' => 'Matches on', + 'repeat_freq' => 'Repeats', + 'journal_currency_id' => 'Currency', + 'currency_id' => 'Currency', + 'attachments' => 'Attachments', + 'journal_amount' => 'Amount', + 'journal_asset_source_account' => 'Asset account (source)', + 'journal_source_account_name' => 'Revenue account (source)', + 'journal_source_account_id' => 'Asset account (source)', + 'BIC' => 'BIC', + 'account_from_id' => 'From account', + 'account_to_id' => 'To account', + 'source_account' => 'Source account', + 'destination_account' => 'Destination account', + 'journal_destination_account_id' => 'Asset account (destination)', + 'asset_destination_account' => 'Asset account (destination)', + 'asset_source_account' => 'Asset account (source)', + 'journal_description' => 'Description', + 'note' => 'Notes', + 'split_journal' => 'Split this transaction', + 'split_journal_explanation' => 'Split this transaction in multiple parts', + 'currency' => 'Currency', + 'account_id' => 'Asset account', + 'budget_id' => 'Budget', + 'openingBalance' => 'Opening balance', + 'tagMode' => 'Tag mode', + 'tagPosition' => 'Tag location', + 'virtualBalance' => 'Virtual balance', + 'longitude_latitude' => 'Location', + 'targetamount' => 'Target amount', + 'accountRole' => 'Account role', + 'openingBalanceDate' => 'Opening balance date', + 'ccType' => 'Credit card payment plan', + 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Piggy bank', + 'returnHere' => 'Return here', + 'returnHereExplanation' => 'After storing, return here to create another one.', + 'returnHereUpdateExplanation' => 'After updating, return here.', + 'description' => 'Description', + 'expense_account' => 'Expense account', + 'revenue_account' => 'Revenue account', + 'decimal_places' => 'Decimal places', + 'exchange_rate_instruction' => 'Foreign currencies', + 'exchanged_amount' => 'Exchanged amount', + 'source_amount' => 'Amount (source)', + 'destination_amount' => 'Amount (destination)', + 'native_amount' => 'Native amount', + + 'revenue_account_source' => 'Revenue account (source)', + 'source_account_asset' => 'Source account (asset account)', + 'destination_account_expense' => 'Destination account (expense account)', + 'destination_account_asset' => 'Destination account (asset account)', + 'source_account_revenue' => 'Source account (revenue account)', + 'type' => 'Type', + 'convert_Withdrawal' => 'Convert withdrawal', + 'convert_Deposit' => 'Convert deposit', + 'convert_Transfer' => 'Convert transfer', + + + 'amount' => 'Amount', + 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', + 'category' => 'Category', + 'tags' => 'Tags', + 'deletePermanently' => 'Delete permanently', + 'cancel' => 'Cancel', + 'targetdate' => 'Target date', + 'tag' => 'Tag', + 'under' => 'Under', + 'symbol' => 'Symbol', + 'code' => 'Code', + 'iban' => 'IBAN', + 'accountNumber' => 'Account number', + 'has_headers' => 'Headers', + 'date_format' => 'Date format', + 'specifix' => 'Bank- or file specific fixes', + 'attachments[]' => 'Attachments', + 'store_new_withdrawal' => 'Store new withdrawal', + 'store_new_deposit' => 'Store new deposit', + 'store_new_transfer' => 'Store new transfer', + 'add_new_withdrawal' => 'Add a new withdrawal', + 'add_new_deposit' => 'Add a new deposit', + 'add_new_transfer' => 'Add a new transfer', + 'noPiggybank' => '(no piggy bank)', + 'title' => 'Title', + 'notes' => 'Notes', + 'filename' => 'File name', + 'mime' => 'Mime type', + 'size' => 'Size', + 'trigger' => 'Trigger', + 'stop_processing' => 'Stop processing', + 'start_date' => 'Start of range', + 'end_date' => 'End of range', + 'export_start_range' => 'Start of export range', + 'export_end_range' => 'End of export range', + 'export_format' => 'File format', + 'include_attachments' => 'Include uploaded attachments', + 'include_old_uploads' => 'Include imported data', + 'accounts' => 'Export transactions from these accounts', + 'delete_account' => 'Delete account ":name"', + 'delete_bill' => 'Delete bill ":name"', + 'delete_budget' => 'Delete budget ":name"', + 'delete_category' => 'Delete category ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'delete_journal' => 'Delete transaction with description ":description"', + 'delete_attachment' => 'Delete attachment ":name"', + 'delete_rule' => 'Delete rule ":title"', + 'delete_rule_group' => 'Delete rule group ":title"', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', + 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', + 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', + 'delete_all_permanently' => 'Delete selected permanently', + 'update_all_journals' => 'Update these transactions', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will spared deletion.', + + 'email' => 'Email address', + 'password' => 'Password', + 'password_confirmation' => 'Password (again)', + 'blocked' => 'Is blocked?', + 'blocked_code' => 'Reason for block', + + + // admin + 'domain' => 'Domain', + 'single_user_mode' => 'Single user mode', + 'must_confirm_account' => 'New users must activate account', + 'is_demo_site' => 'Is demo site', + + + // import + 'import_file' => 'Import file', + 'configuration_file' => 'Configuration file', + 'import_file_type' => 'Import file type', + 'csv_comma' => 'A comma (,)', + 'csv_semicolon' => 'A semicolon (;)', + 'csv_tab' => 'A tab (invisible)', + 'csv_delimiter' => 'CSV field delimiter', + 'csv_import_account' => 'Default import account', + 'csv_config' => 'CSV import configuration', + + + 'due_date' => 'Due date', + 'payment_date' => 'Payment date', + 'invoice_date' => 'Invoice date', + 'internal_reference' => 'Internal reference', +]; \ No newline at end of file From e041a5e03705503b208923ddf99cc9546fe17a31 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:19 +0200 Subject: [PATCH 061/103] New translations help.php (Spanish) --- resources/lang/es_ES/help.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 resources/lang/es_ES/help.php diff --git a/resources/lang/es_ES/help.php b/resources/lang/es_ES/help.php new file mode 100644 index 0000000000..61210ffe41 --- /dev/null +++ b/resources/lang/es_ES/help.php @@ -0,0 +1,33 @@ + 'Welcome to Firefly III', + 'main-content-text' => 'Do yourself a favor and follow this short guide to make sure you know your way around.', + 'sidebar-toggle-title' => 'Sidebar to create stuff', + 'sidebar-toggle-text' => 'Hidden under the plus icon are all the buttons to create new stuff. Accounts, transactions, everything!', + 'account-menu-title' => 'All your accounts', + 'account-menu-text' => 'Here you can find all the accounts you\'ve made.', + 'budget-menu-title' => 'Budgets', + 'budget-menu-text' => 'Use this page to organise your finances and limit spending.', + 'report-menu-title' => 'Reports', + 'report-menu-text' => 'Check this out when you want a solid overview of your finances.', + 'transaction-menu-title' => 'Transactions', + 'transaction-menu-text' => 'All transactions you\'ve created can be found here.', + 'option-menu-title' => 'Options', + 'option-menu-text' => 'This is pretty self-explanatory.', + 'main-content-end-title' => 'The end!', + 'main-content-end-text' => 'Remember that every page has a small question mark at the right top. Click it to get help about the page you\'re on.', + 'index' => 'index', + 'home' => 'home', +]; \ No newline at end of file From 3cf53604a1c2fe501fcac5641cd9f0c0c6cd091c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:31 +0200 Subject: [PATCH 062/103] New translations firefly.php (Portuguese, Brazilian) --- resources/lang/pt_BR/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 7d7ee85b8c..904c5a3ceb 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Dividir essa transferência', 'cannot_edit_multiple_source' => 'Você não pode editar transações parceladas #:id com a descrição ":description" porque ele contém várias contas de origem.', 'cannot_edit_multiple_dest' => 'Você não pode editar transações parceladas #:id com a descrição ":description" porque ele contém várias contas de destino.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'Você não selecionou nenhuma transação válida para editar.', // import From f7fde93ed24daeb02d89a25b8322f1bb482468a5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:37 +0200 Subject: [PATCH 063/103] New translations firefly.php (Dutch) --- resources/lang/nl_NL/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 2203382d99..186615f08a 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Splits deze overschrijving', 'cannot_edit_multiple_source' => 'Je kan transactie #:id met omschrijving ":description" niet splitsen, want deze bevat meerdere bronrekeningen.', 'cannot_edit_multiple_dest' => 'Je kan transactie #:id met omschrijving ":description" niet wijzigen, want deze bevat meerdere doelrekeningen.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'Je hebt geen geldige transacties geselecteerd.', // import From e45ffba0100da5370e762d83fb830e77658f13e8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:39 +0200 Subject: [PATCH 064/103] New translations passwords.php (Spanish) --- resources/lang/es_ES/passwords.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/es_ES/passwords.php b/resources/lang/es_ES/passwords.php index 1dfc1a738c..f61f371a0d 100644 --- a/resources/lang/es_ES/passwords.php +++ b/resources/lang/es_ES/passwords.php @@ -16,4 +16,4 @@ return [ 'sent' => 'Te enviamos un correo con el link para reestablecer tu contraseña!', 'reset' => 'Tu contraseña fue reestablecida!', 'blocked' => 'Buen intento.', -]; +]; \ No newline at end of file From e08fd399d20bf33a773e394a5ddeded0b6bd619b Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:40 +0200 Subject: [PATCH 065/103] New translations csv.php (Spanish) --- resources/lang/es_ES/csv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php index 47b611f81a..27143be1bc 100644 --- a/resources/lang/es_ES/csv.php +++ b/resources/lang/es_ES/csv.php @@ -9,7 +9,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); return [ @@ -77,4 +77,4 @@ return [ 'column_tags-space' => 'Etiquetas (separadas por espacios)', 'column_account-number' => 'Caja de ahorro (número de cuenta)', 'column_opposing-number' => 'Cuenta opuesta (número de cuenta)', -]; +]; \ No newline at end of file From db2898dfe5243fd9e2122412d6ab321d3e47d437 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:44 +0200 Subject: [PATCH 066/103] New translations auth.php (Spanish) --- resources/lang/es_ES/auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/es_ES/auth.php b/resources/lang/es_ES/auth.php index 45d765e4fa..3f4e103f18 100644 --- a/resources/lang/es_ES/auth.php +++ b/resources/lang/es_ES/auth.php @@ -25,4 +25,4 @@ return [ 'failed' => 'Las credenciales no coinciden con los registros.', 'throttle' => 'Demasiados intentos de inicio de sesión. Por favor reintente en :seconds segundos.', -]; +]; \ No newline at end of file From 3b2fe139025b0411fca8329af99cdf5a24fac06f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:47 +0200 Subject: [PATCH 067/103] New translations firefly.php (Slovenian) --- resources/lang/sl_SI/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/sl_SI/firefly.php b/resources/lang/sl_SI/firefly.php index 8358bb5d84..c5edb42c52 100644 --- a/resources/lang/sl_SI/firefly.php +++ b/resources/lang/sl_SI/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Split this transfer', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import From 9487a95c137dcc58e57192106fa00e291b1142ac Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:40:54 +0200 Subject: [PATCH 068/103] New translations firefly.php (Polish) --- resources/lang/pl_PL/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 25cf01b66a..e320c548e4 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Podziel ten transfer', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'Nie wybrałeś żadnych poprawnych transakcji do edycji.', // import From a505406ee7f99488bab9f1d2ee7db4d072b071e3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:41:03 +0200 Subject: [PATCH 069/103] New translations firefly.php (Chinese Traditional) --- resources/lang/zh_TW/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/zh_TW/firefly.php b/resources/lang/zh_TW/firefly.php index 47700e4be2..63f0e11527 100644 --- a/resources/lang/zh_TW/firefly.php +++ b/resources/lang/zh_TW/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Split this transfer', 'cannot_edit_multiple_source' => '你不能修改描述為 ":description" 的分割交易 #:id 因為他有多個來源帳號。', 'cannot_edit_multiple_dest' => '你不能修改描述為 ":description" 的分割交易 #:id 因為他有多個目標帳號。', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import From 7801274d33091da2206ec84b4e29c028a78f0dc5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:41:15 +0200 Subject: [PATCH 070/103] New translations firefly.php (German) --- resources/lang/de_DE/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index e025b6a7c0..34873bd40c 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -963,6 +963,7 @@ Sollen zusätzlich Ihre Girokonten angezeigt werden?', 'split_this_transfer' => 'Diese Überweisung aufteilen', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import From 3838b2145952ed721c9dae6f761a8f203bcfeb69 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 13:41:19 +0200 Subject: [PATCH 071/103] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index eebbf5c9bf..00cc9d9ad1 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -962,6 +962,7 @@ return [ 'split_this_transfer' => 'Split this transfer', 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', // import From a487c7b4b2f8a5f35be537c821b750e7ae1d56a0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 4 Jun 2017 23:39:26 +0200 Subject: [PATCH 072/103] Make sure amounts are formatted, and fixed some issues. --- app/Helpers/Collector/JournalCollector.php | 13 +- app/Helpers/Report/PopupReport.php | 2 +- app/Http/Controllers/JsonController.php | 27 ++-- app/Http/Controllers/PiggyBankController.php | 24 ++- .../Account/AccountRepository.php | 26 +++- app/Repositories/Journal/JournalTasker.php | 4 + .../PiggyBank/PiggyBankRepository.php | 4 +- app/Support/Amount.php | 80 +++++++++- app/Support/Twig/AmountFormat.php | 141 +++++++++++++++++- resources/views/list/journals-tiny.twig | 3 +- resources/views/list/journals.twig | 9 +- resources/views/popup/list/journals.twig | 2 +- .../reports/partials/journals-audit.twig | 3 +- resources/views/transactions/mass-delete.twig | 5 +- resources/views/transactions/show.twig | 29 +--- 15 files changed, 301 insertions(+), 71 deletions(-) diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index a85535d907..fb19073685 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -65,12 +65,22 @@ class JournalCollector implements JournalCollectorInterface 'bills.name as bill_name', 'bills.name_encrypted as bill_name_encrypted', 'transactions.id as id', - 'transactions.amount as transaction_amount', + 'transactions.description as transaction_description', 'transactions.account_id', 'transactions.identifier', 'transactions.transaction_journal_id', + + 'transactions.amount as transaction_amount', 'transaction_currencies.code as transaction_currency_code', + 'transaction_currencies.symbol as transaction_currency_symbol', + 'transaction_currencies.decimal_places as transaction_currency_dp', + + 'transactions.foreign_amount as transaction_foreign_amount', + 'foreign_currencies.code as foreign_currency_code', + 'foreign_currencies.symbol as foreign_currency_symbol', + 'foreign_currencies.decimal_places as foreign_currency_dp', + 'accounts.name as account_name', 'accounts.encrypted as account_encrypted', 'account_types.type as account_type', @@ -489,6 +499,7 @@ class JournalCollector implements JournalCollectorInterface ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') ->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id') ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transactions.transaction_currency_id') + ->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', 'transactions.foreign_currency_id') ->whereNull('transactions.deleted_at') ->whereNull('transaction_journals.deleted_at') ->where('transaction_journals.user_id', $this->user->id) diff --git a/app/Helpers/Report/PopupReport.php b/app/Helpers/Report/PopupReport.php index f4d0a07d17..265c930e6c 100644 --- a/app/Helpers/Report/PopupReport.php +++ b/app/Helpers/Report/PopupReport.php @@ -187,7 +187,7 @@ class PopupReport implements PopupReportInterface $journals = $journals->filter( function (Transaction $transaction) use ($report) { // get the destinations: - $destinations = $transaction->destinationAccountList($transaction->transactionJournal)->pluck('id')->toArray(); + $destinations = $transaction->transactionJournal->destinationAccountList()->pluck('id')->toArray(); // do these intersect with the current list? return !empty(array_intersect($report, $destinations)); diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index c8e916e231..4449e17b01 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -116,10 +116,11 @@ class JsonController extends Controller * Since both this method and the chart use the exact same data, we can suffice * with calling the one method in the bill repository that will get this amount. */ - $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. - $amount = bcmul($amount, '-1'); + $amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount. + $amount = bcmul($amount, '-1'); + $currency = Amount::getDefaultCurrency(); - $data = ['box' => 'bills-paid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + $data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount]; return Response::json($data); } @@ -131,10 +132,11 @@ class JsonController extends Controller */ public function boxBillsUnpaid(BillRepositoryInterface $repository) { - $start = session('start', Carbon::now()->startOfMonth()); - $end = session('end', Carbon::now()->endOfMonth()); - $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. - $data = ['box' => 'bills-unpaid', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + $start = session('start', Carbon::now()->startOfMonth()); + $end = session('end', Carbon::now()->endOfMonth()); + $amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount. + $currency = Amount::getDefaultCurrency(); + $data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount]; return Response::json($data); } @@ -167,8 +169,9 @@ class JsonController extends Controller ->setTypes([TransactionType::DEPOSIT]) ->withOpposingAccount(); - $amount = strval($collector->getJournals()->sum('transaction_amount')); - $data = ['box' => 'in', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + $amount = strval($collector->getJournals()->sum('transaction_amount')); + $currency = Amount::getDefaultCurrency(); + $data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount]; $cache->store($data); return Response::json($data); @@ -200,9 +203,9 @@ class JsonController extends Controller $collector->setAllAssetAccounts()->setRange($start, $end) ->setTypes([TransactionType::WITHDRAWAL]) ->withOpposingAccount(); - $amount = strval($collector->getJournals()->sum('transaction_amount')); - - $data = ['box' => 'out', 'amount' => Amount::format($amount, false), 'amount_raw' => $amount]; + $amount = strval($collector->getJournals()->sum('transaction_amount')); + $currency = Amount::getDefaultCurrency(); + $data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount]; $cache->store($data); return Response::json($data); diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 052900cba5..564ee720ae 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -276,18 +276,29 @@ class PiggyBankController extends Controller */ public function postAdd(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { - $amount = $request->get('amount'); - + $amount = $request->get('amount'); + $currency = Amount::getDefaultCurrency(); if ($repository->canAddAmount($piggyBank, $amount)) { $repository->addAmount($piggyBank, $amount); - Session::flash('success', strval(trans('firefly.added_amount_to_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name]))); + Session::flash( + 'success', strval( + trans( + 'firefly.added_amount_to_piggy', + ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name] + ) + ) + ); Preferences::mark(); return redirect(route('piggy-banks.index')); } Log::error('Cannot add ' . $amount . ' because canAddAmount returned false.'); - Session::flash('error', strval(trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); + Session::flash( + 'error', strval( + trans('firefly.cannot_add_amount_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)]) + ) + ); return redirect(route('piggy-banks.index')); } @@ -302,10 +313,11 @@ class PiggyBankController extends Controller public function postRemove(Request $request, PiggyBankRepositoryInterface $repository, PiggyBank $piggyBank) { $amount = $request->get('amount'); + $currency = Amount::getDefaultCurrency(); if ($repository->canRemoveAmount($piggyBank, $amount)) { $repository->removeAmount($piggyBank, $amount); Session::flash( - 'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::format($amount, false), 'name' => $piggyBank->name])) + 'success', strval(trans('firefly.removed_amount_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => $piggyBank->name])) ); Preferences::mark(); @@ -314,7 +326,7 @@ class PiggyBankController extends Controller $amount = strval(round($request->get('amount'), 12)); - Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::format($amount, false), 'name' => e($piggyBank->name)]))); + Session::flash('error', strval(trans('firefly.cannot_remove_from_piggy', ['amount' => Amount::formatAnything($currency, $amount, false), 'name' => e($piggyBank->name)]))); return redirect(route('piggy-banks.index')); } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 7b594555b8..bb9ace5034 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -481,7 +481,6 @@ class AccountRepository implements AccountRepositoryInterface [ 'user_id' => $this->user->id, 'transaction_type_id' => $transactionType->id, - // TODO update this transaction currency reference. 'transaction_currency_id' => $currencyId, 'description' => 'Initial balance for "' . $account->name . '"', 'completed' => true, @@ -502,9 +501,23 @@ class AccountRepository implements AccountRepositoryInterface $secondAmount = $amount; } - $one = new Transaction(['account_id' => $firstAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $firstAmount]); + $one = new Transaction( + [ + 'account_id' => $firstAccount->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $firstAmount, + 'transaction_currency_id' => $currencyId, + ] + ); $one->save();// first transaction: from - $two = new Transaction(['account_id' => $secondAccount->id, 'transaction_journal_id' => $journal->id, 'amount' => $secondAmount]); + + $two = new Transaction( + [ + 'account_id' => $secondAccount->id, + 'transaction_journal_id' => $journal->id, + 'amount' => $secondAmount, + 'transaction_currency_id' => $currencyId,] + ); $two->save(); // second transaction: to Log::debug(sprintf('Stored two transactions, #%d and #%d', $one->id, $two->id)); @@ -623,18 +636,19 @@ class AccountRepository implements AccountRepositoryInterface // update date: $journal->date = $date; - // TODO update this transaction currency reference. $journal->transaction_currency_id = $currencyId; $journal->save(); // update transactions: /** @var Transaction $transaction */ foreach ($journal->transactions()->get() as $transaction) { if ($account->id == $transaction->account_id) { - $transaction->amount = $amount; + $transaction->amount = $amount; + $transaction->transaction_currency_id = $currencyId; $transaction->save(); } if ($account->id != $transaction->account_id) { - $transaction->amount = bcmul($amount, '-1'); + $transaction->amount = bcmul($amount, '-1'); + $transaction->transaction_currency_id = $currencyId; $transaction->save(); } } diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index bb0c8bcdd2..5958b0ff1c 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -101,10 +101,12 @@ class JournalTasker implements JournalTaskerInterface 'destination_accounts.encrypted as destination_account_encrypted', 'destination_account_types.type as destination_account_type', 'native_currencies.id as transaction_currency_id', + 'native_currencies.decimal_places as transaction_currency_dp', 'native_currencies.code as transaction_currency_code', 'native_currencies.symbol as transaction_currency_symbol', 'foreign_currencies.id as foreign_currency_id', + 'foreign_currencies.decimal_places as foreign_currency_dp', 'foreign_currencies.code as foreign_currency_code', 'foreign_currencies.symbol as foreign_currency_symbol', @@ -142,9 +144,11 @@ class JournalTasker implements JournalTaskerInterface 'transaction_currency_id' => $entry->transaction_currency_id, 'transaction_currency_code' => $entry->transaction_currency_code, 'transaction_currency_symbol' => $entry->transaction_currency_symbol, + 'transaction_currency_dp' => $entry->transaction_currency_dp, 'foreign_currency_id' => $entry->foreign_currency_id, 'foreign_currency_code' => $entry->foreign_currency_code, 'foreign_currency_symbol' => $entry->foreign_currency_symbol, + 'foreign_currency_dp' => $entry->foreign_currency_dp, ]; if ($entry->destination_account_type === AccountType::CASH) { $transaction['destination_account_name'] = ''; diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index e721c71b63..b5807a51f9 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -240,10 +240,12 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface */ public function getPiggyBanksWithAmount(): Collection { + $currency = Amount::getDefaultCurrency(); $set = $this->getPiggyBanks(); foreach ($set as $piggy) { $currentAmount = $piggy->currentRelevantRep()->currentamount ?? '0'; - $piggy->name = $piggy->name . ' (' . Amount::format($currentAmount, false) . ')'; + + $piggy->name = $piggy->name . ' (' . Amount::formatAnything($currency, $currentAmount, false) . ')'; } return $set; diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 7d11d68b7d..c75630d0ab 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -14,7 +14,7 @@ declare(strict_types=1); namespace FireflyIII\Support; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\Transaction; +use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -28,6 +28,7 @@ use Preferences as Prefs; */ class Amount { + /** * bool $sepBySpace is $localeconv['n_sep_by_space'] * int $signPosn = $localeconv['n_sign_posn'] @@ -242,4 +243,81 @@ class Amount 'zero' => $positive, ]; } + + /** + * @param TransactionJournal $journal + * @param bool $coloured + * + * @return string + */ + public function journalAmount(TransactionJournal $journal, bool $coloured = true): string + { + $amounts = []; + $transactions = $journal->transactions()->where('amount', '>', 0)->get(); + /** @var TransactionModel $transaction */ + foreach ($transactions as $transaction) { + // model some fields to fit "transactionAmount()": + $transaction->transaction_amount = $transaction->amount; + $transaction->transaction_foreign_amount = $transaction->foreign_amount; + $transaction->transaction_type_type = $journal->transactionType->type; + $transaction->transaction_currency_symbol = $transaction->transactionCurrency->symbol; + $transaction->transaction_currency_dp = $transaction->transactionCurrency->decimal_places; + if (!is_null($transaction->foreign_currency_id)) { + $transaction->foreign_currency_symbol = $transaction->foreignCurrency->symbol; + $transaction->foreign_currency_dp = $transaction->foreignCurrency->decimal_places; + } + + $amounts[] = $this->transactionAmount($transaction, $coloured); + } + + return join(' / ', $amounts); + + } + + /** + * This formats a transaction, IF that transaction has been "collected" using the JournalCollector. + * + * @param TransactionModel $transaction + * @param bool $coloured + * + * @return string + */ + public function transactionAmount(TransactionModel $transaction, bool $coloured = true): string + { + $amount = bcmul(app('steam')->positive(strval($transaction->transaction_amount)), '-1'); + $format = '%s'; + + if ($transaction->transaction_type_type === TransactionType::DEPOSIT) { + $amount = bcmul($amount, '-1'); + } + + if ($transaction->transaction_type_type === TransactionType::TRANSFER) { + $amount = app('steam')->positive($amount); + $coloured = false; + $format = '%s'; + } + + $currency = new TransactionCurrency; + $currency->symbol = $transaction->transaction_currency_symbol; + $currency->decimal_places = $transaction->transaction_currency_dp; + $str = sprintf($format, $this->formatAnything($currency, $amount, $coloured)); + + + if (!is_null($transaction->transaction_foreign_amount)) { + $amount = strval($transaction->transaction_foreign_amount); + + if ($transaction->transaction_type_type === TransactionType::TRANSFER) { + $amount = app('steam')->positive($amount); + $coloured = false; + $format = '%s'; + } + + $currency = new TransactionCurrency; + $currency->symbol = $transaction->foreign_currency_symbol; + $currency->decimal_places = $transaction->foreign_currency_dp; + $str .= ' (' . sprintf($format, $this->formatAnything($currency, $amount, $coloured)) . ')'; + } + + return $str; + } } diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index eb06a98ebf..cc4c45dc19 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -13,6 +13,7 @@ namespace FireflyIII\Support\Twig; use FireflyIII\Models\Account as AccountModel; +use FireflyIII\Models\Transaction as TransactionModel; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use Twig_Extension; @@ -45,6 +46,12 @@ class AmountFormat extends Twig_Extension { return [ $this->formatAmountByAccount(), + $this->transactionAmount(), + $this->journalAmount(), + $this->formatDestinationAfter(), + $this->formatDestinationBefore(), + $this->formatSourceAfter(), + $this->formatSourceBefore(), ]; } @@ -68,7 +75,9 @@ class AmountFormat extends Twig_Extension return new Twig_SimpleFilter( 'formatAmount', function (string $string): string { - return app('amount')->format($string); + $currency = app('amount')->getDefaultCurrency(); + + return app('amount')->formatAnything($currency, $string, true); }, ['is_safe' => ['html']] ); } @@ -102,8 +111,136 @@ class AmountFormat extends Twig_Extension return new Twig_SimpleFilter( 'formatAmountPlain', function (string $string): string { - return app('amount')->format($string, false); + $currency = app('amount')->getDefaultCurrency(); + + return app('amount')->formatAnything($currency, $string, false); }, ['is_safe' => ['html']] ); } + + /** + * @return Twig_SimpleFunction + */ + protected function formatDestinationAfter(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatDestinationAfter', function (array $transaction): string { + + // build fake currency for main amount. + $format = new TransactionCurrency; + $format->decimal_places = $transaction['transaction_currency_dp']; + $format->symbol = $transaction['transaction_currency_symbol']; + $string = app('amount')->formatAnything($format, $transaction['destination_account_after'], true); + + // also append foreign amount for clarity: + if (!is_null($transaction['foreign_destination_amount'])) { + // build fake currency for foreign amount + $format = new TransactionCurrency; + $format->decimal_places = $transaction['foreign_currency_dp']; + $format->symbol = $transaction['foreign_currency_symbol']; + $string .= ' (' . app('amount')->formatAnything($format, $transaction['foreign_destination_amount'], true) . ')'; + } + + + return $string; + + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function formatDestinationBefore(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatDestinationBefore', function (array $transaction): string { + + // build fake currency for main amount. + $format = new TransactionCurrency; + $format->decimal_places = $transaction['transaction_currency_dp']; + $format->symbol = $transaction['transaction_currency_symbol']; + + return app('amount')->formatAnything($format, $transaction['destination_account_before'], true); + + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function formatSourceAfter(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatSourceAfter', function (array $transaction): string { + + // build fake currency for main amount. + $format = new TransactionCurrency; + $format->decimal_places = $transaction['transaction_currency_dp']; + $format->symbol = $transaction['transaction_currency_symbol']; + $string = app('amount')->formatAnything($format, $transaction['source_account_after'], true); + + // also append foreign amount for clarity: + if (!is_null($transaction['foreign_source_amount'])) { + // build fake currency for foreign amount + $format = new TransactionCurrency; + $format->decimal_places = $transaction['foreign_currency_dp']; + $format->symbol = $transaction['foreign_currency_symbol']; + $string .= ' (' . app('amount')->formatAnything($format, $transaction['foreign_source_amount'], true) . ')'; + } + + + return $string; + + + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function formatSourceBefore(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'formatSourceBefore', function (array $transaction): string { + + // build fake currency for main amount. + $format = new TransactionCurrency; + $format->decimal_places = $transaction['transaction_currency_dp']; + $format->symbol = $transaction['transaction_currency_symbol']; + + return app('amount')->formatAnything($format, $transaction['source_account_before'], true); + + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function journalAmount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalAmount', function (TransactionJournal $journal): string { + + return app('amount')->journalAmount($journal, true); + }, ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + protected function transactionAmount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'transactionAmount', function (TransactionModel $transaction): string { + + return app('amount')->transactionAmount($transaction, true); + }, ['is_safe' => ['html']] + ); + } + } \ No newline at end of file diff --git a/resources/views/list/journals-tiny.twig b/resources/views/list/journals-tiny.twig index 63306fa102..ed6ee40b30 100644 --- a/resources/views/list/journals-tiny.twig +++ b/resources/views/list/journals-tiny.twig @@ -14,8 +14,7 @@ {{ transaction.description }} {% endif %} - {# TODO replace with new format code #} - XX.XX + {{ transactionAmount(transaction) }} {% endfor %} diff --git a/resources/views/list/journals.twig b/resources/views/list/journals.twig index acd95000bb..02fd791df8 100644 --- a/resources/views/list/journals.twig +++ b/resources/views/list/journals.twig @@ -62,14 +62,7 @@ - {% if transaction.transaction_type_type == 'Transfer' %} - - {# TODO format amount of transaction. #} - {# TODO format: Amount of transaction (amount in foreign) / total (total foreign) #} - XX.XX - {% else %} - XX.XX - {% endif %} + {{ transactionAmount(transaction) }} diff --git a/resources/views/popup/list/journals.twig b/resources/views/popup/list/journals.twig index 1177cce1f5..1ab88a9f32 100644 --- a/resources/views/popup/list/journals.twig +++ b/resources/views/popup/list/journals.twig @@ -46,7 +46,7 @@ {# TODO replace with new format code #} - XX.XX + {{ transactionAmount(transaction) }} {{ transaction.date.formatLocalized(monthAndDayFormat) }} diff --git a/resources/views/reports/partials/journals-audit.twig b/resources/views/reports/partials/journals-audit.twig index 48c5430658..4f0f794f96 100644 --- a/resources/views/reports/partials/journals-audit.twig +++ b/resources/views/reports/partials/journals-audit.twig @@ -59,8 +59,7 @@ {{ transaction.before|formatAmount }} - {# TODO replace with new format code #} - XX.XX + {{ transactionAmount(transaction) }} {{ transaction.after|formatAmount }} diff --git a/resources/views/transactions/mass-delete.twig b/resources/views/transactions/mass-delete.twig index 2049df92f8..864ea084ae 100644 --- a/resources/views/transactions/mass-delete.twig +++ b/resources/views/transactions/mass-delete.twig @@ -29,7 +29,7 @@   {{ trans('list.description') }} - {{ trans('list.amount') }} + {{ trans('list.total_amount') }} {{ trans('list.date') }} {{ trans('list.from') }} {{ trans('list.to') }} @@ -43,8 +43,7 @@ {{ journal.description }} - {# TODO fix amount display #} - XX.XX + {{ journalAmount(journal) }} {{ journal.date.formatLocalized(monthAndDayFormat) }} diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index b115ca16c8..a6a7fc8876 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -37,8 +37,7 @@ {{ 'total_amount'|_ }} - {# TODO fix amount display #} - XX.XX + {{ journalAmount(journal) }} @@ -299,8 +298,7 @@ - {# TODO replace with new display: #} - XX.XX + {{ formatSourceBefore(transaction) }} → {{ formatSourceAfter(transaction) }} {% if transaction.destination_account_type == 'Cash account' %} @@ -311,29 +309,10 @@ - {# TODO replace with new format code #} - XX.XX + {{ formatDestinationBefore(transaction) }} → {{ formatDestinationAfter(transaction) }} - {% if journal.transactiontype.type == 'Deposit' %} - - {# TODO replace with new format code #} - XX.XX - - {% endif %} - {% if journal.transactiontype.type == 'Withdrawal' %} - - {# TODO replace with new format code #} - XX.XX - - {% endif %} - {% if journal.transactiontype.type == 'Transfer' %} - - - {# TODO replace with new format code #} - XX.XX - - {% endif %} + {{ journalAmount(journal) }} {{ transactionIdBudgets(transaction.source_id) }} From 0b47e5d05d6f3cea944f9a5a97318e0903f632e4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 5 Jun 2017 07:03:20 +0200 Subject: [PATCH 073/103] Removed unnecessary variable. --- app/Console/Commands/UpgradeDatabase.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 2c3a20f3a0..7a185d004b 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -82,9 +82,8 @@ class UpgradeDatabase extends Command */ private function currencyInfoToTransactions() { - $count = 0; - $expanded = 0; - $set = TransactionJournal::with('transactions')->get(); + $count = 0; + $set = TransactionJournal::with('transactions')->get(); /** @var TransactionJournal $journal */ foreach ($set as $journal) { /** @var Transaction $transaction */ From f72f8b03dff74c03025a2a897b78a844073bf2a8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 5 Jun 2017 07:03:32 +0200 Subject: [PATCH 074/103] Catch empty currency preference --- app/Support/Twig/AmountFormat.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index cc4c45dc19..ba7011f902 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -92,13 +92,17 @@ class AmountFormat extends Twig_Extension return new Twig_SimpleFunction( 'formatAmountByAccount', function (AccountModel $account, string $amount, bool $coloured = true): string { $currencyId = intval($account->getMeta('currency_id')); - if ($currencyId === 0) { - // Format using default currency: - return app('amount')->format($amount, $coloured); + + if ($currencyId !== 0) { + $currency = TransactionCurrency::find($currencyId); + + return app('amount')->formatAnything($currency, $amount, $coloured); } - $currency = TransactionCurrency::find($currencyId); + $currency = app('amount')->getDefaultCurrency(); return app('amount')->formatAnything($currency, $amount, $coloured); + + }, ['is_safe' => ['html']] ); } From 1dec270907ced01323fb5556e18b5c7ac17601ac Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 5 Jun 2017 07:37:53 +0200 Subject: [PATCH 075/103] These changes fix the tests. --- app/Repositories/Journal/JournalTasker.php | 4 +- database/factories/ModelFactory.php | 44 +++++++----- .../Controllers/JsonControllerTest.php | 21 +++--- .../Transaction/SplitControllerTest.php | 72 +++++++++---------- 4 files changed, 75 insertions(+), 66 deletions(-) diff --git a/app/Repositories/Journal/JournalTasker.php b/app/Repositories/Journal/JournalTasker.php index 5958b0ff1c..5aacaa94ac 100644 --- a/app/Repositories/Journal/JournalTasker.php +++ b/app/Repositories/Journal/JournalTasker.php @@ -144,11 +144,11 @@ class JournalTasker implements JournalTaskerInterface 'transaction_currency_id' => $entry->transaction_currency_id, 'transaction_currency_code' => $entry->transaction_currency_code, 'transaction_currency_symbol' => $entry->transaction_currency_symbol, - 'transaction_currency_dp' => $entry->transaction_currency_dp, + 'transaction_currency_dp' => $entry->transaction_currency_dp, 'foreign_currency_id' => $entry->foreign_currency_id, 'foreign_currency_code' => $entry->foreign_currency_code, 'foreign_currency_symbol' => $entry->foreign_currency_symbol, - 'foreign_currency_dp' => $entry->foreign_currency_dp, + 'foreign_currency_dp' => $entry->foreign_currency_dp, ]; if ($entry->destination_account_type === AccountType::CASH) { $transaction['destination_account_name'] = ''; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index bd8d30b2a9..2727d4d86c 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -217,25 +217,31 @@ $factory->define( $factory->define( FireflyIII\Models\Transaction::class, function (Faker\Generator $faker) { return [ - 'transaction_amount' => strval($faker->randomFloat(2, -100, 100)), - 'destination_amount' => strval($faker->randomFloat(2, -100, 100)), - 'opposing_account_id' => $faker->numberBetween(1, 10), - 'source_account_id' => $faker->numberBetween(1, 10), - 'opposing_account_name' => $faker->words(3, true), - 'description' => $faker->words(3, true), - 'source_account_name' => $faker->words(3, true), - 'destination_account_id' => $faker->numberBetween(1, 10), - 'date' => new Carbon, - 'destination_account_name' => $faker->words(3, true), - 'amount' => strval($faker->randomFloat(2, -100, 100)), - 'budget_id' => 0, - 'category' => $faker->words(3, true), - 'transaction_journal_id' => $faker->numberBetween(1, 10), - 'journal_id' => $faker->numberBetween(1, 10), - 'transaction_currency_code' => 'EUR', - 'transaction_type_type' => 'Withdrawal', - 'account_encrypted' => 0, - 'account_name' => 'Some name', + 'transaction_amount' => strval($faker->randomFloat(2, -100, 100)), + 'destination_amount' => strval($faker->randomFloat(2, -100, 100)), + 'opposing_account_id' => $faker->numberBetween(1, 10), + 'source_account_id' => $faker->numberBetween(1, 10), + 'opposing_account_name' => $faker->words(3, true), + 'description' => $faker->words(3, true), + 'source_account_name' => $faker->words(3, true), + 'destination_account_id' => $faker->numberBetween(1, 10), + 'date' => new Carbon, + 'destination_account_name' => $faker->words(3, true), + 'amount' => strval($faker->randomFloat(2, -100, 100)), + 'budget_id' => 0, + 'category' => $faker->words(3, true), + 'transaction_journal_id' => $faker->numberBetween(1, 10), + 'journal_id' => $faker->numberBetween(1, 10), + 'transaction_currency_code' => 'EUR', + 'transaction_type_type' => 'Withdrawal', + 'account_encrypted' => 0, + 'account_name' => 'Some name', + 'transaction_currency_id' => 1, + 'transaction_currency_symbol' => '€', + 'foreign_destination_amount' => null, + 'foreign_currency_id' => null, + 'foreign_currency_code' => null, + 'foreign_currency_symbol' => null, ]; } ); \ No newline at end of file diff --git a/tests/Feature/Controllers/JsonControllerTest.php b/tests/Feature/Controllers/JsonControllerTest.php index 798575bbef..22c4152d77 100644 --- a/tests/Feature/Controllers/JsonControllerTest.php +++ b/tests/Feature/Controllers/JsonControllerTest.php @@ -21,7 +21,6 @@ use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Account\AccountTaskerInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -88,15 +87,17 @@ class JsonControllerTest extends TestCase public function testBoxBillsPaid() { // mock stuff + $billRepos = $this->mock(BillRepositoryInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); $billRepos->shouldReceive('getBillsPaidInRange')->andReturn('-100'); $this->be($this->user()); + $currency = Amount::getDefaultCurrency(); $response = $this->get(route('json.box.paid')); $response->assertStatus(200); - $response->assertExactJson(['amount' => Amount::format('100', false), 'amount_raw' => '100', 'box' => 'bills-paid']); + $response->assertExactJson(['amount' => Amount::formatAnything($currency, '100', false), 'amount_raw' => '100', 'box' => 'bills-paid']); } /** @@ -105,6 +106,7 @@ class JsonControllerTest extends TestCase public function testBoxBillsUnpaid() { // mock stuff + $billRepos = $this->mock(BillRepositoryInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); @@ -112,9 +114,10 @@ class JsonControllerTest extends TestCase $this->be($this->user()); + $currency = Amount::getDefaultCurrency(); $response = $this->get(route('json.box.unpaid')); $response->assertStatus(200); - $response->assertExactJson(['amount' => Amount::format('100', false), 'amount_raw' => '100', 'box' => 'bills-unpaid']); + $response->assertExactJson(['amount' => Amount::formatAnything($currency, '100', false), 'amount_raw' => '100', 'box' => 'bills-unpaid']); } /** @@ -123,8 +126,7 @@ class JsonControllerTest extends TestCase public function testBoxIn() { // mock stuff - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $tasker = $this->mock(AccountTaskerInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); @@ -139,9 +141,10 @@ class JsonControllerTest extends TestCase $this->be($this->user()); + $currency = Amount::getDefaultCurrency(); $response = $this->get(route('json.box.in')); $response->assertStatus(200); - $response->assertExactJson(['amount' => Amount::format('100', false), 'amount_raw' => '100', 'box' => 'in']); + $response->assertExactJson(['amount' => Amount::formatAnything($currency, '100', false), 'amount_raw' => '100', 'box' => 'in']); } /** @@ -150,8 +153,7 @@ class JsonControllerTest extends TestCase public function testBoxOut() { // mock stuff - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $tasker = $this->mock(AccountTaskerInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); @@ -165,9 +167,10 @@ class JsonControllerTest extends TestCase $collector->shouldReceive('withOpposingAccount')->andReturnSelf()->once(); $this->be($this->user()); + $currency = Amount::getDefaultCurrency(); $response = $this->get(route('json.box.out')); $response->assertStatus(200); - $response->assertExactJson(['amount' => Amount::format('100', false), 'amount_raw' => '100', 'box' => 'out']); + $response->assertExactJson(['amount' => Amount::formatAnything($currency, '100', false), 'amount_raw' => '100', 'box' => 'out']); } /** diff --git a/tests/Feature/Controllers/Transaction/SplitControllerTest.php b/tests/Feature/Controllers/Transaction/SplitControllerTest.php index 00c5a5b3c6..578802bc8c 100644 --- a/tests/Feature/Controllers/Transaction/SplitControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SplitControllerTest.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace Tests\Feature\Controllers\Transaction; @@ -32,37 +32,6 @@ use Tests\TestCase; */ class SplitControllerTest extends TestCase { - /** - * @covers \FireflyIII\Http\Controllers\Transaction\SplitController::edit - * @covers \FireflyIII\Http\Controllers\Transaction\SplitController::__construct - * @covers \FireflyIII\Http\Controllers\Transaction\SplitController::arrayFromJournal - * @covers \FireflyIII\Http\Controllers\Transaction\SplitController::getTransactionDataFromJournal - */ - public function testEditSingle() - { - - $currencyRepository = $this->mock(CurrencyRepositoryInterface::class); - $accountRepository = $this->mock(AccountRepositoryInterface::class); - $budgetRepository = $this->mock(BudgetRepositoryInterface::class); - $transactions = factory(Transaction::class, 1)->make(); - $tasker = $this->mock(JournalTaskerInterface::class); - - $currencyRepository->shouldReceive('get')->once()->andReturn(new Collection); - $accountRepository->shouldReceive('getAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]]) - ->andReturn(new Collection)->once(); - $budgetRepository->shouldReceive('getActiveBudgets')->andReturn(new Collection); - $tasker->shouldReceive('getTransactionsOverview')->andReturn($transactions->toArray()); - - - $deposit = TransactionJournal::where('transaction_type_id', 2)->where('user_id', $this->user()->id)->first(); - $this->be($this->user()); - $response = $this->get(route('transactions.split.edit', [$deposit->id])); - $response->assertStatus(200); - // has bread crumb - $response->assertSee('
diff --git a/resources/views/reports/partials/journals-audit.twig b/resources/views/reports/partials/journals-audit.twig index 4f0f794f96..d5da12c69f 100644 --- a/resources/views/reports/partials/journals-audit.twig +++ b/resources/views/reports/partials/journals-audit.twig @@ -57,11 +57,11 @@ {% endif %} - {{ transaction.before|formatAmount }} + {{ formatAmountByCurrency(transaction.currency, transaction.before) }} {{ transactionAmount(transaction) }} - {{ transaction.after|formatAmount }} + {{ formatAmountByCurrency(transaction.currency, transaction.after) }} {{ transaction.date.formatLocalized(monthAndDayFormat) }} From 0e929602a801af11e875a085a686a2e189b5ecee Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 07:23:54 +0200 Subject: [PATCH 084/103] Verify currency data routine. --- app/Console/Commands/UpgradeDatabase.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 7a185d004b..654267b9d0 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -74,6 +74,7 @@ class UpgradeDatabase extends Command $this->updateAccountCurrencies(); $this->updateJournalCurrencies(); $this->currencyInfoToTransactions(); + $this->verifyCurrencyInfo(); $this->info('Firefly III database is up to date.'); } @@ -386,4 +387,25 @@ class UpgradeDatabase extends Command } } } + + /** + * + */ + private function verifyCurrencyInfo() + { + $count = 0; + $transactions = Transaction::get(); + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $currencyId = intval($transaction->transaction_currency_id); + $foreignId = intval($transaction->foreign_currency_id); + if ($currencyId === $foreignId) { + $transaction->foreign_currency_id = null; + $transaction->foreign_amount = null; + $transaction->save(); + $count++; + } + } + $this->line(sprintf('Updated currency information for %d transactions', $count)); + } } From 65ccb2d443e116d9e72f6216f92a3ca3742eaea4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 19:29:10 +0200 Subject: [PATCH 085/103] Fix error display #662 --- public/js/ff/export/index.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/public/js/ff/export/index.js b/public/js/ff/export/index.js index c7d0f3fd40..c04d89f2cc 100644 --- a/public/js/ff/export/index.js +++ b/public/js/ff/export/index.js @@ -104,10 +104,22 @@ function callExport() { // show download showDownload(); - }).fail(function () { + }).fail(function (data) { // show error. // show form again. - showError('The export failed. Please check the log files to find out why.'); + + var errorText = 'The export failed. Please check the log files to find out why.'; + if (typeof data.responseJSON === 'object') { + errorText = ''; + for (var propt in data.responseJSON) { + if (data.responseJSON.hasOwnProperty(propt)) { + errorText += propt + ': ' + data.responseJSON[propt][0]; + } + } + } + + showError(errorText); + // stop polling: window.clearTimeout(intervalId); From a8ec4fe2fd8f4195e03d8cf5fa319e60e5e5cdcf Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 19:30:31 +0200 Subject: [PATCH 086/103] New interface for budget overview. --- app/Console/Commands/UpgradeDatabase.php | 2 +- app/Http/Controllers/BudgetController.php | 37 +++- resources/views/budgets/index.twig | 219 ++++++++++++++-------- routes/web.php | 2 +- 4 files changed, 172 insertions(+), 88 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 654267b9d0..107f61f604 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -389,7 +389,7 @@ class UpgradeDatabase extends Command } /** - * + * */ private function verifyCurrencyInfo() { diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 05188a146e..bad2b980dc 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers; use Amount; use Carbon\Carbon; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Requests\BudgetFormRequest; @@ -166,16 +167,36 @@ class BudgetController extends Controller } /** + * @param string|null $moment + * * @return View */ - public function index() + public function index(string $moment = null) { + $range = Preferences::get('viewRange', '1M')->data; + $start = session('start', new Carbon); + $end = session('end', new Carbon); + + // make date if present: + if (!is_null($moment) || strlen(strval($moment)) !== 0) { + try { + $start = new Carbon($moment); + $end = Navigation::endOfPeriod($start, $range); + } catch (Exception $e) { + + } + } + $next = clone $end; + $next->addDay(); + $prev = clone $start; + $prev->subDay(); + + $this->repository->cleanupBudgets(); + $budgets = $this->repository->getActiveBudgets(); $inactive = $this->repository->getInactiveBudgets(); - $start = session('start', new Carbon); - $end = session('end', new Carbon); $periodStart = $start->formatLocalized($this->monthAndDayFormat); $periodEnd = $end->formatLocalized($this->monthAndDayFormat); $budgetInformation = $this->collectBudgetInformation($budgets, $start, $end); @@ -184,9 +205,17 @@ class BudgetController extends Controller $spent = array_sum(array_column($budgetInformation, 'spent')); $budgeted = array_sum(array_column($budgetInformation, 'budgeted')); + // display info + $currentMonth = Navigation::periodShow($start, $range); + $nextText = Navigation::periodShow($next, $range); + $prevText = Navigation::periodShow($prev, $range); + return view( 'budgets.index', - compact('available', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted') + compact( + 'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', + 'spent', 'budgeted' + ) ); } diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 9ecec09211..8f2823cd0f 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -87,96 +87,151 @@ {% if budgets.count == 0 and inactive.count == 0 %} {% include 'partials.empty' with {what: 'default', type: 'budgets',route: route('budgets.create')} %} {% endif %} + + {# date thing #}
- {% for budget in budgets %} -
+
-

- - {% if budgetInformation[budget.id]['currentLimit'] %} - {{ budget.name }} - {% else %} - {{ budget.name }} - {% endif %} -

- - -
-
- - +

Period thing

+
+
+
+
+ +
+
+ +
+
+
-
- + + + + +
+
+
+
+

Budget stuff

+
+
+
+ + + + + + + + + + + + {% for budget in budgets %} - - - - + + + {# +
+
 Budget{{ 'budgeted'|_ }}SpentLeft
- {{ 'budgeted'|_ }} -
- {{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('end').formatLocalized(monthAndDayFormat) }}
-
-
-
-
{{ defaultCurrency.symbol|raw }}
- - {% if budgetInformation[budget.id]['currentLimit'] %} - {% set repAmount = budgetInformation[budget.id]['currentLimit'].amount %} - {% else %} - {% set repAmount = '0' %} - {% endif %} - -
+
+ +
- {{ 'spent'|_ }} -
- {{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('end').formatLocalized(monthAndDayFormat) }} -
+
+ {% if budgetInformation[budget.id]['currentLimit'] %} + {{ budget.name }} + {% else %} + {{ budget.name }} + {% endif %} + +
+
{{ defaultCurrency.symbol|raw }}
+ + {% if budgetInformation[budget.id]['currentLimit'] %} + {% set repAmount = budgetInformation[budget.id]['currentLimit'].amount %} + {% else %} + {% set repAmount = '0' %} + {% endif %} + +
{{ budgetInformation[budget.id]['spent']|formatAmount }} + {{ (repAmount - budgetInformation[budget.id]['spent'])|formatAmount }} +
+ + + + + + + + + {% if budgetInformation[budget.id]['otherLimits'].count > 0 %} + + + + {% endif %} +
+ + + +
+ +
+
+ {{ 'spent'|_ }} +
+ {{ session('start').formatLocalized(monthAndDayFormat) }} - + {{ session('end').formatLocalized(monthAndDayFormat) }} +
+
+ +
+
    + {% for other in budgetInformation[budget.id]['otherLimits'] %} +
  • + + Budgeted + {{ other.amount|formatAmountPlain }} + between + {{ other.start_date.formatLocalized(monthAndDayFormat) }} + and {{ other.end_date.formatLocalized(monthAndDayFormat) }}. +
  • + {% endfor %} +
+
+
+
+
+ #} - {% if budgetInformation[budget.id]['otherLimits'].count > 0 %} - - -
    - {% for other in budgetInformation[budget.id]['otherLimits'] %} -
  • - - Budgeted - {{ other.amount|formatAmountPlain }} - between - {{ other.start_date.formatLocalized(monthAndDayFormat) }} - and {{ other.end_date.formatLocalized(monthAndDayFormat) }}. -
  • - {% endfor %} -
- - - {% endif %} + {% endfor %} +
- {% if loop.index % 3 == 0 %} -
- {% endif %} - {% endfor %}
{% if inactive|length > 0 %} @@ -200,16 +255,16 @@
{% endif %} {% endblock %} -{% block scripts %} - + // budgeted data: + var budgeted = {{ budgeted }}; + var available = {{ available }}; + - -{% endblock %} + + {% endblock %} diff --git a/routes/web.php b/routes/web.php index 4b480c3b1c..d8e969e4ac 100755 --- a/routes/web.php +++ b/routes/web.php @@ -134,7 +134,7 @@ Route::group( */ Route::group( ['middleware' => 'user-full-auth', 'prefix' => 'budgets', 'as' => 'budgets.'], function () { - Route::get('', ['uses' => 'BudgetController@index', 'as' => 'index']); + Route::get('{moment?}', ['uses' => 'BudgetController@index', 'as' => 'index']); Route::get('income', ['uses' => 'BudgetController@updateIncome', 'as' => 'income']); Route::get('create', ['uses' => 'BudgetController@create', 'as' => 'create']); Route::get('edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'edit']); From 9d5d1c0a419630b903215c65baebb808329eef38 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:35:39 +0200 Subject: [PATCH 087/103] Updated budget view. --- app/Http/Controllers/BudgetController.php | 29 ++++++++++++++++++++++- public/js/ff/budgets/index.js | 11 ++++++++- resources/lang/en_US/firefly.php | 5 ++-- resources/views/budgets/index.twig | 23 +++++++++++------- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index bad2b980dc..565d9bfe8b 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -190,6 +190,7 @@ class BudgetController extends Controller $next->addDay(); $prev = clone $start; $prev->subDay(); + $prev = Navigation::startOfPeriod($prev, $range); $this->repository->cleanupBudgets(); @@ -205,6 +206,32 @@ class BudgetController extends Controller $spent = array_sum(array_column($budgetInformation, 'spent')); $budgeted = array_sum(array_column($budgetInformation, 'budgeted')); + // select thing for last 12 periods: + $previousLoop = []; + $previousDate = clone $start; + $count = 0; + while ($count < 12) { + $previousDate->subDay(); + $previousDate = Navigation::startOfPeriod($previousDate, $range); + $format = $previousDate->format('Y-m-d'); + $previousLoop[$format] = Navigation::periodShow($previousDate, $range); + $count++; + } + + // select thing for next 12 periods: + $nextLoop = []; + $nextDate = clone $end; + $nextDate->addDay(); + $count = 0; + + while ($count < 12) { + $format = $nextDate->format('Y-m-d'); + $nextLoop[$format] = Navigation::periodShow($nextDate, $range); + $nextDate = Navigation::endOfPeriod($nextDate, $range); + $count++; + $nextDate->addDay(); + } + // display info $currentMonth = Navigation::periodShow($start, $range); $nextText = Navigation::periodShow($next, $range); @@ -214,7 +241,7 @@ class BudgetController extends Controller 'budgets.index', compact( 'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', - 'spent', 'budgeted' + 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start' ) ); } diff --git a/public/js/ff/budgets/index.js b/public/js/ff/budgets/index.js index 936ae07b7f..ac233b85c4 100644 --- a/public/js/ff/budgets/index.js +++ b/public/js/ff/budgets/index.js @@ -8,7 +8,7 @@ * See the LICENSE file for details. */ -/** global: spent, budgeted, available, currencySymbol */ +/** global: spent, budgeted, available, currencySymbol, budgetIndexURI */ function drawSpentBar() { "use strict"; @@ -99,6 +99,15 @@ $(function () { */ $('input[type="number"]').on('input', updateBudgetedAmounts); + // + $('.selectPeriod').change(function (e) { + var sel = $(e.target).val(); + if (sel !== "x") { + var newURI = budgetIndexURI.replace("REPLACE", sel); + window.location.assign(newURI); + } + }); + }); function updateIncome() { diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 54f7cbc4ae..501c03430a 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Everything', 'customRange' => 'Custom range', 'apply' => 'Apply', + 'select_date' => 'Select date..', 'cancel' => 'Cancel', 'from' => 'From', 'to' => 'To', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 8f2823cd0f..7f1f5ffc4e 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -98,20 +98,26 @@
- + + {% for format, previousLabel in previousLoop %} + + {% endfor %}
- + + {% for format, nextLabel in nextLoop %} + + {% endfor %}
@@ -131,10 +137,10 @@   - Budget + {{ 'budget'|_ }} {{ 'budgeted'|_ }} - Spent - Left + {{ 'spent'|_ }} + {{ 'left'|_ }} @@ -264,6 +270,7 @@ // budgeted data: var budgeted = {{ budgeted }}; var available = {{ available }}; + var budgetIndexURI = "{{ route('budgets.index','REPLACE') }}"; From 51ddcd9ee1de78fdfe5ecc7b21e5b095095ec97e Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:37:24 +0200 Subject: [PATCH 088/103] Plus not minus [skip ci] --- resources/views/budgets/index.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 7f1f5ffc4e..240b7e9845 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -180,7 +180,7 @@ {{ budgetInformation[budget.id]['spent']|formatAmount }} - {{ (repAmount - budgetInformation[budget.id]['spent'])|formatAmount }} + {{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }} {#
From 2e26303b66d42ccd5b3e9214c36176d0a2240abe Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:40:11 +0200 Subject: [PATCH 089/103] New translations firefly.php (Spanish) --- resources/lang/es_ES/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 4d6a6fd94e..7c4e8c3fb3 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Todo', 'customRange' => 'Rango personalizado', 'apply' => 'Aplicar', + 'select_date' => 'Select date..', 'cancel' => 'Cancelar', 'from' => 'Desde', 'to' => 'Hasta', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', From 154b74ce6ff66c6ed265e983ab7f5692ff48f3b1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:40:26 +0200 Subject: [PATCH 090/103] New translations firefly.php (Portuguese, Brazilian) --- resources/lang/pt_BR/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index 904c5a3ceb..8e94668064 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Tudo', 'customRange' => 'Intervalo Personalizado', 'apply' => 'Aplicar', + 'select_date' => 'Select date..', 'cancel' => 'Cancelar', 'from' => 'De', 'to' => 'Até', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', From 5e430968c10d7398585847cfc2a434f79a03c5cc Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:40:34 +0200 Subject: [PATCH 091/103] New translations firefly.php (Dutch) --- resources/lang/nl_NL/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 186615f08a..b38bce4333 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Alles', 'customRange' => 'Zelf bereik kiezen', 'apply' => 'Go', + 'select_date' => 'Select date..', 'cancel' => 'Annuleren', 'from' => 'Van', 'to' => 'Tot', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'Alle transacties voor budget ":name" tussen :start en :end', 'chart_budget_in_period' => 'Grafiek voor alle transacties voor budget ":name" tussen :start en :end', 'chart_account_in_period' => 'Grafiek voor alle transacties voor rekening ":name" tussen :start en :end', - 'chart_category_in_period' => 'Grafiek voor alle transacties voor categorie ":name" tussen :start en :end', - 'chart_category_all' => 'Grafiek voor alle transacties voor categorie ":name"', + 'chart_category_in_period' => 'Grafiek voor alle transacties voor categorie ":name" tussen :start en :end', + 'chart_category_all' => 'Grafiek voor alle transacties voor categorie ":name"', 'budget_in_period_breadcrumb' => 'Tussen :start en :end', 'clone_withdrawal' => 'Kopieer deze uitgave', 'clone_deposit' => 'Kopieer deze inkomsten', From 39288cfb0bd3260723cddea89900fb86a4f93b04 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:40:41 +0200 Subject: [PATCH 092/103] New translations firefly.php (Slovenian) --- resources/lang/sl_SI/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/sl_SI/firefly.php b/resources/lang/sl_SI/firefly.php index c5edb42c52..98280357e1 100644 --- a/resources/lang/sl_SI/firefly.php +++ b/resources/lang/sl_SI/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'vse', 'customRange' => 'obseg po meri', 'apply' => 'uporabi', + 'select_date' => 'Select date..', 'cancel' => 'prekliči', 'from' => 'From', 'to' => 'To', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', From aeadfbdd6aed53a449eaaa182a5e25611dc505a8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:40:50 +0200 Subject: [PATCH 093/103] New translations firefly.php (Polish) --- resources/lang/pl_PL/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index e320c548e4..2a6258b8cf 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Wszystko', 'customRange' => 'Niestandardowy zakres', 'apply' => 'Zastosuj', + 'select_date' => 'Select date..', 'cancel' => 'Anuluj', 'from' => 'Z', 'to' => 'Do', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Między :start i :end', 'clone_withdrawal' => 'Zduplikuj tę wypłatę', 'clone_deposit' => 'Zduplikuj tą wpłatę', From 234e656ff6afed25d9c07f94c433683809f6cefe Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:40:59 +0200 Subject: [PATCH 094/103] New translations firefly.php (Chinese Traditional) --- resources/lang/zh_TW/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/zh_TW/firefly.php b/resources/lang/zh_TW/firefly.php index 63f0e11527..fce1d22576 100644 --- a/resources/lang/zh_TW/firefly.php +++ b/resources/lang/zh_TW/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => '顯示所有', 'customRange' => '自訂範圍', 'apply' => '套用', + 'select_date' => 'Select date..', 'cancel' => '取消', 'from' => '從', 'to' => '到', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', From 5069367873d3190618a4f60b00bcaeea27891fd6 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:41:16 +0200 Subject: [PATCH 095/103] New translations firefly.php (German) --- resources/lang/de_DE/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index 34873bd40c..da3d194e10 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Alle', 'customRange' => 'Individueller Bereich', 'apply' => 'Übernehmen', + 'select_date' => 'Select date..', 'cancel' => 'Abbrechen', 'from' => 'Von', 'to' => 'Bis', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Zwischen :start und :end', 'clone_withdrawal' => 'Diese Ausgabe klonen', 'clone_deposit' => 'Diese Einnahme klonen', From 28cf123da378ffd459a12d63c3cc72115e4e4aca Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 6 Jun 2017 20:41:24 +0200 Subject: [PATCH 096/103] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 00cc9d9ad1..27cc32b44e 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -20,6 +20,7 @@ return [ 'everything' => 'Tout', 'customRange' => 'Etendue personnalisée', 'apply' => 'Appliquer', + 'select_date' => 'Select date..', 'cancel' => 'Annuler', 'from' => 'Depuis', 'to' => 'A', @@ -112,8 +113,8 @@ return [ 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', + 'chart_category_all' => 'Chart for all transactions for category ":name"', 'budget_in_period_breadcrumb' => 'Between :start and :end', 'clone_withdrawal' => 'Clone this withdrawal', 'clone_deposit' => 'Clone this deposit', From e5db5a7b5ce16fa14b61d9ba61f6a37a23ea4aad Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 7 Jun 2017 07:38:58 +0200 Subject: [PATCH 097/103] Various code clean up. --- app/Http/Controllers/BudgetController.php | 1 + app/Http/Controllers/Chart/AccountController.php | 2 +- .../Transaction/ConvertController.php | 16 ++++++++++------ app/Support/Search/Search.php | 2 +- app/Support/Steam.php | 8 ++------ public/js/ff/budgets/show.js | 2 +- public/js/ff/transactions/single/edit.js | 2 -- test.sh | 4 ++++ 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 565d9bfe8b..1e4545d518 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -183,6 +183,7 @@ class BudgetController extends Controller $start = new Carbon($moment); $end = Navigation::endOfPeriod($start, $range); } catch (Exception $e) { + // start and end are already defined. } } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 81fcf35c81..c0af003e81 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -349,7 +349,7 @@ class AccountController extends Controller $cache->addProperty('chart.account.period'); $cache->addProperty($account->id); if ($cache->has()) { - //return Response::json($cache->get()); // @codeCoverageIgnore + return Response::json($cache->get()); // @codeCoverageIgnore } $format = (string)trans('config.month_and_day'); diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index 8853a3dbaa..2b1e8bf061 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -174,14 +174,17 @@ class ConvertController extends Controller switch ($joined) { default: throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore - case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: // one + case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: + // one $destination = $sourceAccount; break; - case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: // two + case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: + // two $destination = $accountRepository->find(intval($data['destination_account_asset'])); break; - case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: // three - case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: // five + case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: + case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: + // three and five if ($data['destination_account_expense'] === '') { // destination is a cash account. $destination = $accountRepository->getCashAccount(); @@ -197,8 +200,9 @@ class ConvertController extends Controller ]; $destination = $accountRepository->store($data); break; - case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: // four - case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: // six + case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: + case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: + // four and six $destination = $destinationAccount; break; } diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index 4b9ffacb31..ebceaf59f9 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -53,7 +53,7 @@ class Search implements SearchInterface public function __construct() { $this->modifiers = new Collection; - $this->validModifiers = config('firefly.search_modifiers'); + $this->validModifiers = (array) config('firefly.search_modifiers'); } /** diff --git a/app/Support/Steam.php b/app/Support/Steam.php index e97b0f2c03..398400e05b 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -135,7 +135,7 @@ class Steam $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - //return $cache->get(); // @codeCoverageIgnore + return $cache->get(); // @codeCoverageIgnore } $start->subDay(); @@ -167,10 +167,6 @@ class Steam ] ); -// echo '
';
-//        var_dump($set->toArray());
-//        exit;
-
         $currentBalance = $startBalance;
         /** @var Transaction $entry */
         foreach ($set as $entry) {
@@ -217,7 +213,7 @@ class Steam
         $cache->addProperty('balances');
         $cache->addProperty($date);
         if ($cache->has()) {
-            //return $cache->get(); // @codeCoverageIgnore
+            return $cache->get(); // @codeCoverageIgnore
         }
 
         // need to do this per account.
diff --git a/public/js/ff/budgets/show.js b/public/js/ff/budgets/show.js
index 1852c5b6cd..01dda2660e 100644
--- a/public/js/ff/budgets/show.js
+++ b/public/js/ff/budgets/show.js
@@ -8,7 +8,7 @@
  * See the LICENSE file for details.
  */
 
-/** global: budgetChartUri, expenseCategoryUri, expenseAssetUri, expenseExpenseUri */
+/** global: budgetChartUri, expenseCategoryUri, expenseAssetUri, expenseExpenseUri, budgetLimitID */
 
 $(function () {
     "use strict";
diff --git a/public/js/ff/transactions/single/edit.js b/public/js/ff/transactions/single/edit.js
index 05c2132260..7d9308e1cb 100644
--- a/public/js/ff/transactions/single/edit.js
+++ b/public/js/ff/transactions/single/edit.js
@@ -32,8 +32,6 @@ $(document).ready(function () {
  */
 function updateInitialPage() {
 
-    console.log('Native currency is #' + journalData.native_currency.id + ' and (foreign) currency id is #' + journalData.currency.id);
-
     if (journal.transaction_type.type === "Transfer") {
         $('#native_amount_holder').hide();
         $('#amount_holder').hide();
diff --git a/test.sh b/test.sh
index 177dc25cb9..90e472cd80 100755
--- a/test.sh
+++ b/test.sh
@@ -87,6 +87,10 @@ then
 
     # call test data generation script
     $(which php) /sites/FF3/test-data/artisan generate:data local sqlite
+
+    # also run upgrade routine:
+    $(which php) /sites/FF3/firefly-iii/artisan firefly:upgrade-database
+
     # copy new database over backup (resets backup)
     cp $DATABASE $DATABASECOPY
 fi

From 92c5cabd7090162c8b9d66c2b02c26f14154bafd Mon Sep 17 00:00:00 2001
From: James Cole 
Date: Wed, 7 Jun 2017 08:18:42 +0200
Subject: [PATCH 098/103] Try to untangle complex repositories

---
 .../Transaction/MassController.php            |   5 +-
 .../Transaction/SingleController.php          |  11 +-
 .../Transaction/SplitController.php           |  12 +-
 app/Providers/JournalServiceProvider.php      |  22 --
 .../Account/AccountRepository.php             | 189 +-----------
 .../Account/FindAccountsTrait.php             | 212 +++++++++++++
 .../Journal/CreateJournalsTrait.php           | 186 +++++++++++
 .../Journal/JournalRepository.php             | 126 ++++++--
 .../Journal/JournalRepositoryInterface.php    |  16 +
 app/Repositories/Journal/JournalUpdate.php    | 290 ------------------
 .../Journal/JournalUpdateInterface.php        |  44 ---
 ...alSupport.php => SupportJournalsTrait.php} | 137 +--------
 .../Journal/UpdateJournalsTrait.php           | 156 ++++++++++
 13 files changed, 699 insertions(+), 707 deletions(-)
 create mode 100644 app/Repositories/Account/FindAccountsTrait.php
 create mode 100644 app/Repositories/Journal/CreateJournalsTrait.php
 delete mode 100644 app/Repositories/Journal/JournalUpdate.php
 delete mode 100644 app/Repositories/Journal/JournalUpdateInterface.php
 rename app/Repositories/Journal/{JournalSupport.php => SupportJournalsTrait.php} (65%)
 create mode 100644 app/Repositories/Journal/UpdateJournalsTrait.php

diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php
index 61d2a0fa43..8b3abddfce 100644
--- a/app/Http/Controllers/Transaction/MassController.php
+++ b/app/Http/Controllers/Transaction/MassController.php
@@ -23,7 +23,6 @@ use FireflyIII\Models\TransactionType;
 use FireflyIII\Repositories\Account\AccountRepositoryInterface;
 use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
 use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
-use FireflyIII\Repositories\Journal\JournalUpdateInterface;
 use Illuminate\Support\Collection;
 use Preferences;
 use Session;
@@ -204,7 +203,7 @@ class MassController extends Controller
      *
      * @return mixed
      */
-    public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository, JournalUpdateInterface $updater)
+    public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository)
     {
         $journalIds = $request->get('journals');
         $count      = 0;
@@ -250,7 +249,7 @@ class MassController extends Controller
                         'tags'                     => $tags,
                     ];
                     // call repository update function.
-                    $updater->update($journal, $data);
+                    $repository->update($journal, $data);
 
                     $count++;
                 }
diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php
index 67e2c7cb19..75bb6a3c8b 100644
--- a/app/Http/Controllers/Transaction/SingleController.php
+++ b/app/Http/Controllers/Transaction/SingleController.php
@@ -27,7 +27,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
 use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
 use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
 use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
-use FireflyIII\Repositories\Journal\JournalUpdateInterface;
 use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
 use Log;
 use Preferences;
@@ -365,13 +364,13 @@ class SingleController extends Controller
     }
 
     /**
-     * @param JournalFormRequest     $request
-     * @param JournalUpdateInterface $updater
-     * @param TransactionJournal     $journal
+     * @param JournalFormRequest         $request
+     * @param JournalRepositoryInterface $repository
+     * @param TransactionJournal         $journal
      *
      * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      */
-    public function update(JournalFormRequest $request, JournalUpdateInterface $updater, TransactionJournal $journal)
+    public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
     {
         // @codeCoverageIgnoreStart
         if ($this->isOpeningBalance($journal)) {
@@ -380,7 +379,7 @@ class SingleController extends Controller
         // @codeCoverageIgnoreEnd
 
         $data    = $request->getJournalData();
-        $journal = $updater->update($journal, $data);
+        $journal = $repository->update($journal, $data);
         /** @var array $files */
         $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
         $this->attachments->saveAttachmentsForModel($journal, $files);
diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php
index fa899d6bff..e6668bc43e 100644
--- a/app/Http/Controllers/Transaction/SplitController.php
+++ b/app/Http/Controllers/Transaction/SplitController.php
@@ -23,8 +23,8 @@ use FireflyIII\Models\TransactionJournal;
 use FireflyIII\Repositories\Account\AccountRepositoryInterface;
 use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
 use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
 use FireflyIII\Repositories\Journal\JournalTaskerInterface;
-use FireflyIII\Repositories\Journal\JournalUpdateInterface;
 use Illuminate\Http\Request;
 use Log;
 use Preferences;
@@ -122,20 +122,20 @@ class SplitController extends Controller
 
 
     /**
-     * @param Request                $request
-     * @param JournalUpdateInterface $updater
-     * @param TransactionJournal     $journal
+     * @param Request                    $request
+     * @param JournalRepositoryInterface $repository
+     * @param TransactionJournal         $journal
      *
      * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      */
-    public function update(Request $request, JournalUpdateInterface $updater, TransactionJournal $journal)
+    public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
     {
         if ($this->isOpeningBalance($journal)) {
             return $this->redirectToAccount($journal);
         }
 
         $data    = $this->arrayFromInput($request);
-        $journal = $updater->updateSplitJournal($journal, $data);
+        $journal = $repository->updateSplitJournal($journal, $data);
         /** @var array $files */
         $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
         // save attachments:
diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php
index 7fcabbe606..d221714c21 100644
--- a/app/Providers/JournalServiceProvider.php
+++ b/app/Providers/JournalServiceProvider.php
@@ -20,8 +20,6 @@ use FireflyIII\Repositories\Journal\JournalRepository;
 use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
 use FireflyIII\Repositories\Journal\JournalTasker;
 use FireflyIII\Repositories\Journal\JournalTaskerInterface;
-use FireflyIII\Repositories\Journal\JournalUpdate;
-use FireflyIII\Repositories\Journal\JournalUpdateInterface;
 use Illuminate\Foundation\Application;
 use Illuminate\Support\ServiceProvider;
 
@@ -52,7 +50,6 @@ class JournalServiceProvider extends ServiceProvider
         $this->registerRepository();
         $this->registerTasker();
         $this->registerCollector();
-        $this->registerUpdater();
     }
 
     /**
@@ -115,23 +112,4 @@ class JournalServiceProvider extends ServiceProvider
         );
     }
 
-    /**
-     *
-     */
-    private function registerUpdater()
-    {
-        $this->app->bind(
-            JournalUpdateInterface::class,
-            function (Application $app) {
-                /** @var JournalUpdateInterface $tasker */
-                $update = app(JournalUpdate::class);
-
-                if ($app->auth->check()) {
-                    $update->setUser(auth()->user());
-                }
-
-                return $update;
-            }
-        );
-    }
 }
diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php
index bb9ace5034..c7d6422a50 100644
--- a/app/Repositories/Account/AccountRepository.php
+++ b/app/Repositories/Account/AccountRepository.php
@@ -24,8 +24,6 @@ use FireflyIII\Models\Transaction;
 use FireflyIII\Models\TransactionJournal;
 use FireflyIII\Models\TransactionType;
 use FireflyIII\User;
-use Illuminate\Database\Eloquent\Relations\HasMany;
-use Illuminate\Support\Collection;
 use Log;
 
 
@@ -37,6 +35,7 @@ use Log;
  */
 class AccountRepository implements AccountRepositoryInterface
 {
+    use FindAccountsTrait;
 
     /** @var User */
     private $user;
@@ -77,192 +76,6 @@ class AccountRepository implements AccountRepositoryInterface
         return true;
     }
 
-    /**
-     * @param $accountId
-     *
-     * @return Account
-     */
-    public function find(int $accountId): Account
-    {
-        $account = $this->user->accounts()->find($accountId);
-        if (is_null($account)) {
-            return new Account;
-        }
-
-        return $account;
-    }
-
-    /**
-     * @param string $number
-     * @param array  $types
-     *
-     * @return Account
-     */
-    public function findByAccountNumber(string $number, array $types): Account
-    {
-        $query = $this->user->accounts()
-                            ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
-                            ->where('account_meta.name', 'accountNumber')
-                            ->where('account_meta.data', json_encode($number));
-
-        if (count($types) > 0) {
-            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
-            $query->whereIn('account_types.type', $types);
-        }
-
-        /** @var Collection $accounts */
-        $accounts = $query->get(['accounts.*']);
-        if ($accounts->count() > 0) {
-            return $accounts->first();
-        }
-
-        return new Account;
-    }
-
-    /**
-     * @param string $iban
-     * @param array  $types
-     *
-     * @return Account
-     */
-    public function findByIban(string $iban, array $types): Account
-    {
-        $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
-
-        if (count($types) > 0) {
-            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
-            $query->whereIn('account_types.type', $types);
-        }
-
-        $accounts = $query->get(['accounts.*']);
-        /** @var Account $account */
-        foreach ($accounts as $account) {
-            if ($account->iban === $iban) {
-                return $account;
-            }
-        }
-
-        return new Account;
-    }
-
-    /**
-     * @param string $name
-     * @param array  $types
-     *
-     * @return Account
-     */
-    public function findByName(string $name, array $types): Account
-    {
-        $query = $this->user->accounts();
-
-        if (count($types) > 0) {
-            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
-            $query->whereIn('account_types.type', $types);
-
-        }
-        Log::debug(sprintf('Searching for account named %s of the following type(s)', $name), ['types' => $types]);
-
-        $accounts = $query->get(['accounts.*']);
-        /** @var Account $account */
-        foreach ($accounts as $account) {
-            if ($account->name === $name) {
-                Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
-
-                return $account;
-            }
-        }
-        Log::debug('Found nothing.');
-
-        return new Account;
-    }
-
-    /**
-     * @param array $accountIds
-     *
-     * @return Collection
-     */
-    public function getAccountsById(array $accountIds): Collection
-    {
-        /** @var Collection $result */
-        $query = $this->user->accounts();
-
-        if (count($accountIds) > 0) {
-            $query->whereIn('accounts.id', $accountIds);
-        }
-
-        $result = $query->get(['accounts.*']);
-        $result = $result->sortBy(
-            function (Account $account) {
-                return strtolower($account->name);
-            }
-        );
-
-        return $result;
-    }
-
-    /**
-     * @param array $types
-     *
-     * @return Collection
-     */
-    public function getAccountsByType(array $types): Collection
-    {
-        /** @var Collection $result */
-        $query = $this->user->accounts();
-        if (count($types) > 0) {
-            $query->accountTypeIn($types);
-        }
-
-        $result = $query->get(['accounts.*']);
-        $result = $result->sortBy(
-            function (Account $account) {
-                return strtolower($account->name);
-            }
-        );
-
-        return $result;
-    }
-
-    /**
-     * @param array $types
-     *
-     * @return Collection
-     */
-    public function getActiveAccountsByType(array $types): Collection
-    {
-        /** @var Collection $result */
-        $query = $this->user->accounts()->with(
-            ['accountmeta' => function (HasMany $query) {
-                $query->where('name', 'accountRole');
-            }]
-        );
-        if (count($types) > 0) {
-            $query->accountTypeIn($types);
-        }
-        $query->where('active', 1);
-        $result = $query->get(['accounts.*']);
-        $result = $result->sortBy(
-            function (Account $account) {
-                return strtolower($account->name);
-            }
-        );
-
-        return $result;
-    }
-
-    /**
-     * @return Account
-     */
-    public function getCashAccount(): Account
-    {
-        $type    = AccountType::where('type', AccountType::CASH)->first();
-        $account = Account::firstOrCreateEncrypted(
-            ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1]
-        );
-
-        return $account;
-    }
-
     /**
      * Returns the date of the very last transaction in this account.
      *
diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php
new file mode 100644
index 0000000000..062c7c1b8f
--- /dev/null
+++ b/app/Repositories/Account/FindAccountsTrait.php
@@ -0,0 +1,212 @@
+user->accounts()->find($accountId);
+        if (is_null($account)) {
+            return new Account;
+        }
+
+        return $account;
+    }
+
+    /**
+     * @param string $number
+     * @param array  $types
+     *
+     * @return Account
+     */
+    public function findByAccountNumber(string $number, array $types): Account
+    {
+        $query = $this->user->accounts()
+                            ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
+                            ->where('account_meta.name', 'accountNumber')
+                            ->where('account_meta.data', json_encode($number));
+
+        if (count($types) > 0) {
+            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
+            $query->whereIn('account_types.type', $types);
+        }
+
+        /** @var Collection $accounts */
+        $accounts = $query->get(['accounts.*']);
+        if ($accounts->count() > 0) {
+            return $accounts->first();
+        }
+
+        return new Account;
+    }
+
+    /**
+     * @param string $iban
+     * @param array  $types
+     *
+     * @return Account
+     */
+    public function findByIban(string $iban, array $types): Account
+    {
+        $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
+
+        if (count($types) > 0) {
+            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
+            $query->whereIn('account_types.type', $types);
+        }
+
+        $accounts = $query->get(['accounts.*']);
+        /** @var Account $account */
+        foreach ($accounts as $account) {
+            if ($account->iban === $iban) {
+                return $account;
+            }
+        }
+
+        return new Account;
+    }
+
+    /**
+     * @param string $name
+     * @param array  $types
+     *
+     * @return Account
+     */
+    public function findByName(string $name, array $types): Account
+    {
+        $query = $this->user->accounts();
+
+        if (count($types) > 0) {
+            $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
+            $query->whereIn('account_types.type', $types);
+
+        }
+        Log::debug(sprintf('Searching for account named %s of the following type(s)', $name), ['types' => $types]);
+
+        $accounts = $query->get(['accounts.*']);
+        /** @var Account $account */
+        foreach ($accounts as $account) {
+            if ($account->name === $name) {
+                Log::debug(sprintf('Found #%d (%s) with type id %d', $account->id, $account->name, $account->account_type_id));
+
+                return $account;
+            }
+        }
+        Log::debug('Found nothing.');
+
+        return new Account;
+    }
+
+    /**
+     * @param array $accountIds
+     *
+     * @return Collection
+     */
+    public function getAccountsById(array $accountIds): Collection
+    {
+        /** @var Collection $result */
+        $query = $this->user->accounts();
+
+        if (count($accountIds) > 0) {
+            $query->whereIn('accounts.id', $accountIds);
+        }
+
+        $result = $query->get(['accounts.*']);
+        $result = $result->sortBy(
+            function (Account $account) {
+                return strtolower($account->name);
+            }
+        );
+
+        return $result;
+    }
+
+    /**
+     * @param array $types
+     *
+     * @return Collection
+     */
+    public function getAccountsByType(array $types): Collection
+    {
+        /** @var Collection $result */
+        $query = $this->user->accounts();
+        if (count($types) > 0) {
+            $query->accountTypeIn($types);
+        }
+
+        $result = $query->get(['accounts.*']);
+        $result = $result->sortBy(
+            function (Account $account) {
+                return strtolower($account->name);
+            }
+        );
+
+        return $result;
+    }
+
+    /**
+     * @param array $types
+     *
+     * @return Collection
+     */
+    public function getActiveAccountsByType(array $types): Collection
+    {
+        /** @var Collection $result */
+        $query = $this->user->accounts()->with(
+            ['accountmeta' => function (HasMany $query) {
+                $query->where('name', 'accountRole');
+            }]
+        );
+        if (count($types) > 0) {
+            $query->accountTypeIn($types);
+        }
+        $query->where('active', 1);
+        $result = $query->get(['accounts.*']);
+        $result = $result->sortBy(
+            function (Account $account) {
+                return strtolower($account->name);
+            }
+        );
+
+        return $result;
+    }
+
+    /**
+     * @return Account
+     */
+    public function getCashAccount(): Account
+    {
+        $type    = AccountType::where('type', AccountType::CASH)->first();
+        $account = Account::firstOrCreateEncrypted(
+            ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1]
+        );
+
+        return $account;
+    }
+}
\ No newline at end of file
diff --git a/app/Repositories/Journal/CreateJournalsTrait.php b/app/Repositories/Journal/CreateJournalsTrait.php
new file mode 100644
index 0000000000..dbfa818e84
--- /dev/null
+++ b/app/Repositories/Journal/CreateJournalsTrait.php
@@ -0,0 +1,186 @@
+ 0) {
+                $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
+                if (!is_null($tag)) {
+                    Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
+                    $tagRepository->connect($journal, $tag);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * @param Transaction $transaction
+     * @param int         $budgetId
+     */
+    protected function storeBudgetWithTransaction(Transaction $transaction, int $budgetId)
+    {
+        if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) {
+            /** @var \FireflyIII\Models\Budget $budget */
+            $budget = Budget::find($budgetId);
+            $transaction->budgets()->save($budget);
+        }
+    }
+
+    /**
+     * @param Transaction $transaction
+     * @param string      $category
+     */
+    protected function storeCategoryWithTransaction(Transaction $transaction, string $category)
+    {
+        if (strlen($category) > 0) {
+            $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]);
+            $transaction->categories()->save($category);
+        }
+    }
+
+    /**
+     * The reference to storeAccounts() in this function is an indication of spagetti code but alas,
+     * I leave it as it is.
+     *
+     * @param TransactionJournal $journal
+     * @param array              $transaction
+     * @param int                $identifier
+     *
+     * @return Collection
+     */
+    protected function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
+    {
+        // store source and destination accounts (depends on type)
+        $accounts = $this->storeAccounts($this->user, $journal->transactionType, $transaction);
+
+        // store transaction one way:
+        $amount        = bcmul(strval($transaction['amount']), '-1');
+        $foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1');
+        $one           = $this->storeTransaction(
+            [
+                'journal'                 => $journal,
+                'account'                 => $accounts['source'],
+                'amount'                  => $amount,
+                'transaction_currency_id' => $transaction['transaction_currency_id'],
+                'foreign_amount'          => $foreignAmount,
+                'foreign_currency_id'     => $transaction['foreign_currency_id'],
+                'description'             => $transaction['description'],
+                'category'                => null,
+                'budget'                  => null,
+                'identifier'              => $identifier,
+            ]
+        );
+        $this->storeCategoryWithTransaction($one, $transaction['category']);
+        $this->storeBudgetWithTransaction($one, $transaction['budget_id']);
+
+        // and the other way:
+        $amount        = strval($transaction['amount']);
+        $foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']);
+        $two           = $this->storeTransaction(
+            [
+                'journal'                 => $journal,
+                'account'                 => $accounts['destination'],
+                'amount'                  => $amount,
+                'transaction_currency_id' => $transaction['transaction_currency_id'],
+                'foreign_amount'          => $foreignAmount,
+                'foreign_currency_id'     => $transaction['foreign_currency_id'],
+                'description'             => $transaction['description'],
+                'category'                => null,
+                'budget'                  => null,
+                'identifier'              => $identifier,
+            ]
+        );
+        $this->storeCategoryWithTransaction($two, $transaction['category']);
+        $this->storeBudgetWithTransaction($two, $transaction['budget_id']);
+
+        return new Collection([$one, $two]);
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return Transaction
+     */
+    protected function storeTransaction(array $data): Transaction
+    {
+        $fields = [
+            'transaction_journal_id'  => $data['journal']->id,
+            'account_id'              => $data['account']->id,
+            'amount'                  => $data['amount'],
+            'foreign_amount'          => $data['foreign_amount'],
+            'transaction_currency_id' => $data['transaction_currency_id'],
+            'foreign_currency_id'     => $data['foreign_currency_id'],
+            'description'             => $data['description'],
+            'identifier'              => $data['identifier'],
+        ];
+
+
+        if (is_null($data['foreign_currency_id'])) {
+            unset($fields['foreign_currency_id']);
+        }
+        if (is_null($data['foreign_amount'])) {
+            unset($fields['foreign_amount']);
+        }
+
+        /** @var Transaction $transaction */
+        $transaction = Transaction::create($fields);
+
+        Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id));
+
+        if (!is_null($data['category'])) {
+            $transaction->categories()->save($data['category']);
+        }
+
+        if (!is_null($data['budget'])) {
+            $transaction->categories()->save($data['budget']);
+        }
+
+        return $transaction;
+
+    }
+
+}
\ No newline at end of file
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index d8402430a1..774ae77947 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -14,10 +14,8 @@ declare(strict_types=1);
 namespace FireflyIII\Repositories\Journal;
 
 use FireflyIII\Models\Account;
-use FireflyIII\Models\Tag;
 use FireflyIII\Models\TransactionJournal;
 use FireflyIII\Models\TransactionType;
-use FireflyIII\Repositories\Tag\TagRepositoryInterface;
 use FireflyIII\User;
 use Illuminate\Support\Collection;
 use Illuminate\Support\MessageBag;
@@ -31,6 +29,8 @@ use Preferences;
  */
 class JournalRepository implements JournalRepositoryInterface
 {
+    use CreateJournalsTrait, UpdateJournalsTrait, SupportJournalsTrait;
+
     /** @var User */
     private $user;
     /** @var array */
@@ -170,8 +170,8 @@ class JournalRepository implements JournalRepositoryInterface
         // find transaction type.
         /** @var TransactionType $transactionType */
         $transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
-        $accounts        = JournalSupport::storeAccounts($this->user, $transactionType, $data);
-        $data            = JournalSupport::verifyNativeAmount($data, $accounts);
+        $accounts        = $this->storeAccounts($this->user, $transactionType, $data);
+        $data            = $this->verifyNativeAmount($data, $accounts);
         $amount          = strval($data['amount']);
         $journal         = new TransactionJournal(
             [
@@ -186,8 +186,8 @@ class JournalRepository implements JournalRepositoryInterface
         $journal->save();
 
         // store stuff:
-        JournalSupport::storeCategoryWithJournal($journal, $data['category']);
-        JournalSupport::storeBudgetWithJournal($journal, $data['budget_id']);
+        $this->storeCategoryWithJournal($journal, $data['category']);
+        $this->storeBudgetWithJournal($journal, $data['budget_id']);
 
         // store two transactions:
         $one = [
@@ -202,7 +202,7 @@ class JournalRepository implements JournalRepositoryInterface
             'budget'                  => null,
             'identifier'              => 0,
         ];
-        JournalSupport::storeTransaction($one);
+        $this->storeTransaction($one);
 
         $two = [
             'journal'                 => $journal,
@@ -217,7 +217,7 @@ class JournalRepository implements JournalRepositoryInterface
             'identifier'              => 0,
         ];
 
-        JournalSupport::storeTransaction($two);
+        $this->storeTransaction($two);
 
 
         // store tags
@@ -241,30 +241,112 @@ class JournalRepository implements JournalRepositoryInterface
     }
 
     /**
+     * @param TransactionJournal $journal
+     * @param array              $data
      *
-     * * Remember: a balancingAct takes at most one expense and one transfer.
-     *            an advancePayment takes at most one expense, infinite deposits and NO transfers.
+     * @return TransactionJournal
+     */
+    public function update(TransactionJournal $journal, array $data): TransactionJournal
+    {
+
+        // update actual journal:
+        $journal->description   = $data['description'];
+        $journal->date          = $data['date'];
+        $accounts               = $this->storeAccounts($this->user, $journal->transactionType, $data);
+        $data                   = $this->verifyNativeAmount($data, $accounts);
+        $data['amount']         = strval($data['amount']);
+        $data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']);
+
+        // unlink all categories, recreate them:
+        $journal->categories()->detach();
+        $journal->budgets()->detach();
+
+        $this->storeCategoryWithJournal($journal, $data['category']);
+        $this->storeBudgetWithJournal($journal, $data['budget_id']);
+
+        // negative because source loses money.
+        $this->updateSourceTransaction($journal, $accounts['source'], $data);
+
+        // positive because destination gets money.
+        $this->updateDestinationTransaction($journal, $accounts['destination'], $data);
+
+        $journal->save();
+
+        // update tags:
+        if (isset($data['tags']) && is_array($data['tags'])) {
+            $this->updateTags($journal, $data['tags']);
+        }
+
+        // update meta fields:
+        $result = $journal->save();
+        if ($result) {
+            foreach ($data as $key => $value) {
+                if (in_array($key, $this->validMetaFields)) {
+                    $journal->setMeta($key, $value);
+                    continue;
+                }
+                Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
+            }
+
+            return $journal;
+        }
+
+        return $journal;
+    }
+
+    /**
+     * Same as above but for transaction journal with multiple transactions.
      *
      * @param TransactionJournal $journal
-     * @param array              $array
+     * @param array              $data
      *
-     * @return bool
+     * @return TransactionJournal
      */
-    private function saveTags(TransactionJournal $journal, array $array): bool
+    public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal
     {
-        /** @var TagRepositoryInterface $tagRepository */
-        $tagRepository = app(TagRepositoryInterface::class);
+        // update actual journal:
+        $journal->description = $data['journal_description'];
+        $journal->date        = $data['date'];
+        $journal->save();
+        Log::debug(sprintf('Updated split journal #%d', $journal->id));
 
-        foreach ($array as $name) {
-            if (strlen(trim($name)) > 0) {
-                $tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
-                if (!is_null($tag)) {
-                    Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
-                    $tagRepository->connect($journal, $tag);
+        // unlink all categories:
+        $journal->categories()->detach();
+        $journal->budgets()->detach();
+
+        // update meta fields:
+        $result = $journal->save();
+        if ($result) {
+            foreach ($data as $key => $value) {
+                if (in_array($key, $this->validMetaFields)) {
+                    $journal->setMeta($key, $value);
+                    continue;
                 }
+                Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
             }
         }
 
-        return true;
+        // update tags:
+        if (isset($data['tags']) && is_array($data['tags'])) {
+            $this->updateTags($journal, $data['tags']);
+        }
+
+        // delete original transactions, and recreate them.
+        $journal->transactions()->delete();
+
+        // store each transaction.
+        $identifier = 0;
+        Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions'])));
+
+        foreach ($data['transactions'] as $transaction) {
+            Log::debug(sprintf('Split journal update split transaction %d', $identifier));
+            $transaction = $this->appendTransactionData($transaction, $data);
+            $this->storeSplitTransaction($journal, $transaction, $identifier);
+            $identifier++;
+        }
+
+        $journal->save();
+
+        return $journal;
     }
 }
diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php
index a25ee4fa23..028f2d52d7 100644
--- a/app/Repositories/Journal/JournalRepositoryInterface.php
+++ b/app/Repositories/Journal/JournalRepositoryInterface.php
@@ -95,4 +95,20 @@ interface JournalRepositoryInterface
      */
     public function store(array $data): TransactionJournal;
 
+    /**
+     * @param TransactionJournal $journal
+     * @param array              $data
+     *
+     * @return TransactionJournal
+     */
+    public function update(TransactionJournal $journal, array $data): TransactionJournal;
+
+    /**
+     * @param TransactionJournal $journal
+     * @param array              $data
+     *
+     * @return TransactionJournal
+     */
+    public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal;
+
 }
diff --git a/app/Repositories/Journal/JournalUpdate.php b/app/Repositories/Journal/JournalUpdate.php
deleted file mode 100644
index ff438369a2..0000000000
--- a/app/Repositories/Journal/JournalUpdate.php
+++ /dev/null
@@ -1,290 +0,0 @@
-user = $user;
-    }
-
-    /**
-     * @param TransactionJournal $journal
-     * @param array              $data
-     *
-     * @return TransactionJournal
-     */
-    public function update(TransactionJournal $journal, array $data): TransactionJournal
-    {
-
-        // update actual journal:
-        $journal->description   = $data['description'];
-        $journal->date          = $data['date'];
-        $accounts               = JournalSupport::storeAccounts($this->user, $journal->transactionType, $data);
-        $data                   = JournalSupport::verifyNativeAmount($data, $accounts);
-        $data['amount']         = strval($data['amount']);
-        $data['foreign_amount'] = is_null($data['foreign_amount']) ? null : strval($data['foreign_amount']);
-
-        // unlink all categories, recreate them:
-        $journal->categories()->detach();
-        $journal->budgets()->detach();
-
-        JournalSupport::storeCategoryWithJournal($journal, $data['category']);
-        JournalSupport::storeBudgetWithJournal($journal, $data['budget_id']);
-
-        // negative because source loses money.
-        $this->updateSourceTransaction($journal, $accounts['source'], $data);
-
-        // positive because destination gets money.
-        $this->updateDestinationTransaction($journal, $accounts['destination'], $data);
-
-        $journal->save();
-
-        // update tags:
-        if (isset($data['tags']) && is_array($data['tags'])) {
-            JournalSupport::updateTags($journal, $data['tags']);
-        }
-
-        // update meta fields:
-        $result = $journal->save();
-        if ($result) {
-            foreach ($data as $key => $value) {
-                if (in_array($key, $this->validMetaFields)) {
-                    $journal->setMeta($key, $value);
-                    continue;
-                }
-                Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
-            }
-
-            return $journal;
-        }
-
-        return $journal;
-    }
-
-    /**
-     * Same as above but for transaction journal with multiple transactions.
-     *
-     * @param TransactionJournal $journal
-     * @param array              $data
-     *
-     * @return TransactionJournal
-     */
-    public function updateSplitJournal(TransactionJournal $journal, array $data): TransactionJournal
-    {
-        // update actual journal:
-        $journal->description = $data['journal_description'];
-        $journal->date        = $data['date'];
-        $journal->save();
-        Log::debug(sprintf('Updated split journal #%d', $journal->id));
-
-        // unlink all categories:
-        $journal->categories()->detach();
-        $journal->budgets()->detach();
-
-        // update meta fields:
-        $result = $journal->save();
-        if ($result) {
-            foreach ($data as $key => $value) {
-                if (in_array($key, $this->validMetaFields)) {
-                    $journal->setMeta($key, $value);
-                    continue;
-                }
-                Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
-            }
-        }
-
-
-        // update tags:
-        if (isset($data['tags']) && is_array($data['tags'])) {
-            JournalSupport::updateTags($journal, $data['tags']);
-        }
-
-        // delete original transactions, and recreate them.
-        $journal->transactions()->delete();
-
-        // store each transaction.
-        $identifier = 0;
-        Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions'])));
-
-        foreach ($data['transactions'] as $transaction) {
-            Log::debug(sprintf('Split journal update split transaction %d', $identifier));
-            $transaction = $this->appendTransactionData($transaction, $data);
-            $this->storeSplitTransaction($journal, $transaction, $identifier);
-            $identifier++;
-        }
-
-        $journal->save();
-
-        return $journal;
-    }
-
-    /**
-     * When the user edits a split journal, each line is missing crucial data:
-     *
-     * - Withdrawal lines are missing the source account ID
-     * - Deposit lines are missing the destination account ID
-     * - Transfers are missing both.
-     *
-     * We need to append the array.
-     *
-     * @param array $transaction
-     * @param array $data
-     *
-     * @return array
-     */
-    private function appendTransactionData(array $transaction, array $data): array
-    {
-        switch ($data['what']) {
-            case strtolower(TransactionType::TRANSFER):
-            case strtolower(TransactionType::WITHDRAWAL):
-                $transaction['source_account_id'] = intval($data['journal_source_account_id']);
-                break;
-        }
-
-        switch ($data['what']) {
-            case strtolower(TransactionType::TRANSFER):
-            case strtolower(TransactionType::DEPOSIT):
-                $transaction['destination_account_id'] = intval($data['journal_destination_account_id']);
-                break;
-        }
-
-        return $transaction;
-    }
-
-    /**
-     * @param TransactionJournal $journal
-     * @param array              $transaction
-     * @param int                $identifier
-     *
-     * @return Collection
-     */
-    private function storeSplitTransaction(TransactionJournal $journal, array $transaction, int $identifier): Collection
-    {
-        // store source and destination accounts (depends on type)
-        $accounts = JournalSupport::storeAccounts($this->user, $journal->transactionType, $transaction);
-
-        // store transaction one way:
-        $amount        = bcmul(strval($transaction['amount']), '-1');
-        $foreignAmount = is_null($transaction['foreign_amount']) ? null : bcmul(strval($transaction['foreign_amount']), '-1');
-        $one           = JournalSupport::storeTransaction(
-            [
-                'journal'                 => $journal,
-                'account'                 => $accounts['source'],
-                'amount'                  => $amount,
-                'transaction_currency_id' => $transaction['transaction_currency_id'],
-                'foreign_amount'          => $foreignAmount,
-                'foreign_currency_id'     => $transaction['foreign_currency_id'],
-                'description'             => $transaction['description'],
-                'category'                => null,
-                'budget'                  => null,
-                'identifier'              => $identifier,
-            ]
-        );
-        JournalSupport::storeCategoryWithTransaction($one, $transaction['category']);
-        JournalSupport::storeBudgetWithTransaction($one, $transaction['budget_id']);
-
-        // and the other way:
-        $amount        = strval($transaction['amount']);
-        $foreignAmount = is_null($transaction['foreign_amount']) ? null : strval($transaction['foreign_amount']);
-        $two           = JournalSupport::storeTransaction(
-            [
-                'journal'                 => $journal,
-                'account'                 => $accounts['destination'],
-                'amount'                  => $amount,
-                'transaction_currency_id' => $transaction['transaction_currency_id'],
-                'foreign_amount'          => $foreignAmount,
-                'foreign_currency_id'     => $transaction['foreign_currency_id'],
-                'description'             => $transaction['description'],
-                'category'                => null,
-                'budget'                  => null,
-                'identifier'              => $identifier,
-            ]
-        );
-        JournalSupport::storeCategoryWithTransaction($two, $transaction['category']);
-        JournalSupport::storeBudgetWithTransaction($two, $transaction['budget_id']);
-
-        return new Collection([$one, $two]);
-    }
-
-    /**
-     * @param TransactionJournal $journal
-     * @param Account            $account
-     * @param array              $data
-     *
-     * @throws FireflyException
-     */
-    private function updateDestinationTransaction(TransactionJournal $journal, Account $account, array $data)
-    {
-        $set = $journal->transactions()->where('amount', '>', 0)->get();
-        if ($set->count() != 1) {
-            throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
-        }
-        /** @var Transaction $transaction */
-        $transaction                          = $set->first();
-        $transaction->amount                  = app('steam')->positive($data['amount']);
-        $transaction->transaction_currency_id = $data['currency_id'];
-        $transaction->foreign_amount          = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']);
-        $transaction->foreign_currency_id     = $data['foreign_currency_id'];
-        $transaction->account_id              = $account->id;
-        $transaction->save();
-
-    }
-
-    /**
-     * @param TransactionJournal $journal
-     * @param Account            $account
-     * @param array              $data
-     *
-     * @throws FireflyException
-     */
-    private function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data)
-    {
-        // should be one:
-        $set = $journal->transactions()->where('amount', '<', 0)->get();
-        if ($set->count() != 1) {
-            throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
-        }
-        /** @var Transaction $transaction */
-        $transaction                          = $set->first();
-        $transaction->amount                  = bcmul(app('steam')->positive($data['amount']), '-1');
-        $transaction->transaction_currency_id = $data['currency_id'];
-        $transaction->foreign_amount          = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1');
-        $transaction->foreign_currency_id     = $data['foreign_currency_id'];
-        $transaction->account_id              = $account->id;
-        $transaction->save();
-    }
-
-}
\ No newline at end of file
diff --git a/app/Repositories/Journal/JournalUpdateInterface.php b/app/Repositories/Journal/JournalUpdateInterface.php
deleted file mode 100644
index 21f6f80c85..0000000000
--- a/app/Repositories/Journal/JournalUpdateInterface.php
+++ /dev/null
@@ -1,44 +0,0 @@
- null,
@@ -50,11 +46,11 @@ class JournalSupport
         Log::debug(sprintf('Going to store accounts for type %s', $type->type));
         switch ($type->type) {
             case TransactionType::WITHDRAWAL:
-                $accounts = self::storeWithdrawalAccounts($user, $data);
+                $accounts = $this->storeWithdrawalAccounts($user, $data);
                 break;
 
             case TransactionType::DEPOSIT:
-                $accounts = self::storeDepositAccounts($user, $data);
+                $accounts = $this->storeDepositAccounts($user, $data);
 
                 break;
             case TransactionType::TRANSFER:
@@ -84,7 +80,7 @@ class JournalSupport
      * @param TransactionJournal $journal
      * @param int                $budgetId
      */
-    public static function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
+    protected function storeBudgetWithJournal(TransactionJournal $journal, int $budgetId)
     {
         if (intval($budgetId) > 0 && $journal->transactionType->type === TransactionType::WITHDRAWAL) {
             /** @var \FireflyIII\Models\Budget $budget */
@@ -93,24 +89,11 @@ class JournalSupport
         }
     }
 
-    /**
-     * @param Transaction $transaction
-     * @param int         $budgetId
-     */
-    public static function storeBudgetWithTransaction(Transaction $transaction, int $budgetId)
-    {
-        if (intval($budgetId) > 0 && $transaction->transactionJournal->transactionType->type !== TransactionType::TRANSFER) {
-            /** @var \FireflyIII\Models\Budget $budget */
-            $budget = Budget::find($budgetId);
-            $transaction->budgets()->save($budget);
-        }
-    }
-
     /**
      * @param TransactionJournal $journal
      * @param string             $category
      */
-    public static function storeCategoryWithJournal(TransactionJournal $journal, string $category)
+    protected function storeCategoryWithJournal(TransactionJournal $journal, string $category)
     {
         if (strlen($category) > 0) {
             $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]);
@@ -118,25 +101,13 @@ class JournalSupport
         }
     }
 
-    /**
-     * @param Transaction $transaction
-     * @param string      $category
-     */
-    public static function storeCategoryWithTransaction(Transaction $transaction, string $category)
-    {
-        if (strlen($category) > 0) {
-            $category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $transaction->transactionJournal->user_id]);
-            $transaction->categories()->save($category);
-        }
-    }
-
     /**
      * @param User  $user
      * @param array $data
      *
      * @return array
      */
-    public static function storeDepositAccounts(User $user, array $data): array
+    protected function storeDepositAccounts(User $user, array $data): array
     {
         Log::debug('Now in storeDepositAccounts().');
         $destinationAccount = Account::where('user_id', $user->id)->where('id', $data['destination_account_id'])->first(['accounts.*']);
@@ -170,56 +141,13 @@ class JournalSupport
         ];
     }
 
-    /**
-     * @param array $data
-     *
-     * @return Transaction
-     */
-    public static function storeTransaction(array $data): Transaction
-    {
-        $fields = [
-            'transaction_journal_id'  => $data['journal']->id,
-            'account_id'              => $data['account']->id,
-            'amount'                  => $data['amount'],
-            'foreign_amount'          => $data['foreign_amount'],
-            'transaction_currency_id' => $data['transaction_currency_id'],
-            'foreign_currency_id'     => $data['foreign_currency_id'],
-            'description'             => $data['description'],
-            'identifier'              => $data['identifier'],
-        ];
-
-
-        if (is_null($data['foreign_currency_id'])) {
-            unset($fields['foreign_currency_id']);
-        }
-        if (is_null($data['foreign_amount'])) {
-            unset($fields['foreign_amount']);
-        }
-
-        /** @var Transaction $transaction */
-        $transaction = Transaction::create($fields);
-
-        Log::debug(sprintf('Transaction stored with ID: %s', $transaction->id));
-
-        if (!is_null($data['category'])) {
-            $transaction->categories()->save($data['category']);
-        }
-
-        if (!is_null($data['budget'])) {
-            $transaction->categories()->save($data['budget']);
-        }
-
-        return $transaction;
-
-    }
-
     /**
      * @param User  $user
      * @param array $data
      *
      * @return array
      */
-    public static function storeWithdrawalAccounts(User $user, array $data): array
+    protected function storeWithdrawalAccounts(User $user, array $data): array
     {
         Log::debug('Now in storeWithdrawalAccounts().');
         $sourceAccount = Account::where('user_id', $user->id)->where('id', $data['source_account_id'])->first(['accounts.*']);
@@ -256,49 +184,6 @@ class JournalSupport
         ];
     }
 
-    /**
-     * @param TransactionJournal $journal
-     * @param array              $array
-     *
-     * @return bool
-     */
-    public static function updateTags(TransactionJournal $journal, array $array): bool
-    {
-        // create tag repository
-        /** @var TagRepositoryInterface $tagRepository */
-        $tagRepository = app(TagRepositoryInterface::class);
-
-
-        // find or create all tags:
-        $tags = [];
-        $ids  = [];
-        foreach ($array as $name) {
-            if (strlen(trim($name)) > 0) {
-                $tag    = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
-                $tags[] = $tag;
-                $ids[]  = $tag->id;
-            }
-        }
-
-        // delete all tags connected to journal not in this array:
-        if (count($ids) > 0) {
-            DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
-        }
-        // if count is zero, delete them all:
-        if (count($ids) == 0) {
-            DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
-        }
-
-        // connect each tag to journal (if not yet connected):
-        /** @var Tag $tag */
-        foreach ($tags as $tag) {
-            Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
-            $tagRepository->connect($journal, $tag);
-        }
-
-        return true;
-    }
-
     /**
      * This method checks the data array and the given accounts to verify that the native amount, currency
      * and possible the foreign currency and amount are properly saved.
@@ -309,7 +194,7 @@ class JournalSupport
      * @return array
      * @throws FireflyException
      */
-    public static function verifyNativeAmount(array $data, array $accounts): array
+    protected function verifyNativeAmount(array $data, array $accounts): array
     {
         /** @var TransactionType $transactionType */
         $transactionType             = TransactionType::where('type', ucfirst($data['what']))->first();
diff --git a/app/Repositories/Journal/UpdateJournalsTrait.php b/app/Repositories/Journal/UpdateJournalsTrait.php
new file mode 100644
index 0000000000..8e249d5bff
--- /dev/null
+++ b/app/Repositories/Journal/UpdateJournalsTrait.php
@@ -0,0 +1,156 @@
+transactions()->where('amount', '>', 0)->get();
+        if ($set->count() != 1) {
+            throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
+        }
+        /** @var Transaction $transaction */
+        $transaction                          = $set->first();
+        $transaction->amount                  = app('steam')->positive($data['amount']);
+        $transaction->transaction_currency_id = $data['currency_id'];
+        $transaction->foreign_amount          = is_null($data['foreign_amount']) ? null : app('steam')->positive($data['foreign_amount']);
+        $transaction->foreign_currency_id     = $data['foreign_currency_id'];
+        $transaction->account_id              = $account->id;
+        $transaction->save();
+
+    }
+
+    /**
+     * @param TransactionJournal $journal
+     * @param Account            $account
+     * @param array              $data
+     *
+     * @throws FireflyException
+     */
+    protected function updateSourceTransaction(TransactionJournal $journal, Account $account, array $data)
+    {
+        // should be one:
+        $set = $journal->transactions()->where('amount', '<', 0)->get();
+        if ($set->count() != 1) {
+            throw new FireflyException(sprintf('Journal #%d has %d transactions with an amount more than zero.', $journal->id, $set->count()));
+        }
+        /** @var Transaction $transaction */
+        $transaction                          = $set->first();
+        $transaction->amount                  = bcmul(app('steam')->positive($data['amount']), '-1');
+        $transaction->transaction_currency_id = $data['currency_id'];
+        $transaction->foreign_amount          = is_null($data['foreign_amount']) ? null : bcmul(app('steam')->positive($data['foreign_amount']), '-1');
+        $transaction->foreign_currency_id     = $data['foreign_currency_id'];
+        $transaction->account_id              = $account->id;
+        $transaction->save();
+    }
+
+    /**
+     * @param TransactionJournal $journal
+     * @param array              $array
+     *
+     * @return bool
+     */
+    protected function updateTags(TransactionJournal $journal, array $array): bool
+    {
+        // create tag repository
+        /** @var TagRepositoryInterface $tagRepository */
+        $tagRepository = app(TagRepositoryInterface::class);
+
+
+        // find or create all tags:
+        $tags = [];
+        $ids  = [];
+        foreach ($array as $name) {
+            if (strlen(trim($name)) > 0) {
+                $tag    = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
+                $tags[] = $tag;
+                $ids[]  = $tag->id;
+            }
+        }
+
+        // delete all tags connected to journal not in this array:
+        if (count($ids) > 0) {
+            DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
+        }
+        // if count is zero, delete them all:
+        if (count($ids) == 0) {
+            DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
+        }
+
+        // connect each tag to journal (if not yet connected):
+        /** @var Tag $tag */
+        foreach ($tags as $tag) {
+            Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
+            $tagRepository->connect($journal, $tag);
+        }
+
+        return true;
+    }
+}
\ No newline at end of file

From 8bbd3063ec0eea2e77a9bd3622825735667cf49e Mon Sep 17 00:00:00 2001
From: James Cole 
Date: Wed, 7 Jun 2017 11:13:04 +0200
Subject: [PATCH 099/103] Move code around for simplicity and fix tests.

---
 app/Handlers/Events/UserEventHandler.php      |   4 +-
 app/Import/Setup/CsvSetup.php                 | 222 +---------------
 app/Mail/RegisteredUser.php                   |   8 +-
 app/Mail/RequestedNewPassword.php             |  12 +-
 app/Support/Import/CsvImportSupportTrait.php  | 242 ++++++++++++++++++
 app/User.php                                  |   4 +-
 resources/views/emails/footer-html.twig       |   2 +-
 resources/views/emails/footer-text.twig       |   2 +-
 routes/web.php                                |   2 +-
 .../Controllers/BudgetControllerTest.php      |   6 +-
 .../Transaction/MassControllerTest.php        |   6 +-
 .../Transaction/SingleControllerTest.php      |   4 +-
 .../Transaction/SplitControllerTest.php       |   4 +-
 .../Handlers/Events/UserEventHandlerTest.php  |   4 +-
 14 files changed, 271 insertions(+), 251 deletions(-)
 create mode 100644 app/Support/Import/CsvImportSupportTrait.php

diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php
index 0259c403e5..8c7eb4a90c 100644
--- a/app/Handlers/Events/UserEventHandler.php
+++ b/app/Handlers/Events/UserEventHandler.php
@@ -97,12 +97,12 @@ class UserEventHandler
         }
         // get the email address
         $email     = $event->user->email;
-        $address   = route('index');
+        $uri       = route('index');
         $ipAddress = $event->ipAddress;
 
         // send email.
         try {
-            Mail::to($email)->send(new RegisteredUserMail($address, $ipAddress));
+            Mail::to($email)->send(new RegisteredUserMail($uri, $ipAddress));
             // @codeCoverageIgnoreStart
         } catch (Swift_TransportException $e) {
             Log::error($e->getMessage());
diff --git a/app/Import/Setup/CsvSetup.php b/app/Import/Setup/CsvSetup.php
index 51ccedb338..e3888e0030 100644
--- a/app/Import/Setup/CsvSetup.php
+++ b/app/Import/Setup/CsvSetup.php
@@ -16,15 +16,12 @@ namespace FireflyIII\Import\Setup;
 
 use ExpandedForm;
 use FireflyIII\Exceptions\FireflyException;
-use FireflyIII\Import\Mapper\MapperInterface;
-use FireflyIII\Import\MapperPreProcess\PreProcessorInterface;
-use FireflyIII\Import\Specifics\SpecificInterface;
 use FireflyIII\Models\Account;
 use FireflyIII\Models\AccountType;
 use FireflyIII\Models\ImportJob;
 use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Support\Import\CsvImportSupportTrait;
 use Illuminate\Http\Request;
-use League\Csv\Reader;
 use Log;
 use Symfony\Component\HttpFoundation\FileBag;
 
@@ -35,6 +32,7 @@ use Symfony\Component\HttpFoundation\FileBag;
  */
 class CsvSetup implements SetupInterface
 {
+    use CsvImportSupportTrait;
     /** @var  Account */
     public $defaultImportAccount;
     /** @var  ImportJob */
@@ -286,220 +284,4 @@ class CsvSetup implements SetupInterface
             $this->job->save();
         }
     }
-
-    /**
-     * @return bool
-     */
-    private function doColumnMapping(): bool
-    {
-        $mapArray = $this->job->configuration['column-do-mapping'] ?? [];
-        $doMap    = false;
-        foreach ($mapArray as $value) {
-            if ($value === true) {
-                $doMap = true;
-                break;
-            }
-        }
-
-        return $this->job->configuration['column-mapping-complete'] === false && $doMap;
-    }
-
-    /**
-     * @return bool
-     */
-    private function doColumnRoles(): bool
-    {
-        return $this->job->configuration['column-roles-complete'] === false;
-    }
-
-    /**
-     * @return array
-     * @throws FireflyException
-     */
-    private function getDataForColumnMapping(): array
-    {
-        $config  = $this->job->configuration;
-        $data    = [];
-        $indexes = [];
-
-        foreach ($config['column-do-mapping'] as $index => $mustBeMapped) {
-            if ($mustBeMapped) {
-
-                $column = $config['column-roles'][$index] ?? '_ignore';
-
-                // is valid column?
-                $validColumns = array_keys(config('csv.import_roles'));
-                if (!in_array($column, $validColumns)) {
-                    throw new FireflyException(sprintf('"%s" is not a valid column.', $column));
-                }
-
-                $canBeMapped   = config('csv.import_roles.' . $column . '.mappable');
-                $preProcessMap = config('csv.import_roles.' . $column . '.pre-process-map');
-                if ($canBeMapped) {
-                    $mapperClass = config('csv.import_roles.' . $column . '.mapper');
-                    $mapperName  = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass);
-                    /** @var MapperInterface $mapper */
-                    $mapper       = new $mapperName;
-                    $indexes[]    = $index;
-                    $data[$index] = [
-                        'name'          => $column,
-                        'mapper'        => $mapperName,
-                        'index'         => $index,
-                        'options'       => $mapper->getMap(),
-                        'preProcessMap' => null,
-                        'values'        => [],
-                    ];
-                    if ($preProcessMap) {
-                        $preClass                      = sprintf(
-                            '\\FireflyIII\\Import\\MapperPreProcess\\%s',
-                            config('csv.import_roles.' . $column . '.pre-process-mapper')
-                        );
-                        $data[$index]['preProcessMap'] = $preClass;
-                    }
-                }
-
-            }
-        }
-
-        // in order to actually map we also need all possible values from the CSV file.
-        $content = $this->job->uploadFileContents();
-        /** @var Reader $reader */
-        $reader = Reader::createFromString($content);
-        $reader->setDelimiter($config['delimiter']);
-        $results        = $reader->fetch();
-        $validSpecifics = array_keys(config('csv.import_specifics'));
-
-        foreach ($results as $rowIndex => $row) {
-
-            // skip first row?
-            if ($rowIndex === 0 && $config['has-headers']) {
-                continue;
-            }
-
-            // run specifics here:
-            // and this is the point where the specifix go to work.
-            foreach ($config['specifics'] as $name => $enabled) {
-
-                if (!in_array($name, $validSpecifics)) {
-                    throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
-                }
-                $class = config('csv.import_specifics.' . $name);
-                /** @var SpecificInterface $specific */
-                $specific = app($class);
-
-                // it returns the row, possibly modified:
-                $row = $specific->run($row);
-            }
-
-            //do something here
-            foreach ($indexes as $index) { // this is simply 1, 2, 3, etc.
-                if (!isset($row[$index])) {
-                    // don't really know how to handle this. Just skip, for now.
-                    continue;
-                }
-                $value = $row[$index];
-                if (strlen($value) > 0) {
-
-                    // we can do some preprocessing here,
-                    // which is exclusively to fix the tags:
-                    if (!is_null($data[$index]['preProcessMap'])) {
-                        /** @var PreProcessorInterface $preProcessor */
-                        $preProcessor           = app($data[$index]['preProcessMap']);
-                        $result                 = $preProcessor->run($value);
-                        $data[$index]['values'] = array_merge($data[$index]['values'], $result);
-
-                        Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]);
-                        Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]);
-                        Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $data[$index]['values']]);
-
-
-                        continue;
-                    }
-
-                    $data[$index]['values'][] = $value;
-                }
-            }
-        }
-        foreach ($data as $index => $entry) {
-            $data[$index]['values'] = array_unique($data[$index]['values']);
-        }
-
-        return $data;
-    }
-
-    /**
-     * This method collects the data that will enable a user to choose column content.
-     *
-     * @return array
-     */
-    private function getDataForColumnRoles(): array
-    {
-        Log::debug('Now in getDataForColumnRoles()');
-        $config = $this->job->configuration;
-        $data   = [
-            'columns'       => [],
-            'columnCount'   => 0,
-            'columnHeaders' => [],
-        ];
-
-        // show user column role configuration.
-        $content = $this->job->uploadFileContents();
-
-        // create CSV reader.
-        $reader = Reader::createFromString($content);
-        $reader->setDelimiter($config['delimiter']);
-        $start  = $config['has-headers'] ? 1 : 0;
-        $end    = $start + config('csv.example_rows');
-        $header = [];
-        if ($config['has-headers']) {
-            $header = $reader->fetchOne(0);
-        }
-
-
-        // collect example data in $data['columns']
-        Log::debug(sprintf('While %s is smaller than %d', $start, $end));
-        while ($start < $end) {
-            $row = $reader->fetchOne($start);
-            Log::debug(sprintf('Row %d has %d columns', $start, count($row)));
-            // run specifics here:
-            // and this is the point where the specifix go to work.
-            foreach ($config['specifics'] as $name => $enabled) {
-                /** @var SpecificInterface $specific */
-                $specific = app('FireflyIII\Import\Specifics\\' . $name);
-                Log::debug(sprintf('Will now apply specific "%s" to row %d.', $name, $start));
-                // it returns the row, possibly modified:
-                $row = $specific->run($row);
-            }
-
-            foreach ($row as $index => $value) {
-                $value                         = trim($value);
-                $data['columnHeaders'][$index] = $header[$index] ?? '';
-                if (strlen($value) > 0) {
-                    $data['columns'][$index][] = $value;
-                }
-            }
-            $start++;
-            $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount'];
-        }
-
-        // make unique example data
-        foreach ($data['columns'] as $index => $values) {
-            $data['columns'][$index] = array_unique($values);
-        }
-
-        $data['set_roles'] = [];
-        // collect possible column roles:
-        $data['available_roles'] = [];
-        foreach (array_keys(config('csv.import_roles')) as $role) {
-            $data['available_roles'][$role] = trans('csv.column_' . $role);
-        }
-
-        $config['column-count']   = $data['columnCount'];
-        $this->job->configuration = $config;
-        $this->job->save();
-
-        return $data;
-
-
-    }
 }
diff --git a/app/Mail/RegisteredUser.php b/app/Mail/RegisteredUser.php
index a693fb13e1..d988506393 100644
--- a/app/Mail/RegisteredUser.php
+++ b/app/Mail/RegisteredUser.php
@@ -12,18 +12,18 @@ class RegisteredUser extends Mailable
     /** @var  string */
     public $address;
     /** @var  string */
-    public $userIp;
+    public $ipAddress;
 
     /**
      * Create a new message instance.
      *
      * @param string $address
-     * @param string $userIp
+     * @param string $ipAddress
      */
-    public function __construct(string $address, string $userIp)
+    public function __construct(string $address, string $ipAddress)
     {
         $this->address = $address;
-        $this->userIp  = $userIp;
+        $this->ipAddress  = $ipAddress;
     }
 
     /**
diff --git a/app/Mail/RequestedNewPassword.php b/app/Mail/RequestedNewPassword.php
index cdda5b9858..bd2d9e90b0 100644
--- a/app/Mail/RequestedNewPassword.php
+++ b/app/Mail/RequestedNewPassword.php
@@ -10,20 +10,20 @@ class RequestedNewPassword extends Mailable
 {
     use Queueable, SerializesModels;
     /** @var  string */
-    public $url;
+    public $ipAddress;
     /** @var  string */
-    public $userIp;
+    public $url;
 
     /**
      * RequestedNewPassword constructor.
      *
      * @param string $url
-     * @param string $userIp
+     * @param string $ipAddress
      */
-    public function __construct(string $url, string $userIp)
+    public function __construct(string $url, string $ipAddress)
     {
-        $this->url    = $url;
-        $this->userIp = $userIp;
+        $this->url       = $url;
+        $this->ipAddress = $ipAddress;
     }
 
     /**
diff --git a/app/Support/Import/CsvImportSupportTrait.php b/app/Support/Import/CsvImportSupportTrait.php
new file mode 100644
index 0000000000..efee16704a
--- /dev/null
+++ b/app/Support/Import/CsvImportSupportTrait.php
@@ -0,0 +1,242 @@
+job->configuration['column-do-mapping'] ?? [];
+        $doMap    = false;
+        foreach ($mapArray as $value) {
+            if ($value === true) {
+                $doMap = true;
+                break;
+            }
+        }
+
+        return $this->job->configuration['column-mapping-complete'] === false && $doMap;
+    }
+
+    /**
+     * @return bool
+     */
+    protected function doColumnRoles(): bool
+    {
+        return $this->job->configuration['column-roles-complete'] === false;
+    }
+
+    /**
+     * @return array
+     * @throws FireflyException
+     */
+    protected function getDataForColumnMapping(): array
+    {
+        $config  = $this->job->configuration;
+        $data    = [];
+        $indexes = [];
+
+        foreach ($config['column-do-mapping'] as $index => $mustBeMapped) {
+            if ($mustBeMapped) {
+
+                $column = $config['column-roles'][$index] ?? '_ignore';
+
+                // is valid column?
+                $validColumns = array_keys(config('csv.import_roles'));
+                if (!in_array($column, $validColumns)) {
+                    throw new FireflyException(sprintf('"%s" is not a valid column.', $column));
+                }
+
+                $canBeMapped   = config('csv.import_roles.' . $column . '.mappable');
+                $preProcessMap = config('csv.import_roles.' . $column . '.pre-process-map');
+                if ($canBeMapped) {
+                    $mapperClass = config('csv.import_roles.' . $column . '.mapper');
+                    $mapperName  = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass);
+                    /** @var MapperInterface $mapper */
+                    $mapper       = new $mapperName;
+                    $indexes[]    = $index;
+                    $data[$index] = [
+                        'name'          => $column,
+                        'mapper'        => $mapperName,
+                        'index'         => $index,
+                        'options'       => $mapper->getMap(),
+                        'preProcessMap' => null,
+                        'values'        => [],
+                    ];
+                    if ($preProcessMap) {
+                        $preClass                      = sprintf(
+                            '\\FireflyIII\\Import\\MapperPreProcess\\%s',
+                            config('csv.import_roles.' . $column . '.pre-process-mapper')
+                        );
+                        $data[$index]['preProcessMap'] = $preClass;
+                    }
+                }
+
+            }
+        }
+
+        // in order to actually map we also need all possible values from the CSV file.
+        $content = $this->job->uploadFileContents();
+        /** @var Reader $reader */
+        $reader = Reader::createFromString($content);
+        $reader->setDelimiter($config['delimiter']);
+        $results        = $reader->fetch();
+        $validSpecifics = array_keys(config('csv.import_specifics'));
+
+        foreach ($results as $rowIndex => $row) {
+
+            // skip first row?
+            if ($rowIndex === 0 && $config['has-headers']) {
+                continue;
+            }
+
+            // run specifics here:
+            // and this is the point where the specifix go to work.
+            foreach ($config['specifics'] as $name => $enabled) {
+
+                if (!in_array($name, $validSpecifics)) {
+                    throw new FireflyException(sprintf('"%s" is not a valid class name', $name));
+                }
+                $class = config('csv.import_specifics.' . $name);
+                /** @var SpecificInterface $specific */
+                $specific = app($class);
+
+                // it returns the row, possibly modified:
+                $row = $specific->run($row);
+            }
+
+            //do something here
+            foreach ($indexes as $index) { // this is simply 1, 2, 3, etc.
+                if (!isset($row[$index])) {
+                    // don't really know how to handle this. Just skip, for now.
+                    continue;
+                }
+                $value = $row[$index];
+                if (strlen($value) > 0) {
+
+                    // we can do some preprocessing here,
+                    // which is exclusively to fix the tags:
+                    if (!is_null($data[$index]['preProcessMap'])) {
+                        /** @var PreProcessorInterface $preProcessor */
+                        $preProcessor           = app($data[$index]['preProcessMap']);
+                        $result                 = $preProcessor->run($value);
+                        $data[$index]['values'] = array_merge($data[$index]['values'], $result);
+
+                        Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]);
+                        Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]);
+                        Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $data[$index]['values']]);
+
+
+                        continue;
+                    }
+
+                    $data[$index]['values'][] = $value;
+                }
+            }
+        }
+        foreach ($data as $index => $entry) {
+            $data[$index]['values'] = array_unique($data[$index]['values']);
+        }
+
+        return $data;
+    }
+
+    /**
+     * This method collects the data that will enable a user to choose column content.
+     *
+     * @return array
+     */
+    protected function getDataForColumnRoles(): array
+    {
+        Log::debug('Now in getDataForColumnRoles()');
+        $config = $this->job->configuration;
+        $data   = [
+            'columns'       => [],
+            'columnCount'   => 0,
+            'columnHeaders' => [],
+        ];
+
+        // show user column role configuration.
+        $content = $this->job->uploadFileContents();
+
+        // create CSV reader.
+        $reader = Reader::createFromString($content);
+        $reader->setDelimiter($config['delimiter']);
+        $start  = $config['has-headers'] ? 1 : 0;
+        $end    = $start + config('csv.example_rows');
+        $header = [];
+        if ($config['has-headers']) {
+            $header = $reader->fetchOne(0);
+        }
+
+
+        // collect example data in $data['columns']
+        Log::debug(sprintf('While %s is smaller than %d', $start, $end));
+        while ($start < $end) {
+            $row = $reader->fetchOne($start);
+            Log::debug(sprintf('Row %d has %d columns', $start, count($row)));
+            // run specifics here:
+            // and this is the point where the specifix go to work.
+            foreach ($config['specifics'] as $name => $enabled) {
+                /** @var SpecificInterface $specific */
+                $specific = app('FireflyIII\Import\Specifics\\' . $name);
+                Log::debug(sprintf('Will now apply specific "%s" to row %d.', $name, $start));
+                // it returns the row, possibly modified:
+                $row = $specific->run($row);
+            }
+
+            foreach ($row as $index => $value) {
+                $value                         = trim($value);
+                $data['columnHeaders'][$index] = $header[$index] ?? '';
+                if (strlen($value) > 0) {
+                    $data['columns'][$index][] = $value;
+                }
+            }
+            $start++;
+            $data['columnCount'] = count($row) > $data['columnCount'] ? count($row) : $data['columnCount'];
+        }
+
+        // make unique example data
+        foreach ($data['columns'] as $index => $values) {
+            $data['columns'][$index] = array_unique($values);
+        }
+
+        $data['set_roles'] = [];
+        // collect possible column roles:
+        $data['available_roles'] = [];
+        foreach (array_keys(config('csv.import_roles')) as $role) {
+            $data['available_roles'][$role] = trans('csv.column_' . $role);
+        }
+
+        $config['column-count']   = $data['columnCount'];
+        $this->job->configuration = $config;
+        $this->job->save();
+
+        return $data;
+    }
+}
\ No newline at end of file
diff --git a/app/User.php b/app/User.php
index f5ee28f920..3c045f2412 100644
--- a/app/User.php
+++ b/app/User.php
@@ -214,9 +214,9 @@ class User extends Authenticatable
      */
     public function sendPasswordResetNotification($token)
     {
-        $ip = Request::ip();
+        $ipAddress = Request::ip();
 
-        event(new RequestedNewPassword($this, $token, $ip));
+        event(new RequestedNewPassword($this, $token, $ipAddress));
     }
 
     /**
diff --git a/resources/views/emails/footer-html.twig b/resources/views/emails/footer-html.twig
index 935128c2aa..6e2ca37007 100644
--- a/resources/views/emails/footer-html.twig
+++ b/resources/views/emails/footer-html.twig
@@ -6,7 +6,7 @@
 

- PS: This message was sent because a request from IP {{ ip }} {{ userIp }} triggered it. + PS: This message was sent because a request from IP {{ ip }}{{ userIp }}{{ ipAddress }} triggered it.

diff --git a/resources/views/emails/footer-text.twig b/resources/views/emails/footer-text.twig index 2e4d10c979..e2ec3ff9d4 100644 --- a/resources/views/emails/footer-text.twig +++ b/resources/views/emails/footer-text.twig @@ -3,4 +3,4 @@ Beep boop, The Firefly III Mail Robot -PS: This message was sent because a request from IP {{ ip }} {{ userIp }} triggered it. +PS: This message was sent because a request from IP {{ ip }}{{ userIp }}{{ ipAddress }} triggered it. diff --git a/routes/web.php b/routes/web.php index d8e969e4ac..9782f9d6c6 100755 --- a/routes/web.php +++ b/routes/web.php @@ -134,7 +134,6 @@ Route::group( */ Route::group( ['middleware' => 'user-full-auth', 'prefix' => 'budgets', 'as' => 'budgets.'], function () { - Route::get('{moment?}', ['uses' => 'BudgetController@index', 'as' => 'index']); Route::get('income', ['uses' => 'BudgetController@updateIncome', 'as' => 'income']); Route::get('create', ['uses' => 'BudgetController@create', 'as' => 'create']); Route::get('edit/{budget}', ['uses' => 'BudgetController@edit', 'as' => 'edit']); @@ -142,6 +141,7 @@ Route::group( Route::get('show/{budget}', ['uses' => 'BudgetController@show', 'as' => 'show']); Route::get('show/{budget}/{budgetlimit}', ['uses' => 'BudgetController@showByBudgetLimit', 'as' => 'show.limit']); Route::get('list/no-budget/{moment?}', ['uses' => 'BudgetController@noBudget', 'as' => 'no-budget']); + Route::get('{moment?}', ['uses' => 'BudgetController@index', 'as' => 'index']); Route::post('income', ['uses' => 'BudgetController@postUpdateIncome', 'as' => 'income.post']); Route::post('store', ['uses' => 'BudgetController@store', 'as' => 'store']); diff --git a/tests/Feature/Controllers/BudgetControllerTest.php b/tests/Feature/Controllers/BudgetControllerTest.php index b79a537f54..f740ec293f 100644 --- a/tests/Feature/Controllers/BudgetControllerTest.php +++ b/tests/Feature/Controllers/BudgetControllerTest.php @@ -444,14 +444,16 @@ class BudgetControllerTest extends TestCase */ public function testUpdateIncome() { + // must be in list + $this->be($this->user()); + // mock stuff $repository = $this->mock(BudgetRepositoryInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('getAvailableBudget')->andReturn('1'); + $repository->shouldReceive('cleanupBudgets'); - // must be in list - $this->be($this->user()); $response = $this->get(route('budgets.income', [1])); $response->assertStatus(200); } diff --git a/tests/Feature/Controllers/Transaction/MassControllerTest.php b/tests/Feature/Controllers/Transaction/MassControllerTest.php index 065d66a9e3..988dfaf3ac 100644 --- a/tests/Feature/Controllers/Transaction/MassControllerTest.php +++ b/tests/Feature/Controllers/Transaction/MassControllerTest.php @@ -7,7 +7,7 @@ * See the LICENSE file for details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace Tests\Feature\Controllers\Transaction; @@ -18,7 +18,6 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Support\Collection; use Tests\TestCase; @@ -172,9 +171,8 @@ class MassControllerTest extends TestCase ->first(); // mock stuff $repository = $this->mock(JournalRepositoryInterface::class); - $updater = $this->mock(JournalUpdateInterface::class); $repository->shouldReceive('first')->once()->andReturn(new TransactionJournal); - $updater->shouldReceive('update')->once(); + $repository->shouldReceive('update')->once(); $repository->shouldReceive('find')->once()->andReturn($deposit); diff --git a/tests/Feature/Controllers/Transaction/SingleControllerTest.php b/tests/Feature/Controllers/Transaction/SingleControllerTest.php index b396a961db..56bd1041bf 100644 --- a/tests/Feature/Controllers/Transaction/SingleControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SingleControllerTest.php @@ -23,7 +23,6 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -270,7 +269,6 @@ class SingleControllerTest extends TestCase // mock $this->expectsEvents(UpdatedTransactionJournal::class); - $updater = $this->mock(JournalUpdateInterface::class); $repository = $this->mock(JournalRepositoryInterface::class); $journal = new TransactionJournal(); @@ -280,7 +278,7 @@ class SingleControllerTest extends TestCase $journal->transactionType()->associate($type); - $updater->shouldReceive('update')->andReturn($journal); + $repository->shouldReceive('update')->andReturn($journal); $repository->shouldReceive('first')->times(2)->andReturn(new TransactionJournal); $this->session(['transactions.edit.uri' => 'http://localhost']); diff --git a/tests/Feature/Controllers/Transaction/SplitControllerTest.php b/tests/Feature/Controllers/Transaction/SplitControllerTest.php index c6cf00eb07..578802bc8c 100644 --- a/tests/Feature/Controllers/Transaction/SplitControllerTest.php +++ b/tests/Feature/Controllers/Transaction/SplitControllerTest.php @@ -21,7 +21,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalTaskerInterface; -use FireflyIII\Repositories\Journal\JournalUpdateInterface; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use Tests\TestCase; @@ -135,8 +134,7 @@ class SplitControllerTest extends TestCase // mock stuff $repository = $this->mock(JournalRepositoryInterface::class); - $updater = $this->mock(JournalUpdateInterface::class); - $updater->shouldReceive('updateSplitJournal')->andReturn($deposit); + $repository->shouldReceive('updateSplitJournal')->andReturn($deposit); $repository->shouldReceive('first')->times(2)->andReturn(new TransactionJournal); $attachmentRepos = $this->mock(AttachmentHelperInterface::class); $attachmentRepos->shouldReceive('saveAttachmentsForModel'); diff --git a/tests/Unit/Handlers/Events/UserEventHandlerTest.php b/tests/Unit/Handlers/Events/UserEventHandlerTest.php index 0edb4c3938..27b44dfb72 100644 --- a/tests/Unit/Handlers/Events/UserEventHandlerTest.php +++ b/tests/Unit/Handlers/Events/UserEventHandlerTest.php @@ -56,7 +56,7 @@ class UserEventHandlerTest extends TestCase Mail::assertSent( RequestedNewPasswordMail::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->ip === '127.0.0.1'; + return $mail->hasTo($user->email) && $mail->ipAddress === '127.0.0.1'; } ); @@ -78,7 +78,7 @@ class UserEventHandlerTest extends TestCase // must send user an email: Mail::assertSent( RegisteredUserMail::class, function ($mail) use ($user) { - return $mail->hasTo($user->email) && $mail->ip === '127.0.0.1'; + return $mail->hasTo($user->email) && $mail->ipAddress === '127.0.0.1'; } ); From 935fb015d3aec2860634d907cbda026972cba013 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 7 Jun 2017 11:58:04 +0200 Subject: [PATCH 100/103] Live update budget amounts. --- app/Http/Controllers/BudgetController.php | 3 +- app/Support/Amount.php | 2 +- public/js/ff/budgets/index.js | 17 ++++++++ resources/views/budgets/index.twig | 53 +---------------------- 4 files changed, 22 insertions(+), 53 deletions(-) diff --git a/app/Http/Controllers/BudgetController.php b/app/Http/Controllers/BudgetController.php index 1e4545d518..329f46fdc8 100644 --- a/app/Http/Controllers/BudgetController.php +++ b/app/Http/Controllers/BudgetController.php @@ -241,7 +241,8 @@ class BudgetController extends Controller return view( 'budgets.index', compact( - 'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText', 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', + 'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText', + 'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets', 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start' ) ); diff --git a/app/Support/Amount.php b/app/Support/Amount.php index d90a1a2de4..89ef0dea05 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -204,7 +204,7 @@ class Amount } /** - * @return TransactionCurrency + * @return \FireflyIII\Models\TransactionCurrency * @throws FireflyException */ public function getDefaultCurrency(): TransactionCurrency diff --git a/public/js/ff/budgets/index.js b/public/js/ff/budgets/index.js index ac233b85c4..01a00c9b0f 100644 --- a/public/js/ff/budgets/index.js +++ b/public/js/ff/budgets/index.js @@ -59,9 +59,26 @@ function updateBudgetedAmounts(e) { "use strict"; var target = $(e.target); var id = target.data('id'); + var value = target.val(); var original = target.data('original'); var difference = value - original; + + var spentCell = $('td[class="spent"][data-id="' + id + '"]'); + var leftCell = $('td[class="left"][data-id="' + id + '"]'); + var spentAmount = parseFloat(spentCell.data('spent')); + var newAmountLeft = spentAmount + parseFloat(value); + var amountLeftString = accounting.formatMoney(newAmountLeft); + if(newAmountLeft < 0) { + leftCell.html('' + amountLeftString + ''); + } + if(newAmountLeft > 0) { + leftCell.html('' + amountLeftString + ''); + } + if(newAmountLeft === 0.0) { + leftCell.html('' + amountLeftString + ''); + } + if (difference !== 0) { // add difference to 'budgeted' var budgeted = budgeted + difference; diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 240b7e9845..00214b8c6c 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -176,61 +176,12 @@ step="1" min="0" name="amount" type="number">
- + {{ budgetInformation[budget.id]['spent']|formatAmount }} - + {{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }} - {# -
- - - - - - - - - - {% if budgetInformation[budget.id]['otherLimits'].count > 0 %} - - - - {% endif %} -
- - - -
- -
-
- {{ 'spent'|_ }} -
- {{ session('start').formatLocalized(monthAndDayFormat) }} - - {{ session('end').formatLocalized(monthAndDayFormat) }} -
-
- -
-
    - {% for other in budgetInformation[budget.id]['otherLimits'] %} -
  • - - Budgeted - {{ other.amount|formatAmountPlain }} - between - {{ other.start_date.formatLocalized(monthAndDayFormat) }} - and {{ other.end_date.formatLocalized(monthAndDayFormat) }}. -
  • - {% endfor %} -
-
-
-
- - #} {% endfor %} From d8a8574ddace25ad03a126b20b53ef9e9ca13bf5 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 7 Jun 2017 12:08:32 +0200 Subject: [PATCH 101/103] Prep for new release. --- CHANGELOG.md | 13 +++++++++++++ config/firefly.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a64c684432..33a9707ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.5.0] - 2017-07-07 + +### Added +- Better support for multi-currency transactions and display of transactions, accounts and everything. This requires a database overhaul (moving the currency information to specific transactions) so be careful when upgrading. +- Translations for Spanish and Slovenian. +- New interface for budget page, ~~stolen from~~ inspired by YNAB. +- Expanded Docker to work with postgresql as well, thanks to @kressh + +### Fixed +- PostgreSQL support in database upgrade routine (#644, reported by @) +- Frontpage budget chart was off, fix by @nhaarman +- Was not possible to remove opening balance. + ## [4.4.3] - 2017-05-03 ### Added - Added support for Slovenian diff --git a/config/firefly.php b/config/firefly.php index cb76de0723..c4f648e3a4 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -23,7 +23,7 @@ return [ 'is_demo_site' => false, ], 'encryption' => (is_null(env('USE_ENCRYPTION')) || env('USE_ENCRYPTION') === true), - 'version' => '4.4.3', + 'version' => '4.5.0', 'maxUploadSize' => 5242880, 'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'], 'list_length' => 10, From 474fa9dea0bc026a8880b8f4cc8543cb3e58cdeb Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 7 Jun 2017 12:10:10 +0200 Subject: [PATCH 102/103] New translations firefly.php (Dutch) --- resources/lang/nl_NL/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index b38bce4333..158bc9f235 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -20,7 +20,7 @@ return [ 'everything' => 'Alles', 'customRange' => 'Zelf bereik kiezen', 'apply' => 'Go', - 'select_date' => 'Select date..', + 'select_date' => 'Selecteer periode..', 'cancel' => 'Annuleren', 'from' => 'Van', 'to' => 'Tot', From 5c1879412212d9864b3a70bc0d720d1f94b1a309 Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 7 Jun 2017 12:22:59 +0200 Subject: [PATCH 103/103] Update lock file, and update database. --- composer.lock | 294 +++++++++++++++------------ storage/database/databasecopy.sqlite | Bin 258048 -> 272384 bytes 2 files changed, 159 insertions(+), 135 deletions(-) diff --git a/composer.lock b/composer.lock index 5363e7571a..cef2489c1d 100644 --- a/composer.lock +++ b/composer.lock @@ -665,16 +665,16 @@ }, { "name": "laravel/framework", - "version": "v5.4.21", + "version": "v5.4.24", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee" + "reference": "ec8548db26c1b147570f661128649e98f3ac0f29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee", - "reference": "2ed668f96d1a6ca42f50d5c87ee9ceecfc0a6eee", + "url": "https://api.github.com/repos/laravel/framework/zipball/ec8548db26c1b147570f661128649e98f3ac0f29", + "reference": "ec8548db26c1b147570f661128649e98f3ac0f29", "shasum": "" }, "require": { @@ -790,20 +790,20 @@ "framework", "laravel" ], - "time": "2017-04-28T15:40:01+00:00" + "time": "2017-05-30T12:44:32+00:00" }, { "name": "laravelcollective/html", - "version": "v5.4.1", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/LaravelCollective/html.git", - "reference": "7570f25d58a00fd6909c0563808590f9cdb14d47" + "reference": "9b8f51e7a2368911c896f5d42757886bae0717b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/7570f25d58a00fd6909c0563808590f9cdb14d47", - "reference": "7570f25d58a00fd6909c0563808590f9cdb14d47", + "url": "https://api.github.com/repos/LaravelCollective/html/zipball/9b8f51e7a2368911c896f5d42757886bae0717b5", + "reference": "9b8f51e7a2368911c896f5d42757886bae0717b5", "shasum": "" }, "require": { @@ -844,20 +844,20 @@ ], "description": "HTML and Form Builders for the Laravel Framework", "homepage": "http://laravelcollective.com", - "time": "2017-01-26T19:27:05+00:00" + "time": "2017-05-22T06:35:07+00:00" }, { "name": "league/commonmark", - "version": "0.15.3", + "version": "0.15.4", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc" + "reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c8b43ee5821362216f8e9ac684f0f59de164edcc", - "reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c", + "reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c", "shasum": "" }, "require": { @@ -913,7 +913,7 @@ "markdown", "parser" ], - "time": "2016-12-19T00:11:43+00:00" + "time": "2017-05-09T12:47:53+00:00" }, { "name": "league/csv", @@ -1583,16 +1583,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.7", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4" + "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4", - "reference": "56db4ed32a6d5c9824c3ecc1d2e538f663f47eb4", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517", + "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517", "shasum": "" }, "require": { @@ -1633,20 +1633,20 @@ "mail", "mailer" ], - "time": "2017-04-20T17:32:18+00:00" + "time": "2017-05-01T15:54:03+00:00" }, { "name": "symfony/console", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38" + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", - "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", + "url": "https://api.github.com/repos/symfony/console/zipball/70d2a29b2911cbdc91a7e268046c395278238b2e", + "reference": "70d2a29b2911cbdc91a7e268046c395278238b2e", "shasum": "" }, "require": { @@ -1654,10 +1654,16 @@ "symfony/debug": "~2.8|~3.0", "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, "require-dev": { "psr/log": "~1.0", + "symfony/config": "~3.3", + "symfony/dependency-injection": "~3.3", "symfony/event-dispatcher": "~2.8|~3.0", "symfony/filesystem": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0", "symfony/process": "~2.8|~3.0" }, "suggest": { @@ -1669,7 +1675,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1696,7 +1702,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-04-26T01:39:17+00:00" + "time": "2017-06-02T19:24:58+00:00" }, { "name": "symfony/css-selector", @@ -1753,16 +1759,16 @@ }, { "name": "symfony/debug", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686" + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686", - "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686", + "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", "shasum": "" }, "require": { @@ -1773,13 +1779,12 @@ "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" }, "require-dev": { - "symfony/class-loader": "~2.8|~3.0", "symfony/http-kernel": "~2.8|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1806,11 +1811,11 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-04-19T20:17:50+00:00" + "time": "2017-06-01T21:01:25+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.20", + "version": "v2.8.21", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -1870,16 +1875,16 @@ }, { "name": "symfony/finder", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930" + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", - "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", + "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", "shasum": "" }, "require": { @@ -1888,7 +1893,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1915,20 +1920,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-06-01T21:01:25+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef" + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9de6add7f731e5af7f5b2e9c0da365e43383ebef", - "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", "shasum": "" }, "require": { @@ -1941,7 +1946,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1968,20 +1973,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2017-05-01T14:55:58+00:00" + "time": "2017-06-05T13:06:51+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.2.8", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05" + "reference": "4ad34a0d20a5848c0fcbf6ff6a2ff1cd9cf4b9ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/46e8b209abab55c072c47d72d5cd1d62c0585e05", - "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4ad34a0d20a5848c0fcbf6ff6a2ff1cd9cf4b9ed", + "reference": "4ad34a0d20a5848c0fcbf6ff6a2ff1cd9cf4b9ed", "shasum": "" }, "require": { @@ -1989,18 +1994,21 @@ "psr/log": "~1.0", "symfony/debug": "~2.8|~3.0", "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/http-foundation": "~2.8.13|~3.1.6|~3.2" + "symfony/http-foundation": "~3.3" }, "conflict": { - "symfony/config": "<2.8" + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3" }, "require-dev": { + "psr/cache": "~1.0", "symfony/browser-kit": "~2.8|~3.0", "symfony/class-loader": "~2.8|~3.0", "symfony/config": "~2.8|~3.0", "symfony/console": "~2.8|~3.0", "symfony/css-selector": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", "symfony/dom-crawler": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0", @@ -2009,7 +2017,7 @@ "symfony/stopwatch": "~2.8|~3.0", "symfony/templating": "~2.8|~3.0", "symfony/translation": "~2.8|~3.0", - "symfony/var-dumper": "~3.2" + "symfony/var-dumper": "~3.3" }, "suggest": { "symfony/browser-kit": "", @@ -2023,7 +2031,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -2050,7 +2058,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-05-01T17:46:48+00:00" + "time": "2017-05-29T21:02:12+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2221,16 +2229,16 @@ }, { "name": "symfony/process", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0" + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", - "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", + "url": "https://api.github.com/repos/symfony/process/zipball/8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", + "reference": "8e30690c67aafb6c7992d6d8eb0d707807dd3eaf", "shasum": "" }, "require": { @@ -2239,7 +2247,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -2266,36 +2274,39 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-05-22T12:32:03+00:00" }, { "name": "symfony/routing", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "5029745d6d463585e8b487dbc83d6333f408853a" + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/5029745d6d463585e8b487dbc83d6333f408853a", - "reference": "5029745d6d463585e8b487dbc83d6333f408853a", + "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", "shasum": "" }, "require": { "php": ">=5.5.9" }, "conflict": { - "symfony/config": "<2.8" + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" }, "require-dev": { "doctrine/annotations": "~1.0", "doctrine/common": "~2.2", "psr/log": "~1.0", "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", "symfony/expression-language": "~2.8|~3.0", "symfony/http-foundation": "~2.8|~3.0", - "symfony/yaml": "~2.8|~3.0" + "symfony/yaml": "~3.3" }, "suggest": { "doctrine/annotations": "For using the annotation loader", @@ -2308,7 +2319,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -2341,20 +2352,20 @@ "uri", "url" ], - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-06-02T09:51:43+00:00" }, { "name": "symfony/translation", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f4a04d2df710f81515df576b2de06bdeee518b83" + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83", - "reference": "f4a04d2df710f81515df576b2de06bdeee518b83", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc3b2a0c6cfff60327ba1c043a82092735397543", + "reference": "dc3b2a0c6cfff60327ba1c043a82092735397543", "shasum": "" }, "require": { @@ -2362,13 +2373,14 @@ "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/config": "<2.8" + "symfony/config": "<2.8", + "symfony/yaml": "<3.3" }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~2.8|~3.0", "symfony/intl": "^2.8.18|^3.2.5", - "symfony/yaml": "~2.8|~3.0" + "symfony/yaml": "~3.3" }, "suggest": { "psr/log": "To use logging capability in translator", @@ -2378,7 +2390,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -2405,20 +2417,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-05-22T07:42:36+00:00" }, { "name": "symfony/var-dumper", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8" + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8", - "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/347c4247a3e40018810b476fcd5dec36d46d08dc", + "reference": "347c4247a3e40018810b476fcd5dec36d46d08dc", "shasum": "" }, "require": { @@ -2430,7 +2442,7 @@ }, "require-dev": { "ext-iconv": "*", - "twig/twig": "~1.20|~2.0" + "twig/twig": "~1.34|~2.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", @@ -2439,7 +2451,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -2473,7 +2485,7 @@ "debug", "dump" ], - "time": "2017-05-01T14:55:58+00:00" + "time": "2017-06-02T09:10:29+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -2687,20 +2699,20 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v2.3.2", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "24e4f0261e352d3fd86d0447791b56ae49398674" + "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/24e4f0261e352d3fd86d0447791b56ae49398674", - "reference": "24e4f0261e352d3fd86d0447791b56ae49398674", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/de15d00a74696db62e1b4782474c27ed0c4fc763", + "reference": "de15d00a74696db62e1b4782474c27ed0c4fc763", "shasum": "" }, "require": { - "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*", + "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*", "maximebf/debugbar": "~1.13.0", "php": ">=5.5.9", "symfony/finder": "~2.7|~3.0" @@ -2708,7 +2720,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facade" + } } }, "autoload": { @@ -2737,7 +2757,7 @@ "profiler", "webprofiler" ], - "time": "2017-01-19T08:19:49+00:00" + "time": "2017-06-01T17:46:08+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -3725,16 +3745,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.19", + "version": "5.7.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1" + "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1", - "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3cb94a5f8c07a03c8b7527ed7468a2926203f58b", + "reference": "3cb94a5f8c07a03c8b7527ed7468a2926203f58b", "shasum": "" }, "require": { @@ -3752,7 +3772,7 @@ "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "^3.2", "sebastian/comparator": "^1.2.4", - "sebastian/diff": "~1.2", + "sebastian/diff": "^1.4.3", "sebastian/environment": "^1.3.4 || ^2.0", "sebastian/exporter": "~2.0", "sebastian/global-state": "^1.1", @@ -3803,7 +3823,7 @@ "testing", "xunit" ], - "time": "2017-04-03T02:22:27+00:00" + "time": "2017-05-22T07:42:55+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -4033,23 +4053,23 @@ }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { @@ -4081,7 +4101,7 @@ "keywords": [ "diff" ], - "time": "2015-12-08T07:14:41+00:00" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -4437,16 +4457,16 @@ }, { "name": "symfony/class-loader", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/class-loader.git", - "reference": "fc4c04bfd17130a9dccfded9578353f311967da7" + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/fc4c04bfd17130a9dccfded9578353f311967da7", - "reference": "fc4c04bfd17130a9dccfded9578353f311967da7", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/386a294d621576302e7cc36965d6ed53b8c73c4f", + "reference": "386a294d621576302e7cc36965d6ed53b8c73c4f", "shasum": "" }, "require": { @@ -4462,7 +4482,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -4489,27 +4509,31 @@ ], "description": "Symfony ClassLoader Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-06-02T09:51:43+00:00" }, { "name": "symfony/config", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "e5533fcc0b3dd377626153b2852707878f363728" + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/e5533fcc0b3dd377626153b2852707878f363728", - "reference": "e5533fcc0b3dd377626153b2852707878f363728", + "url": "https://api.github.com/repos/symfony/config/zipball/35716d4904e0506a7a5a9bcf23f854aeb5719bca", + "reference": "35716d4904e0506a7a5a9bcf23f854aeb5719bca", "shasum": "" }, "require": { "php": ">=5.5.9", "symfony/filesystem": "~2.8|~3.0" }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, "require-dev": { + "symfony/dependency-injection": "~3.3", "symfony/yaml": "~3.0" }, "suggest": { @@ -4518,7 +4542,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -4545,7 +4569,7 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-06-02T18:07:20+00:00" }, { "name": "symfony/dom-crawler", @@ -4605,16 +4629,16 @@ }, { "name": "symfony/filesystem", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "040651db13cf061827a460cc10f6e36a445c45b4" + "reference": "c709670bf64721202ddbe4162846f250735842c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/040651db13cf061827a460cc10f6e36a445c45b4", - "reference": "040651db13cf061827a460cc10f6e36a445c45b4", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", + "reference": "c709670bf64721202ddbe4162846f250735842c0", "shasum": "" }, "require": { @@ -4623,7 +4647,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -4650,20 +4674,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-05-28T14:08:56+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3" + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a0105afb670dbd38f521105c444de1b8e10cfe3", - "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", "shasum": "" }, "require": { @@ -4672,7 +4696,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -4699,20 +4723,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2017-04-12T14:14:56+00:00" }, { "name": "symfony/yaml", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6" + "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6", + "url": "https://api.github.com/repos/symfony/yaml/zipball/9752a30000a8ca9f4b34b5227d15d0101b96b063", + "reference": "9752a30000a8ca9f4b34b5227d15d0101b96b063", "shasum": "" }, "require": { @@ -4727,7 +4751,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -4754,7 +4778,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-05-01T14:55:58+00:00" + "time": "2017-06-02T22:05:06+00:00" }, { "name": "webmozart/assert", diff --git a/storage/database/databasecopy.sqlite b/storage/database/databasecopy.sqlite index bc987ad2207260701e9763971faaf72c896e62dd..03c6232934ff356d8404dcb955504ae5ae5f5e9a 100755 GIT binary patch literal 272384 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 literal 258048 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