This commit is contained in:
James Cole 2023-04-28 09:06:05 +02:00
parent 222387adba
commit c2e1e25489
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
4 changed files with 84 additions and 18 deletions

View File

@ -25,7 +25,9 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
/**
* Class FixIbans
@ -53,6 +55,52 @@ class FixIbans extends Command
public function handle(): int
{
$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 */
foreach ($accounts as $account) {
$iban = $account->iban;
@ -65,7 +113,5 @@ class FixIbans extends Command
}
}
}
return 0;
}
}

View File

@ -23,18 +23,20 @@ declare(strict_types=1);
namespace FireflyIII\Rules;
use Closure;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Log;
/**
* Class UniqueIban
*/
class UniqueIban implements Rule
class UniqueIban implements ValidationRule
{
private ?Account $account;
private ?string $expectedType;
private array $expectedTypes;
/**
* Create a new rule instance.
@ -46,16 +48,22 @@ class UniqueIban implements Rule
public function __construct(?Account $account, ?string $expectedType)
{
$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:
if ('expense' === $expectedType) {
$this->expectedType = AccountType::EXPENSE;
$this->expectedTypes = [AccountType::EXPENSE];
}
if ('revenue' === $expectedType) {
$this->expectedType = AccountType::REVENUE;
$this->expectedTypes = [AccountType::REVENUE];
}
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()) {
return true;
}
if (null === $this->expectedType) {
if (0 === count($this->expectedTypes)) {
return true;
}
$maxCounts = $this->getMaxOccurrences();
@ -95,11 +103,11 @@ class UniqueIban implements Rule
if ($count > $max) {
Log::debug(
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,
$count,
$type,
$this->expectedType
join(', ', $this->expectedTypes)
)
);
@ -120,14 +128,15 @@ class UniqueIban implements Rule
AccountType::ASSET => 0,
AccountType::EXPENSE => 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.
// may appear once in revenue accounts
$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.
// may appear once in expense accounts
$maxCounts[AccountType::EXPENSE] = 1;
@ -144,12 +153,16 @@ class UniqueIban implements Rule
*/
private function countHits(string $type, string $iban): int
{
$typesArray = [$type];
if('liabilities' === $type) {
$typesArray = [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
}
$query
= auth()->user()
->accounts()
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->where('accounts.iban', $iban)
->where('account_types.type', $type);
->whereIn('account_types.type', $typesArray);
if (null !== $this->account) {
$query->where('accounts.id', '!=', $this->account->id);
@ -157,4 +170,14 @@ class UniqueIban implements Rule
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'));
}
}
}

View File

@ -18,9 +18,6 @@
.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;
}

View File

@ -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}