mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-29 09:16:15 +00:00
Improve factories and tests.
This commit is contained in:
@@ -35,6 +35,7 @@ use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\NullArrayObject;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
@@ -61,8 +62,6 @@ class TransactionFactory
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
}
|
||||
|
||||
//use TransactionServiceTrait;
|
||||
|
||||
/**
|
||||
* @param Account $account
|
||||
* @param TransactionCurrency $currency
|
||||
@@ -72,19 +71,23 @@ class TransactionFactory
|
||||
*/
|
||||
public function create(Account $account, TransactionCurrency $currency, string $amount): ?Transaction
|
||||
{
|
||||
$result = Transaction::create(
|
||||
[
|
||||
'reconciled' => false,
|
||||
'account_id' => $account->id,
|
||||
'transaction_journal_id' => $this->journal->id,
|
||||
'description' => null,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'amount' => $amount,
|
||||
'foreign_amount' => null,
|
||||
'foreign_currency_id' => null,
|
||||
'identifier' => 0,
|
||||
]
|
||||
);
|
||||
$result = null;
|
||||
$data = [
|
||||
'reconciled' => false,
|
||||
'account_id' => $account->id,
|
||||
'transaction_journal_id' => $this->journal->id,
|
||||
'description' => null,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'amount' => $amount,
|
||||
'foreign_amount' => null,
|
||||
'foreign_currency_id' => null,
|
||||
'identifier' => 0,
|
||||
];
|
||||
try {
|
||||
$result = Transaction::create($data);
|
||||
} catch (QueryException $e) {
|
||||
Log::error(sprintf('Could not create transaction: %s', $e->getMessage()), $data);
|
||||
}
|
||||
if (null !== $result) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
@@ -107,11 +110,14 @@ class TransactionFactory
|
||||
*/
|
||||
public function createPair(NullArrayObject $data, TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): Collection
|
||||
{
|
||||
$sourceAccount = $this->getAccount('source', $data['source'], $data['source_id'], $data['source_name']);
|
||||
$destinationAccount = $this->getAccount('destination', $data['destination'], $data['destination_id'], $data['destination_name']);
|
||||
$sourceAccount = $this->getAccount('source', $data['source'], (int)$data['source_id'], $data['source_name']);
|
||||
$destinationAccount = $this->getAccount('destination', $data['destination'], (int)$data['destination_id'], $data['destination_name']);
|
||||
$amount = $this->getAmount($data['amount']);
|
||||
$foreignAmount = $this->getForeignAmount($data['foreign_amount']);
|
||||
|
||||
$this->makeDramaOverAccountTypes($sourceAccount, $destinationAccount);
|
||||
|
||||
|
||||
$one = $this->create($sourceAccount, $currency, app('steam')->negative($amount));
|
||||
$two = $this->create($destinationAccount, $currency, app('steam')->positive($amount));
|
||||
|
||||
@@ -134,24 +140,6 @@ class TransactionFactory
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*/
|
||||
public function setJournal(TransactionJournal $journal): void
|
||||
{
|
||||
$this->journal = $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $direction
|
||||
* @param Account|null $source
|
||||
@@ -161,7 +149,7 @@ class TransactionFactory
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getAccount(string $direction, ?Account $source, ?int $sourceId, ?string $sourceName): Account
|
||||
public function getAccount(string $direction, ?Account $source, ?int $sourceId, ?string $sourceName): Account
|
||||
{
|
||||
Log::debug(sprintf('Now in getAccount(%s)', $direction));
|
||||
Log::debug(sprintf('Parameters: ((account), %s, %s)', var_export($sourceId, true), var_export($sourceName, true)));
|
||||
@@ -169,21 +157,21 @@ class TransactionFactory
|
||||
$array = [
|
||||
'source' => [
|
||||
TransactionType::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionType::DEPOSIT => [AccountType::REVENUE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionType::DEPOSIT => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,
|
||||
AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION],
|
||||
TransactionType::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionType::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
|
||||
AccountType::MORTGAGE],
|
||||
TransactionType::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
|
||||
AccountType::MORTGAGE],
|
||||
TransactionType::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET],
|
||||
],
|
||||
'destination' => [
|
||||
TransactionType::WITHDRAWAL => [AccountType::EXPENSE, AccountType::ASSET],
|
||||
TransactionType::WITHDRAWAL => [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT,
|
||||
AccountType::MORTGAGE],
|
||||
TransactionType::DEPOSIT => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionType::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
TransactionType::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
|
||||
AccountType::MORTGAGE],
|
||||
TransactionType::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT,
|
||||
AccountType::MORTGAGE],
|
||||
TransactionType::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET],
|
||||
],
|
||||
];
|
||||
$expectedTypes = $array[$direction];
|
||||
@@ -208,12 +196,10 @@ class TransactionFactory
|
||||
// second attempt, find by ID.
|
||||
if (null !== $sourceId) {
|
||||
$source = $this->accountRepository->findNull($sourceId);
|
||||
if (null !== $source) {
|
||||
Log::debug(sprintf('Found account #%d ("%s" of type "%s") based on #%d.', $source->id, $source->name, $source->accountType->type, $sourceId));
|
||||
}
|
||||
|
||||
if (null !== $source && \in_array($source->accountType->type, $expectedTypes[$transactionType], true)) {
|
||||
Log::debug(sprintf('Found "account_id" object for %s: #%d, %s', $direction, $source->id, $source->name));
|
||||
Log::debug(
|
||||
sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $source->id, $source->name, $source->accountType->type)
|
||||
);
|
||||
|
||||
return $source;
|
||||
}
|
||||
@@ -232,6 +218,9 @@ class TransactionFactory
|
||||
return $source;
|
||||
}
|
||||
}
|
||||
if (null === $sourceName && \in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) {
|
||||
return $this->accountRepository->getCashAccount();
|
||||
}
|
||||
$sourceName = $sourceName ?? '(no name)';
|
||||
// final attempt, create it.
|
||||
$preferredType = $expectedTypes[$transactionType][0];
|
||||
@@ -256,7 +245,7 @@ class TransactionFactory
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getAmount(string $amount): string
|
||||
public function getAmount(string $amount): string
|
||||
{
|
||||
if ('' === $amount) {
|
||||
throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount));
|
||||
@@ -273,7 +262,7 @@ class TransactionFactory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getForeignAmount(?string $amount): ?string
|
||||
public function getForeignAmount(?string $amount): ?string
|
||||
{
|
||||
if (null === $amount) {
|
||||
Log::debug('No foreign amount info in array. Return NULL');
|
||||
@@ -294,29 +283,78 @@ class TransactionFactory
|
||||
|
||||
return $amount;
|
||||
}
|
||||
//
|
||||
// /**
|
||||
// * @param string $sourceType
|
||||
// * @param string $destinationType
|
||||
// * @param string $transactionType
|
||||
// *
|
||||
// * @throws FireflyException
|
||||
// */
|
||||
// private function validateTransaction(string $sourceType, string $destinationType, string $transactionType): void
|
||||
// {
|
||||
// // throw big fat error when source type === dest type and it's not a transfer or reconciliation.
|
||||
// if ($sourceType === $destinationType && $transactionType !== TransactionType::TRANSFER) {
|
||||
// throw new FireflyException(sprintf('Source and destination account cannot be both of the type "%s"', $destinationType));
|
||||
// }
|
||||
// // source must be in this list AND dest must be in this list:
|
||||
// $list = [AccountType::DEFAULT, AccountType::ASSET, AccountType::CREDITCARD, AccountType::CASH, AccountType::DEBT, AccountType::MORTGAGE,
|
||||
// AccountType::LOAN, AccountType::MORTGAGE];
|
||||
// if (
|
||||
// !\in_array($sourceType, $list, true)
|
||||
// && !\in_array($destinationType, $list, true)) {
|
||||
// throw new FireflyException(sprintf('At least one of the accounts must be an asset account (%s, %s).', $sourceType, $destinationType));
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* This method will throw a Firefly III Exception of the source and destination account types are not OK.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @param Account $source
|
||||
* @param Account $destination
|
||||
*/
|
||||
public function makeDramaOverAccountTypes(Account $source, Account $destination): void
|
||||
{
|
||||
// if the source is X, then Y is allowed as destination.
|
||||
$combinations = [
|
||||
TransactionType::WITHDRAWAL => [
|
||||
AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::CASH],
|
||||
AccountType::LOAN => [AccountType::EXPENSE],
|
||||
AccountType::DEBT => [AccountType::EXPENSE],
|
||||
AccountType::MORTGAGE => [AccountType::EXPENSE],
|
||||
],
|
||||
TransactionType::DEPOSIT => [
|
||||
AccountType::REVENUE => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
AccountType::CASH => [AccountType::ASSET],
|
||||
AccountType::LOAN => [AccountType::ASSET],
|
||||
AccountType::DEBT => [AccountType::ASSET],
|
||||
AccountType::MORTGAGE => [AccountType::ASSET],
|
||||
],
|
||||
TransactionType::TRANSFER => [
|
||||
AccountType::ASSET => [AccountType::ASSET],
|
||||
AccountType::LOAN => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
AccountType::DEBT => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
AccountType::MORTGAGE => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
],
|
||||
TransactionType::OPENING_BALANCE => [
|
||||
AccountType::ASSET => [AccountType::INITIAL_BALANCE],
|
||||
AccountType::LOAN => [AccountType::INITIAL_BALANCE],
|
||||
AccountType::DEBT => [AccountType::INITIAL_BALANCE],
|
||||
AccountType::MORTGAGE => [AccountType::INITIAL_BALANCE],
|
||||
AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE],
|
||||
],
|
||||
TransactionType::RECONCILIATION => [
|
||||
AccountType::RECONCILIATION => [AccountType::ASSET],
|
||||
AccountType::ASSET => [AccountType::RECONCILIATION],
|
||||
],
|
||||
];
|
||||
$sourceType = $source->accountType->type;
|
||||
$destType = $destination->accountType->type;
|
||||
$journalType = $this->journal->transactionType->type;
|
||||
$allowed = $combinations[$journalType][$sourceType] ?? [];
|
||||
if (!\in_array($destType, $allowed, true)) {
|
||||
throw new FireflyException(
|
||||
sprintf(
|
||||
'Journal of type "%s" has a source account of type "%s" and cannot accept a "%s"-account as destination, but only accounts of: %s', $journalType, $sourceType,
|
||||
$destType, implode(', ', $combinations[$journalType][$sourceType])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*/
|
||||
public function setJournal(TransactionJournal $journal): void
|
||||
{
|
||||
$this->journal = $journal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
}
|
||||
|
@@ -58,8 +58,12 @@ class TransactionJournalFactory
|
||||
private $currencyRepository;
|
||||
/** @var array */
|
||||
private $fields;
|
||||
/** @var PiggyBankEventFactory */
|
||||
private $piggyEventFactory;
|
||||
/** @var PiggyBankRepositoryInterface */
|
||||
private $piggyRepository;
|
||||
/** @var TagFactory */
|
||||
private $tagFactory;
|
||||
/** @var TransactionFactory */
|
||||
private $transactionFactory;
|
||||
/** @var TransactionTypeRepositoryInterface */
|
||||
@@ -89,6 +93,8 @@ class TransactionJournalFactory
|
||||
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
||||
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
||||
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
|
||||
$this->piggyEventFactory = app(PiggyBankEventFactory::class);
|
||||
$this->tagFactory = app(TagFactory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +107,7 @@ class TransactionJournalFactory
|
||||
*/
|
||||
public function create(array $data): Collection
|
||||
{
|
||||
$data = new NullArrayObject($data);
|
||||
Log::debug('Start of TransactionJournalFactory::create()');
|
||||
$collection = new Collection;
|
||||
$transactions = $data['transactions'] ?? [];
|
||||
@@ -111,7 +118,7 @@ class TransactionJournalFactory
|
||||
Log::debug(sprintf('Going to store a %s.', $type->type));
|
||||
|
||||
if (0 === \count($transactions)) {
|
||||
Log::error('There are no transactions in the array, cannot continue.');
|
||||
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
|
||||
|
||||
return new Collection;
|
||||
}
|
||||
@@ -122,12 +129,12 @@ class TransactionJournalFactory
|
||||
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, \count($transactions)));
|
||||
/** Get basic fields */
|
||||
|
||||
$currency = $this->currencyRepository->findCurrency($transaction['currency'], $transaction['currency_id'], $transaction['currency_code']);
|
||||
$currency = $this->currencyRepository->findCurrency($transaction['currency'], (int)$transaction['currency_id'], $transaction['currency_code']);
|
||||
$foreignCurrency = $this->findForeignCurrency($transaction);
|
||||
|
||||
$bill = $this->billRepository->findBill($transaction['bill'], $transaction['bill_id'], $transaction['bill_name']);
|
||||
$bill = $this->billRepository->findBill($transaction['bill'], (int)$transaction['bill_id'], $transaction['bill_name']);
|
||||
$billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null;
|
||||
$description = app('steam')->cleanString($transaction['description']);
|
||||
$description = app('steam')->cleanString((string)$transaction['description']);
|
||||
|
||||
/** Create a basic journal. */
|
||||
$journal = TransactionJournal::create(
|
||||
@@ -136,7 +143,7 @@ class TransactionJournalFactory
|
||||
'transaction_type_id' => $type->id,
|
||||
'bill_id' => $billId,
|
||||
'transaction_currency_id' => $currency->id,
|
||||
'description' => $description,
|
||||
'description' => '' === $description ? '(empty description)' : $description,
|
||||
'date' => $carbon->format('Y-m-d H:i:s'),
|
||||
'order' => 0,
|
||||
'tag_count' => 0,
|
||||
@@ -149,6 +156,17 @@ class TransactionJournalFactory
|
||||
$this->transactionFactory->setJournal($journal);
|
||||
$this->transactionFactory->createPair($transaction, $currency, $foreignCurrency);
|
||||
|
||||
// verify that journal has two transactions. Otherwise, delete and cancel.
|
||||
$count = $journal->transactions()->count();
|
||||
if (2 !== $count) {
|
||||
// @codeCoverageIgnoreStart
|
||||
Log::error(sprintf('The journal unexpectedly has %d transaction(s). This is not OK. Cancel operation.', $count));
|
||||
$journal->delete();
|
||||
|
||||
return new Collection;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/** Link all other data to the journal. */
|
||||
|
||||
/** Link budget */
|
||||
@@ -206,7 +224,7 @@ class TransactionJournalFactory
|
||||
public function storeGroup(Collection $collection, ?string $title): ?TransactionGroup
|
||||
{
|
||||
if ($collection->count() < 2) {
|
||||
return null;
|
||||
return null; // @codeCoverageIgnore
|
||||
}
|
||||
/** @var TransactionJournal $first */
|
||||
$first = $collection->first();
|
||||
@@ -235,32 +253,11 @@ class TransactionJournalFactory
|
||||
return;
|
||||
}
|
||||
|
||||
$piggyBank = $this->piggyRepository->findPiggyBank($data['piggy_bank'], $data['piggy_bank_id'], $data['piggy_bank_name']);
|
||||
$piggyBank = $this->piggyRepository->findPiggyBank($data['piggy_bank'], (int)$data['piggy_bank_id'], $data['piggy_bank_name']);
|
||||
|
||||
if (null !== $piggyBank) {
|
||||
/** @var PiggyBankEventFactory $factory */
|
||||
$factory = app(PiggyBankEventFactory::class);
|
||||
$factory->create($journal, $piggyBank);
|
||||
$this->piggyEventFactory->create($journal, $piggyBank);
|
||||
Log::debug('Create piggy event.');
|
||||
}
|
||||
|
||||
|
||||
/** @var PiggyBankFactory $factory */
|
||||
$factory = app(PiggyBankFactory::class);
|
||||
$factory->setUser($this->user);
|
||||
$piggyBank = null;
|
||||
|
||||
if (isset($data['piggy_bank']) && $data['piggy_bank'] instanceof PiggyBank && $data['piggy_bank']->account->user_id === $this->user->id) {
|
||||
Log::debug('Piggy found and belongs to user');
|
||||
$piggyBank = $data['piggy_bank'];
|
||||
}
|
||||
if (null === $data['piggy_bank']) {
|
||||
Log::debug('Piggy not found, search by piggy data.');
|
||||
$piggyBank = $factory->find($data['piggy_bank_id'], $data['piggy_bank_name']);
|
||||
}
|
||||
|
||||
if (null !== $piggyBank) {
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -276,16 +273,14 @@ class TransactionJournalFactory
|
||||
*/
|
||||
public function storeTags(TransactionJournal $journal, ?array $tags): void
|
||||
{
|
||||
/** @var TagFactory $factory */
|
||||
$factory = app(TagFactory::class);
|
||||
$factory->setUser($journal->user);
|
||||
$this->tagFactory->setUser($journal->user);
|
||||
$set = [];
|
||||
if (!\is_array($tags)) {
|
||||
return; // @codeCoverageIgnore
|
||||
return;
|
||||
}
|
||||
foreach ($tags as $string) {
|
||||
if ('' !== $string) {
|
||||
$tag = $factory->findOrCreate($string);
|
||||
$tag = $this->tagFactory->findOrCreate($string);
|
||||
if (null !== $tag) {
|
||||
$set[] = $tag->id;
|
||||
}
|
||||
@@ -333,14 +328,6 @@ class TransactionJournalFactory
|
||||
|
||||
return;
|
||||
}
|
||||
$note = $journal->notes()->first();
|
||||
if (null !== $note) {
|
||||
try {
|
||||
$note->delete();
|
||||
} catch (Exception $e) {
|
||||
Log::debug(sprintf('Journal service trait could not delete note: %s', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,7 +344,7 @@ class TransactionJournalFactory
|
||||
}
|
||||
|
||||
return $this->currencyRepository->findCurrency(
|
||||
$transaction['foreign_currency'], $transaction['foreign_currency_id'], $transaction['foreign_currency_code']
|
||||
$transaction['foreign_currency'], (int)$transaction['foreign_currency_id'], $transaction['foreign_currency_code']
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -36,37 +36,31 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
/**
|
||||
* Class Account.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $iban
|
||||
* @property AccountType $accountType
|
||||
* @property bool $active
|
||||
* @property string $virtual_balance
|
||||
* @property User $user
|
||||
* @property string startBalance
|
||||
* @property string endBalance
|
||||
* @property string difference
|
||||
* @property Carbon lastActivityDate
|
||||
* @property Collection accountMeta
|
||||
* @property bool encrypted
|
||||
* @property int account_type_id
|
||||
* @property Collection piggyBanks
|
||||
* @property string $interest
|
||||
* @property string $interestPeriod
|
||||
* @property string accountTypeString
|
||||
* @property Carbon created_at
|
||||
* @property Carbon updated_at
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $iban
|
||||
* @property AccountType $accountType
|
||||
* @property bool $active
|
||||
* @property string $virtual_balance
|
||||
* @property User $user
|
||||
* @property string startBalance
|
||||
* @property string endBalance
|
||||
* @property string difference
|
||||
* @property Carbon lastActivityDate
|
||||
* @property Collection accountMeta
|
||||
* @property bool encrypted
|
||||
* @property int account_type_id
|
||||
* @property Collection piggyBanks
|
||||
* @property string $interest
|
||||
* @property string $interestPeriod
|
||||
* @property string accountTypeString
|
||||
* @property Carbon created_at
|
||||
* @property Carbon updated_at
|
||||
* @SuppressWarnings (PHPMD.CouplingBetweenObjects)
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $account_type_id
|
||||
* @property bool $encrypted
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\AccountMeta[] $accountMeta
|
||||
* @property-read string $edit_name
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBank[] $piggyBanks
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property int $user_id
|
||||
* @property-read string $edit_name
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account accountTypeIn($types)
|
||||
* @method static bool|null forceDelete()
|
||||
@@ -103,6 +97,7 @@ class Account extends Model
|
||||
= [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'user_id' => 'integer',
|
||||
'deleted_at' => 'datetime',
|
||||
'active' => 'boolean',
|
||||
'encrypted' => 'boolean',
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\Journal;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\TransactionJournalFactory;
|
||||
@@ -320,6 +321,28 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of the category linked to the journal (if any) or to the transactions (if any).
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getJournalCategoryId(TransactionJournal $journal): int
|
||||
{
|
||||
$category = $journal->categories()->first();
|
||||
if (null !== $category) {
|
||||
return $category->id;
|
||||
}
|
||||
/** @noinspection NullPointerExceptionInspection */
|
||||
$category = $journal->transactions()->first()->categories()->first();
|
||||
if (null !== $category) {
|
||||
return $category->id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the category linked to the journal (if any) or to the transactions (if any).
|
||||
*
|
||||
@@ -615,6 +638,27 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
return $transaction->transactionJournal->piggyBankEvents()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all journals with more than 2 transactions. Should only return empty collections
|
||||
* in Firefly III > v4.8.0.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getSplitJournals(): Collection
|
||||
{
|
||||
// grab all split transactions:
|
||||
$all = Transaction::groupBy('transaction_journal_id')->get(['transaction_journal_id', DB::raw('COUNT(transaction_journal_id) as result')]);
|
||||
/** @var Collection $filtered */
|
||||
$filtered = $all->filter(
|
||||
function (Transaction $transaction) {
|
||||
return (int)$transaction->result > 2;
|
||||
}
|
||||
);
|
||||
$journalIds = array_unique($filtered->pluck('transaction_journal_id')->toArray());
|
||||
|
||||
return TransactionJournal::whereIn('id', $journalIds)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all tags as strings in an array.
|
||||
*
|
||||
|
@@ -149,6 +149,15 @@ interface JournalRepositoryInterface
|
||||
*/
|
||||
public function getJournalBudgetId(TransactionJournal $journal): int;
|
||||
|
||||
/**
|
||||
* Return the ID of the category linked to the journal (if any) or to the transactions (if any).
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getJournalCategoryId(TransactionJournal $journal): int;
|
||||
|
||||
/**
|
||||
* Return the name of the category linked to the journal (if any) or to the transactions (if any).
|
||||
*
|
||||
@@ -256,6 +265,14 @@ interface JournalRepositoryInterface
|
||||
*/
|
||||
public function getPiggyBankEventsbyTr(Transaction $transaction): Collection;
|
||||
|
||||
/**
|
||||
* Returns all journals with more than 2 transactions. Should only return empty collections
|
||||
* in Firefly III > v4.8.0.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getSplitJournals(): Collection;
|
||||
|
||||
/**
|
||||
* Return all tags as strings in an array.
|
||||
*
|
||||
|
@@ -81,16 +81,16 @@ trait RecurringTransactionTrait
|
||||
$destination = null;
|
||||
switch ($recurrence->transactionType->type) {
|
||||
case TransactionType::WITHDRAWAL:
|
||||
$source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::EXPENSE, $array['destination_id'], $array['destination_name']);
|
||||
$source = $this->findAccount(AccountType::ASSET, null, $array['source_id'], $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::EXPENSE,null, $array['destination_id'], $array['destination_name']);
|
||||
break;
|
||||
case TransactionType::DEPOSIT:
|
||||
$source = $this->findAccount(AccountType::REVENUE, $array['source_id'], $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name']);
|
||||
$source = $this->findAccount(AccountType::REVENUE, null, $array['source_id'], $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::ASSET, null, $array['destination_id'], $array['destination_name']);
|
||||
break;
|
||||
case TransactionType::TRANSFER:
|
||||
$source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name']);
|
||||
$source = $this->findAccount(AccountType::ASSET,null, $array['source_id'], $array['source_name']);
|
||||
$destination = $this->findAccount(AccountType::ASSET, null, $array['destination_id'], $array['destination_name']);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -172,13 +172,14 @@ trait RecurringTransactionTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $expectedType
|
||||
* @param int|null $accountId
|
||||
* @param null|string $accountName
|
||||
* @param null|string $expectedType
|
||||
* @param Account|null $account
|
||||
* @param int|null $accountId
|
||||
* @param null|string $accountName
|
||||
*
|
||||
* @return Account|null
|
||||
*/
|
||||
abstract public function findAccount(?string $expectedType, ?int $accountId, ?string $accountName): ?Account;
|
||||
abstract public function findAccount(?string $expectedType, ?Account $account, ?int $accountId, ?string $accountName): ?Account;
|
||||
|
||||
/**
|
||||
* Update meta data for recurring transaction.
|
||||
|
@@ -122,6 +122,14 @@ abstract class TestCase extends BaseTestCase
|
||||
return $this->getRandomAccount(AccountType::ASSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
*/
|
||||
public function getRandomLoan(): Account
|
||||
{
|
||||
return $this->getRandomAccount(AccountType::LOAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TransactionJournal
|
||||
*/
|
||||
|
176
tests/Unit/Console/Commands/Upgrade/MigrateToGroupsTest.php
Normal file
176
tests/Unit/Console/Commands/Upgrade/MigrateToGroupsTest.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* MigrateToGroupsTest.php
|
||||
* Copyright (c) 2019 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Console\Commands\Upgrade;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Factory\TransactionJournalFactory;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Class MigrateToGroupsTest
|
||||
*/
|
||||
class MigrateToGroupsTest extends TestCase
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Log::info(sprintf('Now in %s.', \get_class($this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Console\Commands\Upgrade\MigrateToGroups
|
||||
*/
|
||||
public function testAlreadyExecuted(): void
|
||||
{
|
||||
$this->mock(TransactionJournalFactory::class);
|
||||
$this->mock(JournalRepositoryInterface::class);
|
||||
|
||||
$configObject = new Configuration;
|
||||
$configObject->data = true;
|
||||
FireflyConfig::shouldReceive('get')->withArgs(['migrated_to_groups_4780', false])->andReturn($configObject)->once();
|
||||
|
||||
$this->artisan('firefly:migrate-to-groups')
|
||||
->expectsOutput('Database already seems to be migrated.')
|
||||
->assertExitCode(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Console\Commands\Upgrade\MigrateToGroups
|
||||
*/
|
||||
public function testBasic(): void
|
||||
{
|
||||
$journalFactory = $this->mock(TransactionJournalFactory::class);
|
||||
$journalRepos = $this->mock(JournalRepositoryInterface::class);
|
||||
$withdrawal = $this->getRandomSplitWithdrawal();
|
||||
$collection = new Collection([$withdrawal]);
|
||||
$date = new Carbon;
|
||||
$opposing = new Transaction;
|
||||
$opposing->account_id = 13;
|
||||
|
||||
// not yet executed:
|
||||
$configObject = new Configuration;
|
||||
$configObject->data = false;
|
||||
FireflyConfig::shouldReceive('get')->withArgs(['migrated_to_groups_4780', false])->andReturn($configObject)->once();
|
||||
FireflyConfig::shouldReceive('set')->withArgs(['migrated_to_groups_4780', true])->once();
|
||||
|
||||
|
||||
// calls to repository:
|
||||
$journalRepos->shouldReceive('setUser')->atLeast()->once();
|
||||
$journalRepos->shouldReceive('getJournalBudgetId')->atLeast()->once()->andReturn(1);
|
||||
$journalRepos->shouldReceive('getJournalCategoryId')->atLeast()->once()->andReturn(2);
|
||||
$journalRepos->shouldReceive('findOpposingTransaction')->atLeast()->once()->andReturn($opposing);
|
||||
$journalRepos->shouldReceive('getNoteText')->atLeast()->once()->andReturn('I am some notes.');
|
||||
$journalRepos->shouldReceive('getTags')->atLeast()->once()->andReturn(['a', 'b']);
|
||||
$journalRepos->shouldReceive('getSplitJournals')->once()->andReturn($collection);
|
||||
|
||||
// all meta field calls.
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'internal-reference'])->andReturn('ABC');
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-cc'])->andReturnNull();
|
||||
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-ct-op'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-ct-id'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-db'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-country'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-ep'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-ci'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'sepa-batch-id'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'external-id'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'original-source'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'recurrence_id'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'bunq_payment_id'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'importHash'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaField')->atLeast()->once()->withArgs([Mockery::any(), 'importHashV2'])->andReturnNull();
|
||||
|
||||
$journalRepos->shouldReceive('getMetaDate')->atLeast()->once()->withArgs([Mockery::any(), 'interest_date'])->andReturn($date);
|
||||
$journalRepos->shouldReceive('getMetaDate')->atLeast()->once()->withArgs([Mockery::any(), 'book_date'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaDate')->atLeast()->once()->withArgs([Mockery::any(), 'process_date'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaDate')->atLeast()->once()->withArgs([Mockery::any(), 'due_date'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaDate')->atLeast()->once()->withArgs([Mockery::any(), 'payment_date'])->andReturnNull();
|
||||
$journalRepos->shouldReceive('getMetaDate')->atLeast()->once()->withArgs([Mockery::any(), 'invoice_date'])->andReturnNull();
|
||||
|
||||
// calls to factory
|
||||
$journalFactory->shouldReceive('setUser')->atLeast()->once();
|
||||
$journalFactory->shouldReceive('create')->atLeast()->once()->withAnyArgs()->andReturn(new Collection());
|
||||
|
||||
|
||||
$this->artisan('firefly:migrate-to-groups')
|
||||
->expectsOutput('Going to un-split 1 transaction(s). This could take some time.')
|
||||
->assertExitCode(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Console\Commands\Upgrade\MigrateToGroups
|
||||
*/
|
||||
public function testForced(): void
|
||||
{
|
||||
$this->mock(TransactionJournalFactory::class);
|
||||
$repository = $this->mock(JournalRepositoryInterface::class);
|
||||
|
||||
$repository->shouldReceive('getSplitJournals')->andReturn(new Collection);
|
||||
|
||||
|
||||
$configObject = new Configuration;
|
||||
$configObject->data = true;
|
||||
FireflyConfig::shouldReceive('get')->withArgs(['migrated_to_groups_4780', false])->andReturn($configObject)->once();
|
||||
FireflyConfig::shouldReceive('set')->withArgs(['migrated_to_groups_4780', true])->once();
|
||||
|
||||
$this->artisan('firefly:migrate-to-groups --force')
|
||||
->expectsOutput('Forcing the migration.')
|
||||
->expectsOutput('Found no split journals. Nothing to do.')
|
||||
->assertExitCode(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \FireflyIII\Console\Commands\Upgrade\MigrateToGroups
|
||||
*/
|
||||
public function testNotSplit(): void
|
||||
{
|
||||
$this->mock(TransactionJournalFactory::class);
|
||||
$repository = $this->mock(JournalRepositoryInterface::class);
|
||||
$withdrawal = $this->getRandomWithdrawal();
|
||||
|
||||
$repository->shouldReceive('getSplitJournals')->andReturn(new Collection([$withdrawal]));
|
||||
|
||||
|
||||
$configObject = new Configuration;
|
||||
$configObject->data = false;
|
||||
FireflyConfig::shouldReceive('get')->withArgs(['migrated_to_groups_4780', false])->andReturn($configObject)->once();
|
||||
FireflyConfig::shouldReceive('set')->withArgs(['migrated_to_groups_4780', true])->once();
|
||||
|
||||
$this->artisan('firefly:migrate-to-groups')
|
||||
->expectsOutput('Going to un-split 1 transaction(s). This could take some time.')
|
||||
->assertExitCode(0);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user