mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-16 09:22:33 +00:00
@@ -25,7 +25,9 @@ declare(strict_types=1);
|
|||||||
namespace FireflyIII\Console\Commands\Correction;
|
namespace FireflyIII\Console\Commands\Correction;
|
||||||
|
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
|
use FireflyIII\Models\AccountType;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class FixIbans
|
* Class FixIbans
|
||||||
@@ -53,6 +55,52 @@ class FixIbans extends Command
|
|||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$accounts = Account::whereNotNull('iban')->get();
|
$accounts = Account::whereNotNull('iban')->get();
|
||||||
|
$this->filterIbans($accounts);
|
||||||
|
$this->countAndCorrectIbans($accounts);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function countAndCorrectIbans(Collection $accounts): void
|
||||||
|
{
|
||||||
|
$set = [];
|
||||||
|
/** @var Account $account */
|
||||||
|
foreach($accounts as $account) {
|
||||||
|
$userId = (int)$account->user_id;
|
||||||
|
$set[$userId] = $set[$userId] ?? [];
|
||||||
|
$iban = (string)$account->iban;
|
||||||
|
$type = $account->accountType->type;
|
||||||
|
if(in_array($type, [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE])) {
|
||||||
|
$type = 'liabilities';
|
||||||
|
}
|
||||||
|
if(array_key_exists($iban, $set[$userId])) {
|
||||||
|
// iban already in use! two exceptions exist:
|
||||||
|
if(
|
||||||
|
!(AccountType::EXPENSE === $set[$userId][$iban] && AccountType::REVENUE === $type) && // allowed combination
|
||||||
|
!(AccountType::REVENUE === $set[$userId][$iban] && AccountType::EXPENSE === $type) // also allowed combination.
|
||||||
|
){
|
||||||
|
$this->line(sprintf('IBAN "%s" is used more than once and will be removed from %s #%d ("%s")', $iban, $account->accountType->type, $account->id, $account->name));
|
||||||
|
$account->iban = null;
|
||||||
|
$account->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!array_key_exists($iban, $set[$userId])) {
|
||||||
|
$set[$userId][$iban] = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Collection $accounts
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function filterIbans(Collection $accounts): void
|
||||||
|
{
|
||||||
/** @var Account $account */
|
/** @var Account $account */
|
||||||
foreach ($accounts as $account) {
|
foreach ($accounts as $account) {
|
||||||
$iban = $account->iban;
|
$iban = $account->iban;
|
||||||
@@ -65,7 +113,5 @@ class FixIbans extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,18 +23,20 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Rules;
|
namespace FireflyIII\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\AccountType;
|
use FireflyIII\Models\AccountType;
|
||||||
use Illuminate\Contracts\Validation\Rule;
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class UniqueIban
|
* Class UniqueIban
|
||||||
*/
|
*/
|
||||||
class UniqueIban implements Rule
|
class UniqueIban implements ValidationRule
|
||||||
{
|
{
|
||||||
private ?Account $account;
|
private ?Account $account;
|
||||||
private ?string $expectedType;
|
private array $expectedTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new rule instance.
|
* Create a new rule instance.
|
||||||
@@ -46,16 +48,22 @@ class UniqueIban implements Rule
|
|||||||
public function __construct(?Account $account, ?string $expectedType)
|
public function __construct(?Account $account, ?string $expectedType)
|
||||||
{
|
{
|
||||||
$this->account = $account;
|
$this->account = $account;
|
||||||
$this->expectedType = $expectedType;
|
if(null === $expectedType){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->expectedTypes = [$expectedType];
|
||||||
// a very basic fix to make sure we get the correct account type:
|
// a very basic fix to make sure we get the correct account type:
|
||||||
if ('expense' === $expectedType) {
|
if ('expense' === $expectedType) {
|
||||||
$this->expectedType = AccountType::EXPENSE;
|
$this->expectedTypes = [AccountType::EXPENSE];
|
||||||
}
|
}
|
||||||
if ('revenue' === $expectedType) {
|
if ('revenue' === $expectedType) {
|
||||||
$this->expectedType = AccountType::REVENUE;
|
$this->expectedTypes = [AccountType::REVENUE];
|
||||||
}
|
}
|
||||||
if ('asset' === $expectedType) {
|
if ('asset' === $expectedType) {
|
||||||
$this->expectedType = AccountType::ASSET;
|
$this->expectedTypes = [AccountType::ASSET];
|
||||||
|
}
|
||||||
|
if ('liabilities' === $expectedType) {
|
||||||
|
$this->expectedTypes = [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +92,7 @@ class UniqueIban implements Rule
|
|||||||
if (!auth()->check()) {
|
if (!auth()->check()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (null === $this->expectedType) {
|
if (0 === count($this->expectedTypes)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$maxCounts = $this->getMaxOccurrences();
|
$maxCounts = $this->getMaxOccurrences();
|
||||||
@@ -95,11 +103,11 @@ class UniqueIban implements Rule
|
|||||||
if ($count > $max) {
|
if ($count > $max) {
|
||||||
Log::debug(
|
Log::debug(
|
||||||
sprintf(
|
sprintf(
|
||||||
'IBAN "%s" is in use with %d account(s) of type "%s", which is too much for expected type "%s"',
|
'IBAN "%s" is in use with %d account(s) of type "%s", which is too much for expected types "%s"',
|
||||||
$value,
|
$value,
|
||||||
$count,
|
$count,
|
||||||
$type,
|
$type,
|
||||||
$this->expectedType
|
join(', ', $this->expectedTypes)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -120,14 +128,15 @@ class UniqueIban implements Rule
|
|||||||
AccountType::ASSET => 0,
|
AccountType::ASSET => 0,
|
||||||
AccountType::EXPENSE => 0,
|
AccountType::EXPENSE => 0,
|
||||||
AccountType::REVENUE => 0,
|
AccountType::REVENUE => 0,
|
||||||
|
'liabilities' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ('expense' === $this->expectedType || AccountType::EXPENSE === $this->expectedType) {
|
if (in_array('expense',$this->expectedTypes,true) ||in_array(AccountType::EXPENSE, $this->expectedTypes, true)) {
|
||||||
// IBAN should be unique amongst expense and asset accounts.
|
// IBAN should be unique amongst expense and asset accounts.
|
||||||
// may appear once in revenue accounts
|
// may appear once in revenue accounts
|
||||||
$maxCounts[AccountType::REVENUE] = 1;
|
$maxCounts[AccountType::REVENUE] = 1;
|
||||||
}
|
}
|
||||||
if ('revenue' === $this->expectedType || AccountType::REVENUE === $this->expectedType) {
|
if (in_array('revenue', $this->expectedTypes, true) || in_array(AccountType::REVENUE, $this->expectedTypes, true)) {
|
||||||
// IBAN should be unique amongst revenue and asset accounts.
|
// IBAN should be unique amongst revenue and asset accounts.
|
||||||
// may appear once in expense accounts
|
// may appear once in expense accounts
|
||||||
$maxCounts[AccountType::EXPENSE] = 1;
|
$maxCounts[AccountType::EXPENSE] = 1;
|
||||||
@@ -144,12 +153,16 @@ class UniqueIban implements Rule
|
|||||||
*/
|
*/
|
||||||
private function countHits(string $type, string $iban): int
|
private function countHits(string $type, string $iban): int
|
||||||
{
|
{
|
||||||
|
$typesArray = [$type];
|
||||||
|
if('liabilities' === $type) {
|
||||||
|
$typesArray = [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
|
||||||
|
}
|
||||||
$query
|
$query
|
||||||
= auth()->user()
|
= auth()->user()
|
||||||
->accounts()
|
->accounts()
|
||||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||||
->where('accounts.iban', $iban)
|
->where('accounts.iban', $iban)
|
||||||
->where('account_types.type', $type);
|
->whereIn('account_types.type', $typesArray);
|
||||||
|
|
||||||
if (null !== $this->account) {
|
if (null !== $this->account) {
|
||||||
$query->where('accounts.id', '!=', $this->account->id);
|
$query->where('accounts.id', '!=', $this->account->id);
|
||||||
@@ -157,4 +170,14 @@ class UniqueIban implements Rule
|
|||||||
|
|
||||||
return $query->count();
|
return $query->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
if(!$this->passes($attribute, $value)) {
|
||||||
|
$fail((string)trans('validation.unique_iban_for_user'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,9 +18,6 @@
|
|||||||
.skin-firefly-iii .ti-new-tag-input {
|
.skin-firefly-iii .ti-new-tag-input {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
.skin-firefly-iii input {
|
|
||||||
background: #ecf0f5 !important;
|
|
||||||
}
|
|
||||||
.skin-firefly-iii .main-header .navbar {
|
.skin-firefly-iii .main-header .navbar {
|
||||||
background-color: #3c8dbc;
|
background-color: #3c8dbc;
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
.skin-firefly-iii .money-neutral{color:#999}.skin-firefly-iii .money-positive{color:#3c763d}.skin-firefly-iii .money-negative{color:#a94442}.skin-firefly-iii .money-transfer{color:#31708f}.skin-firefly-iii .ti-new-tag-input{background:#fff}.skin-firefly-iii input{background:#ecf0f5 !important}.skin-firefly-iii .main-header .navbar{background-color:#3c8dbc}.skin-firefly-iii .main-header .navbar .nav>li>a{color:#fff}.skin-firefly-iii .main-header .navbar .nav>li>a:hover,.skin-firefly-iii .main-header .navbar .nav>li>a:active,.skin-firefly-iii .main-header .navbar .nav>li>a:focus,.skin-firefly-iii .main-header .navbar .nav .open>a,.skin-firefly-iii .main-header .navbar .nav .open>a:hover,.skin-firefly-iii .main-header .navbar .nav .open>a:focus,.skin-firefly-iii .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-firefly-iii .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-firefly-iii .main-header .navbar .dropdown-menu li a{color:#fff}.skin-firefly-iii .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-firefly-iii .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-firefly-iii .main-header .logo:hover{background-color:#3b8ab8}.skin-firefly-iii .main-header li.user-header{background-color:#3c8dbc}.skin-firefly-iii .content-header{background:transparent}.skin-firefly-iii .wrapper,.skin-firefly-iii .main-sidebar,.skin-firefly-iii .left-side{background-color:#f9fafc}.skin-firefly-iii .main-sidebar{border-right:1px solid #d2d6de}.skin-firefly-iii .user-panel>.info,.skin-firefly-iii .user-panel>.info>a{color:#444}.skin-firefly-iii .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-firefly-iii .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-firefly-iii .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-firefly-iii .sidebar-menu>li:hover>a,.skin-firefly-iii .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-firefly-iii .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-firefly-iii .sidebar-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-firefly-iii .sidebar a{color:#444}.skin-firefly-iii .sidebar a:hover{text-decoration:none}.skin-firefly-iii .sidebar-menu .treeview-menu>li>a{color:#777}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a,.skin-firefly-iii .sidebar-menu .treeview-menu>li>a:hover{color:#000}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-firefly-iii .sidebar-form input[type="text"],.skin-firefly-iii .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-firefly-iii .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-firefly-iii .sidebar-form input[type="text"]:focus,.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-firefly-iii .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-firefly-iii.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-firefly-iii .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}
|
.skin-firefly-iii .money-neutral{color:#999}.skin-firefly-iii .money-positive{color:#3c763d}.skin-firefly-iii .money-negative{color:#a94442}.skin-firefly-iii .money-transfer{color:#31708f}.skin-firefly-iii .ti-new-tag-input{background:#fff}.skin-firefly-iii .main-header .navbar{background-color:#3c8dbc}.skin-firefly-iii .main-header .navbar .nav>li>a{color:#fff}.skin-firefly-iii .main-header .navbar .nav>li>a:hover,.skin-firefly-iii .main-header .navbar .nav>li>a:active,.skin-firefly-iii .main-header .navbar .nav>li>a:focus,.skin-firefly-iii .main-header .navbar .nav .open>a,.skin-firefly-iii .main-header .navbar .nav .open>a:hover,.skin-firefly-iii .main-header .navbar .nav .open>a:focus,.skin-firefly-iii .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-firefly-iii .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-firefly-iii .main-header .navbar .dropdown-menu li a{color:#fff}.skin-firefly-iii .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-firefly-iii .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-firefly-iii .main-header .logo:hover{background-color:#3b8ab8}.skin-firefly-iii .main-header li.user-header{background-color:#3c8dbc}.skin-firefly-iii .content-header{background:transparent}.skin-firefly-iii .wrapper,.skin-firefly-iii .main-sidebar,.skin-firefly-iii .left-side{background-color:#f9fafc}.skin-firefly-iii .main-sidebar{border-right:1px solid #d2d6de}.skin-firefly-iii .user-panel>.info,.skin-firefly-iii .user-panel>.info>a{color:#444}.skin-firefly-iii .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-firefly-iii .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-firefly-iii .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-firefly-iii .sidebar-menu>li:hover>a,.skin-firefly-iii .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-firefly-iii .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-firefly-iii .sidebar-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-firefly-iii .sidebar a{color:#444}.skin-firefly-iii .sidebar a:hover{text-decoration:none}.skin-firefly-iii .sidebar-menu .treeview-menu>li>a{color:#777}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a,.skin-firefly-iii .sidebar-menu .treeview-menu>li>a:hover{color:#000}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-firefly-iii .sidebar-form input[type="text"],.skin-firefly-iii .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-firefly-iii .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-firefly-iii .sidebar-form input[type="text"]:focus,.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-firefly-iii .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-firefly-iii.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-firefly-iii .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}
|
Reference in New Issue
Block a user