2023-03-25 11:32:33 +01:00
< ? php
2023-04-01 07:50:53 +02:00
2023-03-25 11:32:33 +01:00
/*
* AccountRepository . php
* Copyright ( c ) 2023 james @ firefly - iii . org
*
* This file is part of Firefly III ( https :// github . com / firefly - iii ) .
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*/
2023-07-15 16:02:42 +02:00
declare ( strict_types = 1 );
2023-09-21 15:50:49 +02:00
namespace FireflyIII\Repositories\UserGroups\Account ;
2023-03-25 11:32:33 +01:00
2025-01-03 09:15:52 +01:00
use FireflyIII\Enums\AccountTypeEnum ;
2023-07-25 09:01:44 +02:00
use FireflyIII\Models\Account ;
use FireflyIII\Models\AccountMeta ;
use FireflyIII\Models\AccountType ;
2024-04-28 14:40:22 +02:00
use FireflyIII\Models\ObjectGroup ;
2024-04-20 16:18:41 +02:00
use FireflyIII\Models\Transaction ;
2023-07-25 09:01:44 +02:00
use FireflyIII\Models\TransactionCurrency ;
2024-03-16 22:00:25 +01:00
use FireflyIII\Services\Internal\Update\AccountUpdateService ;
2025-09-07 14:49:49 +02:00
use FireflyIII\Support\Facades\Amount ;
2024-05-18 06:42:09 +02:00
use FireflyIII\Support\Facades\Steam ;
2023-09-21 15:50:49 +02:00
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait ;
2023-11-28 17:18:31 +01:00
use Illuminate\Database\Eloquent\Builder as EloquentBuilder ;
2023-03-25 11:32:33 +01:00
use Illuminate\Support\Collection ;
2024-04-20 16:18:41 +02:00
use Illuminate\Support\Facades\DB ;
2025-05-27 16:57:36 +02:00
use Override ;
use stdClass ;
2023-03-25 11:32:33 +01:00
2025-05-27 17:06:15 +02:00
use function Safe\json_encode ;
2023-03-25 11:32:33 +01:00
/**
* Class AccountRepository
2025-02-23 12:27:34 +01:00
*
* @ deprecated
2023-03-25 11:32:33 +01:00
*/
class AccountRepository implements AccountRepositoryInterface
{
2023-09-21 15:50:49 +02:00
use UserGroupTrait ;
2023-03-25 11:32:33 +01:00
2025-05-27 16:57:36 +02:00
#[Override]
2024-03-06 07:16:01 +01:00
public function countAccounts ( array $types ) : int
{
$query = $this -> userGroup -> accounts ();
if ( 0 !== count ( $types )) {
$query -> accountTypeIn ( $types );
}
return $query -> count ();
}
2023-12-10 06:51:59 +01:00
public function findByAccountNumber ( string $number , array $types ) : ? Account
{
$dbQuery = $this -> userGroup
-> accounts ()
-> leftJoin ( 'account_meta' , 'accounts.id' , '=' , 'account_meta.account_id' )
-> where ( 'accounts.active' , true )
-> where (
2025-01-04 19:43:58 +01:00
static function ( EloquentBuilder $q1 ) use ( $number ) : void {
2025-05-27 17:06:15 +02:00
$json = json_encode ( $number );
2023-12-10 06:51:59 +01:00
$q1 -> where ( 'account_meta.name' , '=' , 'account_number' );
$q1 -> where ( 'account_meta.data' , '=' , $json );
}
2025-03-14 17:45:16 +01:00
)
;
2023-12-10 06:51:59 +01:00
if ( 0 !== count ( $types )) {
$dbQuery -> leftJoin ( 'account_types' , 'accounts.account_type_id' , '=' , 'account_types.id' );
$dbQuery -> whereIn ( 'account_types.type' , $types );
}
2023-12-20 19:35:52 +01:00
2025-01-04 07:39:16 +01:00
/** @var null|Account */
2023-12-10 06:51:59 +01:00
return $dbQuery -> first ([ 'accounts.*' ]);
}
public function findByIbanNull ( string $iban , array $types ) : ? Account
{
2024-05-18 06:49:29 +02:00
$iban = Steam :: filterSpaces ( $iban );
2023-12-10 06:51:59 +01:00
$query = $this -> userGroup -> accounts () -> where ( 'iban' , '!=' , '' ) -> whereNotNull ( 'iban' );
if ( 0 !== count ( $types )) {
$query -> leftJoin ( 'account_types' , 'accounts.account_type_id' , '=' , 'account_types.id' );
$query -> whereIn ( 'account_types.type' , $types );
}
2025-01-04 07:39:16 +01:00
/** @var null|Account */
2023-12-10 06:51:59 +01:00
return $query -> where ( 'iban' , $iban ) -> first ([ 'accounts.*' ]);
}
2023-09-23 07:15:41 +02:00
public function findByName ( string $name , array $types ) : ? Account
{
2025-03-14 17:45:16 +01:00
$query = $this -> userGroup -> accounts ();
2023-09-23 07:15:41 +02:00
if ( 0 !== count ( $types )) {
$query -> leftJoin ( 'account_types' , 'accounts.account_type_id' , '=' , 'account_types.id' );
$query -> whereIn ( 'account_types.type' , $types );
}
2023-10-29 06:33:43 +01:00
app ( 'log' ) -> debug ( sprintf ( 'Searching for account named "%s" (of user #%d) of the following type(s)' , $name , $this -> user -> id ), [ 'types' => $types ]);
2023-09-23 07:15:41 +02:00
$query -> where ( 'accounts.name' , $name );
2023-12-20 19:35:52 +01:00
/** @var null|Account $account */
2023-09-23 07:15:41 +02:00
$account = $query -> first ([ 'accounts.*' ]);
if ( null === $account ) {
2023-10-29 06:33:43 +01:00
app ( 'log' ) -> debug ( sprintf ( 'There is no account with name "%s" of types' , $name ), $types );
2023-09-23 07:15:41 +02:00
return null ;
}
2023-10-29 06:33:43 +01:00
app ( 'log' ) -> debug ( sprintf ( 'Found #%d (%s) with type id %d' , $account -> id , $account -> name , $account -> account_type_id ));
2023-09-23 07:15:41 +02:00
return $account ;
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-12-22 08:43:12 +01:00
public function getAccountBalances ( Account $account ) : Collection
{
return $account -> accountBalances ;
}
2023-08-01 09:27:39 +02:00
public function getAccountCurrency ( Account $account ) : ? TransactionCurrency
2023-07-17 20:33:26 +02:00
{
2025-03-14 17:45:16 +01:00
$type = $account -> accountType -> type ;
$list = config ( 'firefly.valid_currency_account_types' );
2023-08-01 09:27:39 +02:00
// return null if not in this list.
if ( ! in_array ( $type , $list , true )) {
return null ;
2023-03-25 11:32:33 +01:00
}
2024-04-20 16:18:41 +02:00
$currencyId = ( int ) $this -> getMetaValue ( $account , 'currency_id' );
2023-08-01 09:27:39 +02:00
if ( $currencyId > 0 ) {
2025-09-07 14:49:49 +02:00
return Amount :: getTransactionCurrencyById ( $currencyId );
2023-03-25 11:32:33 +01:00
}
2023-08-01 09:27:39 +02:00
return null ;
2023-03-25 11:32:33 +01:00
}
2023-07-25 09:01:44 +02:00
/**
2023-08-01 09:27:39 +02:00
* Return meta value for account . Null if not found .
2023-07-25 09:01:44 +02:00
*/
2023-08-01 09:27:39 +02:00
public function getMetaValue ( Account $account , string $field ) : ? string
2023-07-25 09:01:44 +02:00
{
2023-08-01 09:27:39 +02:00
$result = $account -> accountMeta -> filter (
2025-05-04 13:55:42 +02:00
static fn ( AccountMeta $meta ) => strtolower ( $meta -> name ) === strtolower ( $field )
2023-08-01 09:27:39 +02:00
);
if ( 0 === $result -> count ()) {
return null ;
}
if ( 1 === $result -> count ()) {
2024-04-20 16:18:41 +02:00
return ( string ) $result -> first () -> data ;
2023-07-25 09:01:44 +02:00
}
2023-08-01 09:27:39 +02:00
return null ;
}
public function find ( int $accountId ) : ? Account
{
$account = $this -> user -> accounts () -> find ( $accountId );
if ( null === $account ) {
2025-09-07 07:31:00 +02:00
/** @var null|Account */
2025-05-27 17:06:15 +02:00
return $this -> userGroup -> accounts () -> find ( $accountId );
2023-07-25 09:01:44 +02:00
}
2025-01-04 07:39:16 +01:00
/** @var null|Account */
2023-08-01 09:27:39 +02:00
return $account ;
2023-07-25 09:01:44 +02:00
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-12-22 08:43:12 +01:00
public function getAccountTypes ( Collection $accounts ) : Collection
{
return AccountType :: leftJoin ( 'accounts' , 'accounts.account_type_id' , '=' , 'account_types.id' )
2025-03-14 17:45:16 +01:00
-> whereIn ( 'accounts.id' , $accounts -> pluck ( 'id' ) -> toArray ())
-> get ([ 'accounts.id' , 'account_types.type' ])
;
2024-12-22 08:43:12 +01:00
}
2023-07-25 09:01:44 +02:00
public function getAccountsById ( array $accountIds ) : Collection
{
$query = $this -> userGroup -> accounts ();
if ( 0 !== count ( $accountIds )) {
$query -> whereIn ( 'accounts.id' , $accountIds );
}
$query -> orderBy ( 'accounts.order' , 'ASC' );
$query -> orderBy ( 'accounts.active' , 'DESC' );
$query -> orderBy ( 'accounts.name' , 'ASC' );
return $query -> get ([ 'accounts.*' ]);
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-03-06 07:16:01 +01:00
public function getAccountsInOrder ( array $types , array $sort , int $startRow , int $endRow ) : Collection
2023-07-25 09:01:44 +02:00
{
2023-08-01 09:27:39 +02:00
$query = $this -> userGroup -> accounts ();
if ( 0 !== count ( $types )) {
$query -> accountTypeIn ( $types );
2023-07-25 09:01:44 +02:00
}
2024-03-06 07:16:01 +01:00
$query -> skip ( $startRow );
$query -> take ( $endRow - $startRow );
2023-08-01 09:27:39 +02:00
// add sort parameters. At this point they're filtered to allowed fields to sort by:
if ( 0 !== count ( $sort )) {
2024-03-06 07:16:01 +01:00
foreach ( $sort as $label => $direction ) {
$query -> orderBy ( sprintf ( 'accounts.%s' , $label ), $direction );
2023-08-01 09:27:39 +02:00
}
2023-07-25 09:01:44 +02:00
}
2023-08-01 09:27:39 +02:00
if ( 0 === count ( $sort )) {
2024-03-06 07:16:01 +01:00
$query -> orderBy ( 'accounts.order' , 'ASC' );
2023-08-01 09:27:39 +02:00
$query -> orderBy ( 'accounts.active' , 'DESC' );
$query -> orderBy ( 'accounts.name' , 'ASC' );
}
2023-12-20 19:35:52 +01:00
2023-08-01 09:27:39 +02:00
return $query -> get ([ 'accounts.*' ]);
2023-07-25 09:01:44 +02:00
}
2023-08-06 11:22:36 +02:00
public function getActiveAccountsByType ( array $types ) : Collection
{
$query = $this -> userGroup -> accounts ();
if ( 0 !== count ( $types )) {
$query -> accountTypeIn ( $types );
}
$query -> where ( 'active' , true );
$query -> orderBy ( 'accounts.account_type_id' , 'ASC' );
$query -> orderBy ( 'accounts.order' , 'ASC' );
$query -> orderBy ( 'accounts.name' , 'ASC' );
return $query -> get ([ 'accounts.*' ]);
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-12-22 08:43:12 +01:00
public function getLastActivity ( Collection $accounts ) : array
{
2025-09-07 07:31:00 +02:00
return Transaction :: whereIn ( 'account_id' , $accounts -> pluck ( 'id' ) -> toArray ()) -> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , 'transactions.transaction_journal_id' ) -> groupBy ( 'transactions.account_id' ) -> get ([ 'transactions.account_id' , DB :: raw ( 'MAX(transaction_journals.date) as date_max' )]) -> toArray ();
2024-12-22 08:43:12 +01:00
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-12-22 08:43:12 +01:00
public function getMetaValues ( Collection $accounts , array $fields ) : Collection
{
$query = AccountMeta :: whereIn ( 'account_id' , $accounts -> pluck ( 'id' ) -> toArray ());
if ( count ( $fields ) > 0 ) {
$query -> whereIn ( 'name' , $fields );
}
return $query -> get ([ 'account_meta.id' , 'account_meta.account_id' , 'account_meta.name' , 'account_meta.data' ]);
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-12-22 08:43:12 +01:00
public function getObjectGroups ( Collection $accounts ) : array
{
$groupIds = [];
$return = [];
$set = DB :: table ( 'object_groupables' ) -> where ( 'object_groupable_type' , Account :: class )
2025-03-14 17:45:16 +01:00
-> whereIn ( 'object_groupable_id' , $accounts -> pluck ( 'id' ) -> toArray ()) -> get ()
;
2024-12-22 08:43:12 +01:00
2025-05-27 16:57:36 +02:00
/** @var stdClass $row */
2024-12-22 08:43:12 +01:00
foreach ( $set as $row ) {
$groupIds [] = $row -> object_group_id ;
}
$groupIds = array_unique ( $groupIds );
$groups = ObjectGroup :: whereIn ( 'id' , $groupIds ) -> get ();
2025-05-27 16:57:36 +02:00
/** @var stdClass $row */
2024-12-22 08:43:12 +01:00
foreach ( $set as $row ) {
if ( ! array_key_exists ( $row -> object_groupable_id , $return )) {
/** @var null|ObjectGroup $group */
$group = $groups -> firstWhere ( 'id' , '=' , $row -> object_group_id );
if ( null !== $group ) {
$return [ $row -> object_groupable_id ] = [ 'title' => $group -> title , 'order' => $group -> order , 'id' => $group -> id ];
}
}
}
return $return ;
}
2024-03-04 20:41:34 +01:00
public function resetAccountOrder () : void
{
$sets = [
2025-01-03 09:15:52 +01:00
[ AccountTypeEnum :: DEFAULT -> value , AccountTypeEnum :: ASSET -> value ],
[ AccountTypeEnum :: LOAN -> value , AccountTypeEnum :: DEBT -> value , AccountTypeEnum :: CREDITCARD -> value , AccountTypeEnum :: MORTGAGE -> value ],
2024-03-04 20:41:34 +01:00
];
foreach ( $sets as $set ) {
$list = $this -> getAccountsByType ( $set );
$index = 1 ;
foreach ( $list as $account ) {
if ( false === $account -> active ) {
$account -> order = 0 ;
continue ;
}
2024-04-20 16:18:41 +02:00
if ( $index !== ( int ) $account -> order ) {
2024-03-04 20:41:34 +01:00
app ( 'log' ) -> debug ( sprintf ( 'Account #%d ("%s"): order should %d be but is %d.' , $account -> id , $account -> name , $index , $account -> order ));
$account -> order = $index ;
$account -> save ();
}
++ $index ;
}
}
2024-07-26 03:57:35 +02:00
// reset the rest to zero.
2025-03-14 17:45:16 +01:00
$all = [ AccountTypeEnum :: DEFAULT -> value , AccountTypeEnum :: ASSET -> value , AccountTypeEnum :: LOAN -> value , AccountTypeEnum :: DEBT -> value , AccountTypeEnum :: CREDITCARD -> value , AccountTypeEnum :: MORTGAGE -> value ];
2024-07-29 05:06:54 +02:00
$this -> user -> accounts () -> leftJoin ( 'account_types' , 'account_types.id' , '=' , 'accounts.account_type_id' )
2025-03-14 17:45:16 +01:00
-> whereNotIn ( 'account_types.type' , $all )
-> update ([ 'order' => 0 ])
;
2024-03-04 20:41:34 +01:00
}
2024-04-28 09:54:28 +02:00
public function getAccountsByType ( array $types , ? array $sort = [], ? array $filters = []) : Collection
2024-03-04 20:41:34 +01:00
{
2025-03-14 17:45:16 +01:00
$sortable = [ 'name' , 'active' ]; // TODO yes this is a duplicate array.
$res = array_intersect ([ AccountTypeEnum :: ASSET -> value , AccountTypeEnum :: MORTGAGE -> value , AccountTypeEnum :: LOAN -> value , AccountTypeEnum :: DEBT -> value ], $types );
$query = $this -> userGroup -> accounts ();
2024-03-04 20:41:34 +01:00
if ( 0 !== count ( $types )) {
$query -> accountTypeIn ( $types );
}
2024-04-28 09:54:28 +02:00
// process filters
// TODO this should be repeatable, it feels like a hack when you do it here.
2024-04-28 13:30:42 +02:00
// TODO some fields cannot be filtered using the query, and a second filter must be applied on the collection.
2024-04-28 14:40:22 +02:00
foreach ( $filters as $column => $value ) {
2024-04-28 09:54:28 +02:00
// filter on NULL values
2024-04-28 14:40:22 +02:00
if ( null === $value ) {
2024-04-28 09:54:28 +02:00
continue ;
}
if ( 'active' === $column ) {
$query -> where ( 'accounts.active' , $value );
}
2024-04-28 14:40:22 +02:00
if ( 'name' === $column ) {
2024-10-10 06:30:05 +02:00
$query -> whereLike ( 'accounts.name' , sprintf ( '%%%s%%' , $value ));
2024-04-28 13:30:42 +02:00
}
2024-04-28 09:54:28 +02:00
}
2024-03-04 20:41:34 +01:00
// add sort parameters. At this point they're filtered to allowed fields to sort by:
2024-03-24 11:08:24 +01:00
$hasActiveColumn = array_key_exists ( 'active' , $sort );
2024-03-09 19:31:27 +01:00
if ( count ( $sort ) > 0 ) {
2024-03-24 11:08:24 +01:00
if ( false === $hasActiveColumn ) {
$query -> orderBy ( 'accounts.active' , 'DESC' );
}
2024-03-09 19:31:27 +01:00
foreach ( $sort as $column => $direction ) {
if ( in_array ( $column , $sortable , true )) {
$query -> orderBy ( sprintf ( 'accounts.%s' , $column ), $direction );
}
2024-03-04 20:41:34 +01:00
}
}
if ( 0 === count ( $sort )) {
2024-03-06 07:16:01 +01:00
if ( 0 !== count ( $res )) {
2024-03-24 11:08:24 +01:00
$query -> orderBy ( 'accounts.active' , 'DESC' );
2024-03-06 07:16:01 +01:00
}
2024-03-24 11:08:24 +01:00
$query -> orderBy ( 'accounts.order' , 'ASC' );
2024-03-04 20:41:34 +01:00
$query -> orderBy ( 'accounts.name' , 'ASC' );
2025-02-19 06:21:27 +01:00
$query -> orderBy ( 'accounts.account_type_id' , 'ASC' );
$query -> orderBy ( 'accounts.id' , 'ASC' );
2024-03-04 20:41:34 +01:00
}
return $query -> get ([ 'accounts.*' ]);
}
2025-05-27 16:57:36 +02:00
#[Override]
2024-12-22 08:43:12 +01:00
public function update ( Account $account , array $data ) : Account
{
/** @var AccountUpdateService $service */
$service = app ( AccountUpdateService :: class );
return $service -> update ( $account , $data );
}
2024-10-20 10:16:54 +02:00
public function searchAccount ( string $query , array $types , int $page , int $limit ) : Collection
2024-03-04 20:41:34 +01:00
{
2024-03-06 07:16:01 +01:00
// search by group, not by user
$dbQuery = $this -> userGroup -> accounts ()
2025-03-14 17:45:16 +01:00
-> where ( 'active' , true )
-> orderBy ( 'accounts.updated_at' , 'ASC' )
-> orderBy ( 'accounts.order' , 'ASC' )
-> orderBy ( 'accounts.account_type_id' , 'ASC' )
-> orderBy ( 'accounts.name' , 'ASC' )
-> with ([ 'accountType' ])
;
2024-10-20 10:16:54 +02:00
// split query on spaces just in case:
2024-10-21 05:15:16 +02:00
if ( '' !== trim ( $query )) {
2024-05-19 10:26:25 +02:00
$dbQuery -> where ( function ( EloquentBuilder $q ) use ( $query ) : void {
2024-10-20 10:16:54 +02:00
$parts = explode ( ' ' , $query );
foreach ( $parts as $part ) {
$search = sprintf ( '%%%s%%' , $part );
$q -> orWhereLike ( 'name' , $search );
2024-05-19 06:36:31 +02:00
}
});
2024-03-06 07:16:01 +01:00
}
2024-10-20 10:16:54 +02:00
2024-03-04 20:41:34 +01:00
if ( 0 !== count ( $types )) {
2024-03-06 07:16:01 +01:00
$dbQuery -> leftJoin ( 'account_types' , 'accounts.account_type_id' , '=' , 'account_types.id' );
$dbQuery -> whereIn ( 'account_types.type' , $types );
2024-03-04 20:41:34 +01:00
}
2024-03-05 19:38:45 +01:00
2024-10-21 05:15:16 +02:00
$dbQuery -> skip (( $page - 1 ) * $limit ) -> take ( $limit );
2024-10-20 10:16:54 +02:00
return $dbQuery -> get ([ 'accounts.*' ]);
2024-03-04 20:41:34 +01:00
}
2023-03-25 11:32:33 +01:00
}