Migrated all code to group collector.

This commit is contained in:
James Cole 2019-05-31 13:35:33 +02:00
parent eb6329e556
commit e15c35de64
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
31 changed files with 1540 additions and 1671 deletions

View File

@ -162,166 +162,6 @@ class GroupCollector implements GroupCollectorInterface
}
/**
* @param Collection $collection
*
* @return Collection
* @throws Exception
*/
private function parseArray(Collection $collection): Collection
{
$groups = [];
/** @var TransactionGroup $augmentedGroup */
foreach ($collection as $augmentedGroup) {
$groupId = $augmentedGroup->transaction_group_id;
if (!isset($groups[$groupId])) {
// make new array
$groupArray = [
'id' => $augmentedGroup->transaction_group_id,
'user_id' => $augmentedGroup->user_id,
'title' => $augmentedGroup->transaction_group_title,
'count' => 1,
'sums' => [],
'transactions' => [],
];
$journalId = (int)$augmentedGroup->transaction_journal_id;
$groupArray['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup);
$groups[$groupId] = $groupArray;
continue;
}
// or parse the rest.
$journalId = (int)$augmentedGroup->transaction_journal_id;
$groups[$groupId]['count']++;
if (isset($groups[$groupId]['transactions'][$journalId])) {
$groups[$groupId]['transactions'][$journalId] =
$this->mergeTags($groups[$groupId]['transactions'][$journalId], $augmentedGroup);
}
if (!isset($groups[$groupId]['transactions'][$journalId])) {
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup);
}
}
$groups = $this->parseSums($groups);
return new Collection($groups);
}
/**
* @param TransactionGroup $augmentedGroup
*
* @return array
* @throws Exception
*/
private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array
{
$result = $augmentedGroup->toArray();
$result['tags'] = [];
$result['date'] = new Carbon($result['date']);
$result['created_at'] = new Carbon($result['created_at']);
$result['updated_at'] = new Carbon($result['updated_at']);
$result['reconciled'] = 1 === (int)$result['reconciled'];
if (isset($augmentedGroup['tag_id'])) { // assume the other fields are present as well.
$tagId = (int)$augmentedGroup['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augmentedGroup['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int)$result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
'latitude' => $result['tag_latitude'],
'longitude' => $result['tag_longitude'],
'zoom_level' => $result['tag_zoom_level'],
];
}
return $result;
}
/**
* @param array $existingJournal
* @param TransactionGroup $newGroup
* @return array
*/
private function mergeTags(array $existingJournal, TransactionGroup $newGroup): array
{
$newArray = $newGroup->toArray();
if (isset($newArray['tag_id'])) { // assume the other fields are present as well.
$tagId = (int)$newGroup['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int)$newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
'latitude' => $newArray['tag_latitude'],
'longitude' => $newArray['tag_longitude'],
'zoom_level' => $newArray['tag_zoom_level'],
];
}
return $existingJournal;
}
/**
* @param array $groups
*
* @return array
*/
private function parseSums(array $groups): array
{
/**
* @var int $groudId
* @var array $group
*/
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int)$transaction['currency_id'];
// set default:
if (!isset($groups[$groudId]['sums'][$currencyId])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount']);
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int)$transaction['foreign_currency_id'];
// set default:
if (!isset($groups[$groudId]['sums'][$currencyId])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount']);
}
}
}
return $groups;
}
/**
* Define which accounts can be part of the source and destination transactions.
*
@ -355,12 +195,46 @@ class GroupCollector implements GroupCollectorInterface
*/
public function setBill(Bill $bill): GroupCollectorInterface
{
$this->withBudgetInformation();
$this->withBillInformation();
$this->query->where('transaction_journals.bill_id', '=', $bill->id);
return $this;
}
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/**
* Limit the search to a specific budget.
*
* @param Budget $budget
*
* @return GroupCollectorInterface
*/
public function setBudget(Budget $budget): GroupCollectorInterface
{
$this->withBudgetInformation();
$this->query->where('budgets.id', $budget->id);
return $this;
}
/**
* Will include budget ID + name, if any.
*
@ -382,21 +256,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Limit the search to a specific budget.
*
* @param Budget $budget
*
* @return GroupCollectorInterface
*/
public function setBudget(Budget $budget): GroupCollectorInterface
{
$this->withBudgetInformation();
$this->query->where('budgets.id', $budget->id);
return $this;
}
/**
* Limit the search to a specific set of budgets.
*
@ -554,26 +413,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Join table to get tag information.
*/
private function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
}
}
/**
* Limit the search to one specific transaction group.
*
@ -617,44 +456,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Build the query.
*/
private function startQuery(): void
{
app('log')->debug('GroupCollector::startQuery');
$this->query = $this->user
->transactionGroups()
->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
// join source transaction.
->leftJoin(
'transactions as source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
->where('source.amount', '<', 0);
}
)
// join destination transaction
->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
->where('destination.amount', '>', 0);
}
)
// left join transaction type.
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
->whereNull('transaction_groups.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('source.deleted_at')
->whereNull('destination.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('source.amount', 'DESC');
}
/**
* Automatically include all stuff required to make API calls work.
*
@ -708,25 +509,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/**
* Limit the search to a specific bunch of categories.
*
@ -793,6 +575,18 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
*
*/
public function dumpQuery(): void
{
echo $this->query->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
}
/**
* Return the sum of all journals.
*
@ -830,4 +624,370 @@ class GroupCollector implements GroupCollectorInterface
return $return;
}
/**
* Search for words in descriptions.
*
* @param array $array
*
* @return GroupCollectorInterface
*/
public function setSearchWords(array $array): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($array) {
$q->where(
function (EloquentBuilder $q1) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q1->where('transaction_journals.description', 'LIKE', $keyword);
}
}
);
$q->orWhere(
function (EloquentBuilder $q2) use ($array) {
foreach ($array as $word) {
$keyword = sprintf('%%%s%%', $word);
$q2->where('transaction_groups.title', 'LIKE', $keyword);
}
}
);
}
);
return $this;
}
/**
* Limit the result to a specific transaction group.
*
* @param TransactionGroup $transactionGroup
*
* @return GroupCollectorInterface
*/
public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface
{
$this->query->where('transaction_groups.id', $transactionGroup->id);
return $this;
}
/**
* Limit the search to a specific set of bills.
*
* @param Collection $bills
*
* @return GroupCollectorInterface
*/
public function setBills(Collection $bills): GroupCollectorInterface
{
$this->withBillInformation();
$this->query->whereIn('transaction_journals.bill_id', $bills->pluck('id')->toArray());
return $this;
}
/**
* Get transactions with a specific amount.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function amountIs(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->where('source.amount', app('steam')->negative($amount));
}
);
return $this;
}
/**
* Get transactions where the amount is less than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function amountLess(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->where('destination.amount', '<', app('steam')->positive($amount));
}
);
return $this;
}
/**
* Get transactions where the amount is more than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function amountMore(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->where('destination.amount', '>', app('steam')->positive($amount));
}
);
return $this;
}
/**
* Collect transactions before a specific date.
*
* @param Carbon $date
*
* @return GroupCollectorInterface
*/
public function setBefore(Carbon $date): GroupCollectorInterface
{
$beforeStr = $date->format('Y-m-d 00:00:00');
$this->query->where('transaction_journals.date', '<=', $beforeStr);
Log::debug(sprintf('GroupCollector range is now before %s (inclusive)', $beforeStr));
return $this;
}
/**
* Collect transactions after a specific date.
*
* @param Carbon $date
*
* @return GroupCollectorInterface
*/
public function setAfter(Carbon $date): GroupCollectorInterface
{
$afterStr = $date->format('Y-m-d 00:00:00');
$this->query->where('transaction_journals.date', '>=', $afterStr);
Log::debug(sprintf('GroupCollector range is now after %s (inclusive)', $afterStr));
return $this;
}
/**
* @param Collection $collection
*
* @return Collection
* @throws Exception
*/
private function parseArray(Collection $collection): Collection
{
$groups = [];
/** @var TransactionGroup $augmentedGroup */
foreach ($collection as $augmentedGroup) {
$groupId = $augmentedGroup->transaction_group_id;
if (!isset($groups[$groupId])) {
// make new array
$groupArray = [
'id' => $augmentedGroup->transaction_group_id,
'user_id' => $augmentedGroup->user_id,
'title' => $augmentedGroup->transaction_group_title,
'count' => 1,
'sums' => [],
'transactions' => [],
];
$journalId = (int)$augmentedGroup->transaction_journal_id;
$groupArray['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup);
$groups[$groupId] = $groupArray;
continue;
}
// or parse the rest.
$journalId = (int)$augmentedGroup->transaction_journal_id;
$groups[$groupId]['count']++;
if (isset($groups[$groupId]['transactions'][$journalId])) {
$groups[$groupId]['transactions'][$journalId] =
$this->mergeTags($groups[$groupId]['transactions'][$journalId], $augmentedGroup);
}
if (!isset($groups[$groupId]['transactions'][$journalId])) {
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup);
}
}
$groups = $this->parseSums($groups);
return new Collection($groups);
}
/**
* @param TransactionGroup $augmentedGroup
*
* @return array
* @throws Exception
*/
private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array
{
$result = $augmentedGroup->toArray();
$result['tags'] = [];
$result['date'] = new Carbon($result['date']);
$result['created_at'] = new Carbon($result['created_at']);
$result['updated_at'] = new Carbon($result['updated_at']);
$result['reconciled'] = 1 === (int)$result['reconciled'];
if (isset($augmentedGroup['tag_id'])) { // assume the other fields are present as well.
$tagId = (int)$augmentedGroup['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augmentedGroup['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int)$result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
'latitude' => $result['tag_latitude'],
'longitude' => $result['tag_longitude'],
'zoom_level' => $result['tag_zoom_level'],
];
}
return $result;
}
/**
* @param array $existingJournal
* @param TransactionGroup $newGroup
* @return array
*/
private function mergeTags(array $existingJournal, TransactionGroup $newGroup): array
{
$newArray = $newGroup->toArray();
if (isset($newArray['tag_id'])) { // assume the other fields are present as well.
$tagId = (int)$newGroup['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int)$newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
'latitude' => $newArray['tag_latitude'],
'longitude' => $newArray['tag_longitude'],
'zoom_level' => $newArray['tag_zoom_level'],
];
}
return $existingJournal;
}
/**
* @param array $groups
*
* @return array
*/
private function parseSums(array $groups): array
{
/**
* @var int $groudId
* @var array $group
*/
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int)$transaction['currency_id'];
// set default:
if (!isset($groups[$groudId]['sums'][$currencyId])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount']);
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int)$transaction['foreign_currency_id'];
// set default:
if (!isset($groups[$groudId]['sums'][$currencyId])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount']);
}
}
}
return $groups;
}
/**
* Join table to get tag information.
*/
private function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
}
}
/**
* Build the query.
*/
private function startQuery(): void
{
app('log')->debug('GroupCollector::startQuery');
$this->query = $this->user
->transactionGroups()
->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
// join source transaction.
->leftJoin(
'transactions as source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
->where('source.amount', '<', 0);
}
)
// join destination transaction
->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
->where('destination.amount', '>', 0);
}
)
// left join transaction type.
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
->whereNull('transaction_groups.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('source.deleted_at')
->whereNull('destination.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('source.amount', 'DESC');
}
}

View File

@ -85,6 +85,42 @@ interface GroupCollectorInterface
*/
public function setBill(Bill $bill): GroupCollectorInterface;
/**
* Limit the search to a specific set of bills.
*
* @param Collection $bills
*
* @return GroupCollectorInterface
*/
public function setBills(Collection $bills): GroupCollectorInterface;
/**
* Get transactions with a specific amount.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function amountIs(string $amount): GroupCollectorInterface;
/**
* Get transactions where the amount is less than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function amountLess(string $amount): GroupCollectorInterface;
/**
* Get transactions where the amount is more than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function amountMore(string $amount): GroupCollectorInterface;
/**
* Limit the search to a specific budget.
*
@ -123,7 +159,7 @@ interface GroupCollectorInterface
public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface;
/**
* Limit the result to a set of specific journals.
* Limit the result to a set of specific transaction journals.
*
* @param array $journalIds
*
@ -131,6 +167,24 @@ interface GroupCollectorInterface
*/
public function setJournalIds(array $journalIds): GroupCollectorInterface;
/**
* Limit the result to a specific transaction group.
*
* @param TransactionGroup $transactionGroup
*
* @return GroupCollectorInterface
*/
public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface;
/**
* Search for words in descriptions.
*
* @param array $array
*
* @return GroupCollectorInterface
*/
public function setSearchWords(array $array): GroupCollectorInterface;
/**
* Limit the number of returned entries.
*
@ -241,6 +295,24 @@ interface GroupCollectorInterface
*/
public function setCategories(Collection $categories): GroupCollectorInterface;
/**
* Collect transactions before a specific date.
*
* @param Carbon $date
*
* @return GroupCollectorInterface
*/
public function setBefore(Carbon $date): GroupCollectorInterface;
/**
* Collect transactions after a specific date.
*
* @param Carbon $date
*
* @return GroupCollectorInterface
*/
public function setAfter(Carbon $date): GroupCollectorInterface;
/**
* Include bill name + ID.
*

View File

@ -89,7 +89,7 @@ class ExpenseReportController extends Controller
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
return response()->json($cache->get()); // @codeCoverageIgnore
// return response()->json($cache->get()); // @codeCoverageIgnore
}
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
@ -97,44 +97,43 @@ class ExpenseReportController extends Controller
$chartData = [];
$currentStart = clone $start;
$combined = $this->combineAccounts($expense);
// make "all" set:
$all = new Collection;
foreach ($combined as $name => $combi) {
$all = $all->merge($combi);
foreach ($combined as $name => $combination) {
$all = $all->merge($combination);
}
// prep chart data:
/**
* @var string $name
* @var Collection $combi
* @var Collection $combination
*/
foreach ($combined as $name => $combi) {
foreach ($combined as $name => $combination) {
// first is always expense account:
/** @var Account $exp */
$exp = $combi->first();
$exp = $combination->first();
$chartData[$exp->id . '-in'] = [
'label' => $name . ' (' . strtolower((string)trans('firefly.income')) . ')',
'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.income'))),
'type' => 'bar',
'yAxisID' => 'y-axis-0',
'entries' => [],
];
$chartData[$exp->id . '-out'] = [
'label' => $name . ' (' . strtolower((string)trans('firefly.expenses')) . ')',
'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.expenses'))),
'type' => 'bar',
'yAxisID' => 'y-axis-0',
'entries' => [],
];
// total in, total out:
$chartData[$exp->id . '-total-in'] = [
'label' => $name . ' (' . strtolower((string)trans('firefly.sum_of_income')) . ')',
'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.sum_of_income'))),
'type' => 'line',
'fill' => false,
'yAxisID' => 'y-axis-1',
'entries' => [],
];
$chartData[$exp->id . '-total-out'] = [
'label' => $name . ' (' . strtolower((string)trans('firefly.sum_of_expenses')) . ')',
'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.sum_of_expenses'))),
'type' => 'line',
'fill' => false,
'yAxisID' => 'y-axis-1',
@ -154,15 +153,15 @@ class ExpenseReportController extends Controller
$income = $this->groupByName($this->getIncomeForOpposing($accounts, $all, $currentStart, $currentEnd));
$label = $currentStart->formatLocalized($format);
foreach ($combined as $name => $combi) {
foreach ($combined as $name => $combination) {
// first is always expense account:
/** @var Account $exp */
$exp = $combi->first();
$exp = $combination->first();
$labelIn = $exp->id . '-in';
$labelOut = $exp->id . '-out';
$labelSumIn = $exp->id . '-total-in';
$labelSumOut = $exp->id . '-total-out';
$currentIncome = $income[$name] ?? '0';
$currentIncome = bcmul($income[$name] ?? '0', '-1');
$currentExpense = $expenses[$name] ?? '0';
// add to sum:
@ -180,6 +179,7 @@ class ExpenseReportController extends Controller
/** @var Carbon $currentStart */
$currentStart = clone $currentEnd;
$currentStart->addDay();
$currentStart->startOfDay();
}
// remove all empty entries to prevent cluttering:
$newSet = [];

View File

@ -32,6 +32,7 @@ use Illuminate\Http\Response as LaravelResponse;
use Log;
/**
* TODO make sure all import methods work.
*
* Class IndexController
*/

View File

@ -96,6 +96,7 @@ class ReconcileController extends Controller
*/
public function overview(Request $request, Account $account, Carbon $start, Carbon $end): JsonResponse
{
if (AccountType::ASSET !== $account->accountType->type) {
throw new FireflyException(sprintf('Account %s is not an asset account.', $account->name));
}
@ -107,6 +108,7 @@ class ReconcileController extends Controller
$clearedAmount = '0';
$route = route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]);
// get sum of transaction amounts:
// TODO these methods no longer exist:
$transactions = $this->repository->getTransactionsById($transactionIds);
$cleared = $this->repository->getTransactionsById($clearedIds);
$countCleared = 0;

View File

@ -70,7 +70,7 @@ class CategoryController extends Controller
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
@ -112,7 +112,7 @@ class CategoryController extends Controller
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
$cache->store($result);
@ -167,7 +167,7 @@ class CategoryController extends Controller
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd

View File

@ -23,9 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\CacheProperties;
@ -110,7 +109,7 @@ class ExpenseController extends Controller
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::budget: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
$cache->store($result);
@ -175,7 +174,7 @@ class ExpenseController extends Controller
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
$cache->store($result);
@ -227,7 +226,7 @@ class ExpenseController extends Controller
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
$cache->store($result);
@ -265,22 +264,23 @@ class ExpenseController extends Controller
$all = $all->merge($combi);
}
// get all expenses in period:
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($accounts);
$collector->setOpposingAccounts($all);
$set = $collector->getTransactions();
$sorted = $set->sortBy(
function (Transaction $transaction) {
return (float)$transaction->transaction_amount;
}
);
$collector->setAccounts($all);
$set = $collector->getExtractedJournals();
usort($set, function ($a, $b) {
return $a['amount'] <=> $b['amount'];
});
try {
$result = view('reports.partials.top-transactions', compact('sorted'))->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::topExpense: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
$cache->store($result);
@ -316,22 +316,24 @@ class ExpenseController extends Controller
$all = $all->merge($combi);
}
// get all expenses in period:
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($accounts);
$collector->setOpposingAccounts($all);
$set = $collector->getTransactions();
$sorted = $set->sortByDesc(
function (Transaction $transaction) {
return (float)$transaction->transaction_amount;
}
);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$total = $accounts->merge($all);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total);
$journals = $collector->getExtractedJournals();
usort($journals, function ($a, $b) {
return $a['amount'] <=> $b['amount'];
});
try {
$result = view('reports.partials.top-transactions', compact('sorted'))->render();
// @codeCoverageIgnoreStart
} catch (Throwable $e) {
Log::error(sprintf('Could not render category::topIncome: %s', $e->getMessage()));
$result = 'An error prevented Firefly III from rendering. Apologies.';
$result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage());
}
// @codeCoverageIgnoreEnd
$cache->store($result);

View File

@ -170,6 +170,7 @@ class RuleGroupController extends Controller
* @param RuleGroup $ruleGroup
*
* @return RedirectResponse
* @throws \Exception
*/
public function execute(SelectTransactionsRequest $request, AccountRepositoryInterface $repository, RuleGroup $ruleGroup): RedirectResponse
{

View File

@ -25,7 +25,7 @@ namespace FireflyIII\Http\Controllers\Transaction;
use Carbon\Carbon;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Filter\TransactionViewFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Http\Controllers\Controller;
@ -126,10 +126,13 @@ class MassController extends Controller
* @param Collection $journals
*
* @return IlluminateView
*
* TODO rebuild this feature.
* @throws FireflyException
*/
public function edit(Collection $journals): IlluminateView
{
throw new FireflyException('Needs refactor');
throw new FireflyException(sprintf('The mass-editor is not available in v%s of Firefly III. Sorry about that. It will be back soon.', config('firefly.version')));
/** @var User $user */
$user = auth()->user();
$subTitle = (string)trans('firefly.mass_edit_journals');
@ -148,8 +151,8 @@ class MassController extends Controller
$transformer = app(TransactionTransformer::class);
$transformer->setParameters(new ParameterBag);
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($user);
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
$collector->setJournals($journals);

View File

@ -1,177 +0,0 @@
<?php
/**
* SplitController.php
* Copyright (c) 2017 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 FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SplitJournalFormRequest;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Http\Controllers\ModelInformation;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use Illuminate\Http\Request;
use View;
/**
* Class SplitController.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class SplitController extends Controller
{
use ModelInformation, RequestInformation;
/** @var AttachmentHelperInterface Attachment helper */
private $attachments;
/** @var BudgetRepositoryInterface The budget repository */
private $budgets;
/** @var CurrencyRepositoryInterface The currency repository */
private $currencies;
/** @var JournalRepositoryInterface Journals and transactions overview */
private $repository;
/**
* SplitController constructor.
*/
public function __construct()
{
throw new FireflyException('Do not use me.');
parent::__construct();
// some useful repositories:
$this->middleware(
function ($request, $next) {
$this->budgets = app(BudgetRepositoryInterface::class);
$this->attachments = app(AttachmentHelperInterface::class);
$this->currencies = app(CurrencyRepositoryInterface::class);
$this->repository = app(JournalRepositoryInterface::class);
app('view')->share('mainTitleIcon', 'fa-share-alt');
app('view')->share('title', (string)trans('firefly.split-transactions'));
return $next($request);
}
);
}
/**
* Edit a split.
*
* @param Request $request
* @param TransactionJournal $journal
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
* @throws FireflyException
*/
public function edit(Request $request, TransactionJournal $journal)
{
throw new FireflyException('Needs refactoring');
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal); // @codeCoverageIgnore
}
// basic fields:
$uploadSize = min(app('steam')->phpBytes(ini_get('upload_max_filesize')), app('steam')->phpBytes(ini_get('post_max_size')));
$subTitle = (string)trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
$subTitleIcon = 'fa-pencil';
// lists and collections
$currencies = $this->currencies->get();
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets());
// other fields
$optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
$preFilled = $this->arrayFromJournal($request, $journal);
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('transactions.edit-split.fromUpdate')) {
$this->rememberPreviousUri('transactions.edit-split.uri');
}
session()->forget('transactions.edit-split.fromUpdate');
return view(
'transactions.split.edit', compact(
'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'uploadSize', 'budgets',
'journal'
)
);
}
/**
* Store new split journal.
*
* @param SplitJournalFormRequest $request
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function update(SplitJournalFormRequest $request, TransactionJournal $journal)
{
throw new FireflyException('Needs refactoring.');
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal); // @codeCoverageIgnore
}
$data = $request->getAll();
// keep current bill:
$data['bill_id'] = $journal->bill_id;
$journal = $this->repository->update($journal, $data);
/** @var array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
// save attachments:
$this->attachments->saveAttachmentsForModel($journal, $files);
event(new UpdatedTransactionGroup($group));
// flash messages
// @codeCoverageIgnoreStart
if (count($this->attachments->getMessages()->get('attachments')) > 0) {
session()->flash('info', $this->attachments->getMessages()->get('attachments'));
}
// @codeCoverageIgnoreEnd
$type = strtolower($this->repository->getTransactionType($journal));
session()->flash('success', (string)trans('firefly.updated_' . $type, ['description' => $journal->description]));
app('preferences')->mark();
// @codeCoverageIgnoreStart
if (1 === (int)$request->get('return_to_edit')) {
// set value so edit routine will not overwrite URL:
session()->put('transactions.edit-split.fromUpdate', true);
return redirect(route('transactions.split.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
}
// @codeCoverageIgnoreEnd
// redirect to previous URL.
return redirect($this->getPreviousUri('transactions.edit-split.uri'));
}
}

View File

@ -26,20 +26,19 @@ namespace FireflyIII\Import\Storage;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\Rule;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\TransactionRules\Processor;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
@ -64,8 +63,10 @@ class ImportArrayStorage
private $journalRepos;
/** @var ImportJobRepositoryInterface Import job repository */
private $repository;
/** @var Collection The transfers the user already has. */
/** @var array The transfers the user already has. */
private $transfers;
/** @var TransactionGroupRepositoryInterface */
private $groupRepos;
/**
* Set job, count transfers in the array and create the repository.
@ -83,9 +84,59 @@ class ImportArrayStorage
$this->journalRepos = app(JournalRepositoryInterface::class);
$this->journalRepos->setUser($importJob->user);
$this->groupRepos = app(TransactionGroupRepositoryInterface::class);
$this->groupRepos->setUser($importJob->user);
Log::debug('Constructed ImportArrayStorage()');
}
/**
* Count the number of transfers in the array. If this is zero, don't bother checking for double transfers.
*/
private function countTransfers(): void
{
Log::debug('Now in countTransfers()');
/** @var array $array */
$array = $this->repository->getTransactions($this->importJob);
$count = 0;
foreach ($array as $index => $group) {
foreach ($group['transactions'] as $transaction) {
if (strtolower(TransactionType::TRANSFER) === strtolower($transaction['type'])) {
$count++;
Log::debug(sprintf('Row #%d is a transfer, increase count to %d', $index + 1, $count));
}
}
}
Log::debug(sprintf('Count of transfers in import array is %d.', $count));
if ($count > 0) {
$this->checkForTransfers = true;
Log::debug('Will check for duplicate transfers.');
// get users transfers. Needed for comparison.
$this->getTransfers();
}
}
/**
* Get the users transfers, so they can be compared to whatever the user is trying to import.
*/
private function getTransfers(): void
{
Log::debug('Now in getTransfers()');
app('preferences')->mark();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->importJob->user);
$collector
->setTypes([TransactionType::TRANSFER])->setLimit(10000)->setPage(1)
->withAccountInformation();
$this->transfers = $collector->getExtractedJournals();
Log::debug(sprintf('Count of getTransfers() is %d', count($this->transfers)));
}
/**
* Actually does the storing. Does three things.
* - Store journals
@ -99,7 +150,7 @@ class ImportArrayStorage
{
// store transactions
$this->setStatus('storing_data');
$collection = $this->storeArray();
$collection = $this->storeGroupArray();
$this->setStatus('stored_data');
// link tag:
@ -124,70 +175,104 @@ class ImportArrayStorage
}
/**
* Applies the users rules to the created journals.
*
* @param Collection $collection
* Shorthand method to quickly set job status
*
* @param string $status
*/
private function applyRules(Collection $collection): void
private function setStatus(string $status): void
{
$rules = $this->getRules();
if ($rules->count() > 0) {
foreach ($collection as $journal) {
$rules->each(
function (Rule $rule) use ($journal) {
Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id));
/** @var Processor $processor */
$processor = app(Processor::class);
$processor->make($rule);
$processor->handleTransactionJournal($journal);
$journal->refresh();
if ($rule->stop_processing) {
return false;
}
return true;
}
);
}
}
$this->repository->setStatus($this->importJob, $status);
}
/**
* Count the number of transfers in the array. If this is zero, don't bother checking for double transfers.
* Store array as journals.
*
* @return Collection
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function countTransfers(): void
private function storeGroupArray(): Collection
{
Log::debug('Now in countTransfers()');
/** @var array $array */
$array = $this->repository->getTransactions($this->importJob);
$count = count($array);
Log::notice(sprintf('Will now store the groups. Count of groups is %d.', $count));
Log::notice('Going to store...');
$count = 0;
foreach ($array as $index => $transaction) {
if (strtolower(TransactionType::TRANSFER) === strtolower($transaction['type'])) {
$count++;
Log::debug(sprintf('Row #%d is a transfer, increase count to %d', $index + 1, $count));
$collection = new Collection;
foreach ($array as $index => $group) {
Log::debug(sprintf('Now store #%d', ($index + 1)));
$result = $this->storeGroup($index, $group);
if (null !== $result) {
$collection->push($result);
}
}
Log::debug(sprintf('Count of transfers in import array is %d.', $count));
if ($count > 0) {
$this->checkForTransfers = true;
Log::debug('Will check for duplicate transfers.');
// get users transfers. Needed for comparison.
$this->getTransfers();
}
Log::notice(sprintf('Done storing. Firefly III has stored %d transactions.', $collection->count()));
return $collection;
}
/**
* @param int $index
* @param array $transaction
* @param array $group
* @return TransactionGroup|null
*/
private function storeGroup(int $index, array $group): ?TransactionGroup
{
// do duplicate detection!
if ($this->duplicateDetected($index, $group)) {
Log::warning(sprintf('Row #%d seems to be a imported already and will be ignored.', $index));
return null;
}
Log::debug(sprintf('Going to store entry #%d', $index + 1));
// do some basic error catching.
foreach ($group['transactions'] as $index => $transaction) {
$group['transactions'][$index]['date'] = Carbon::parse($transaction['date'], config('app.timezone'));
$group['transactions'][$index]['description'] = '' === $transaction['description'] ? '(empty description)' : $transaction['description'];
}
// store the group
try {
$newGroup = $this->groupRepos->store($group);
} catch (FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage()));
return null;
}
Log::debug(sprintf('Stored as group #%d', $newGroup->id));
// add to collection of transfers, if necessary:
if ('transfer' === strtolower($group['transactions'][0]['type'])) {
$journals = $this->getTransactionFromJournal($newGroup);
Log::debug('We just stored a transfer, so add the journal to the list of transfers.');
foreach ($journals as $newJournal) {
$this->transfers[] = $newJournal;
}
Log::debug(sprintf('List length is now %d', count($this->transfers)));
}
return $newGroup;
}
/**
* @param int $index
* @param array $group
*
* @return bool
* @throws FireflyException
*/
private function duplicateDetected(int $index, array $transaction): bool
private function duplicateDetected(int $index, array $group): bool
{
$transactions = $group['transactions'] ?? [];
foreach ($transactions as $transaction) {
$hash = $this->getHash($transaction);
$existingId = $this->hashExists($hash);
if (null !== $existingId) {
@ -206,6 +291,7 @@ class ImportArrayStorage
return true;
}
}
return false;
}
@ -215,7 +301,6 @@ class ImportArrayStorage
*
* @param array $transaction
*
* @throws FireflyException
* @return string
*/
private function getHash(array $transaction): string
@ -226,7 +311,12 @@ class ImportArrayStorage
// @codeCoverageIgnoreStart
/** @noinspection ForgottenDebugOutputInspection */
Log::error('Could not encode import array.', $transaction);
throw new FireflyException('Could not encode import array. Please see the logs.');
try {
$json = random_int(1, 10000);
} catch (Exception $e) {
// seriously?
Log::error(sprintf('random_int() just failed. I want a medal: %s', $e->getMessage()));
}
// @codeCoverageIgnoreEnd
}
$hash = hash('sha256', $json);
@ -235,72 +325,6 @@ class ImportArrayStorage
return $hash;
}
/**
* Gets the users rules.
*
* @return Collection
*/
private function getRules(): Collection
{
/** @var RuleRepositoryInterface $repository */
$repository = app(RuleRepositoryInterface::class);
$repository->setUser($this->importJob->user);
$set = $repository->getForImport();
Log::debug(sprintf('Found %d user rules.', $set->count()));
return $set;
}
/**
* @param $journal
*
* @return Transaction
*/
private function getTransactionFromJournal($journal): Transaction
{
// collect transactions using the journal collector
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($this->importJob->user);
$collector->withOpposingAccount();
// filter on specific journals.
$collector->setJournals(new Collection([$journal]));
// add filter to remove transactions:
$transactionType = $journal->transactionType->type;
if ($transactionType === TransactionType::WITHDRAWAL) {
$collector->addFilter(PositiveAmountFilter::class);
}
if (!($transactionType === TransactionType::WITHDRAWAL)) {
$collector->addFilter(NegativeAmountFilter::class);
}
/** @var Transaction $result */
$result = $collector->getTransactions()->first();
Log::debug(sprintf('Return transaction #%d with journal id #%d based on ID #%d', $result->id, $result->journal_id, $journal->id));
return $result;
}
/**
* Get the users transfers, so they can be compared to whatever the user is trying to import.
*/
private function getTransfers(): void
{
Log::debug('Now in getTransfers()');
app('preferences')->mark();
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($this->importJob->user);
$collector->setAllAssetAccounts()
->ignoreCache()
->setTypes([TransactionType::TRANSFER])
->withOpposingAccount();
$collector->removeFilter(InternalTransferFilter::class);
$this->transfers = $collector->getTransactions();
Log::debug(sprintf('Count of getTransfers() is %d', $this->transfers->count()));
}
/**
* Check if the hash exists for the array the user wants to import.
*
@ -321,6 +345,188 @@ class ImportArrayStorage
return (int)$entry->transaction_journal_id;
}
/**
* Log about a duplicate object (double hash).
*
* @param array $transaction
* @param int $existingId
*/
private function logDuplicateObject(array $transaction, int $existingId): void
{
Log::info(
'Transaction is a duplicate, and will not be imported (the hash exists).',
[
'existing' => $existingId,
'description' => $transaction['description'] ?? '',
'amount' => $transaction['transactions'][0]['amount'] ?? 0,
'date' => $transaction['date'] ?? '',
]
);
}
/**
* Check if a transfer exists.
*
* @param array $transaction
*
* @return bool
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
private function transferExists(array $transaction): bool
{
Log::debug('Check if transaction is a double transfer.');
// how many hits do we need?
Log::debug(sprintf('System has %d existing transfers', count($this->transfers)));
// loop over each split:
// check if is a transfer
if (strtolower(TransactionType::TRANSFER) !== strtolower($transaction['type'])) {
Log::debug(sprintf('Is a %s, not a transfer so no.', $transaction['type']));
return false;
}
Log::debug(sprintf('Required hits for transfer comparison is %d', self::REQUIRED_HITS));
// get the amount:
/** @noinspection UnnecessaryCastingInspection */
$amount = (string)($transaction['amount'] ?? '0');
if (bccomp($amount, '0') === -1) {
$amount = bcmul($amount, '-1'); // @codeCoverageIgnore
}
// get the description:
$description = '' === (string)$transaction['description'] ? $transaction['description'] : $transaction['description'];
// get the source and destination ID's:
$transactionSourceIDs = [(int)$transaction['source_id'], (int)$transaction['destination_id']];
sort($transactionSourceIDs);
// get the source and destination names:
$transactionSourceNames = [(string)$transaction['source_name'], (string)$transaction['destination_name']];
sort($transactionSourceNames);
// then loop all transfers:
/** @var array $transfer */
foreach ($this->transfers as $transfer) {
// number of hits for this split-transfer combination:
$hits = 0;
Log::debug(sprintf('Now looking at transaction journal #%d', $transfer['transaction_journal_id']));
// compare amount:
$originalAmount = app('steam')->positive($transfer['amount']);
Log::debug(sprintf('Amount %s compared to %s', $amount, $originalAmount));
if (0 !== bccomp($amount, $originalAmount)) {
Log::debug('Amount is not a match, continue with next transfer.');
continue;
}
++$hits;
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
// compare description:
$comparison = '(empty description)' === $transfer['description'] ? '' : $transfer['description'];
Log::debug(sprintf('Comparing "%s" to "%s" (original: "%s")', $description, $transfer['description'], $comparison));
if ($description !== $comparison) {
Log::debug('Description is not a match, continue with next transfer.');
continue; // @codeCoverageIgnore
}
++$hits;
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
// compare date:
$transferDate = $transfer['date']->format('Y-m-d H:i:s');
Log::debug(sprintf('Comparing dates "%s" to "%s"', $transaction['date'], $transferDate));
if ($transaction['date'] !== $transferDate) {
Log::debug('Date is not a match, continue with next transfer.');
continue; // @codeCoverageIgnore
}
++$hits;
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
// compare source and destination id's
$transferSourceIDs = [(int)$transfer['source_account_id'], (int)$transfer['destination_account_id']];
sort($transferSourceIDs);
/** @noinspection DisconnectedForeachInstructionInspection */
Log::debug('Comparing current transaction source+dest IDs', $transactionSourceIDs);
Log::debug('.. with current transfer source+dest IDs', $transferSourceIDs);
if ($transactionSourceIDs === $transferSourceIDs) {
++$hits;
Log::debug(sprintf('Source IDs are the same! (%d)', $hits));
}
if ($transactionSourceIDs !== $transactionSourceIDs) {
Log::debug('Source IDs are not the same.');
}
unset($transferSourceIDs);
// compare source and destination names
$transferSource = [(string)($transfer['source_account_name'] ?? ''), (string)($transfer['destination_account_name'] ?? '')];
sort($transferSource);
/** @noinspection DisconnectedForeachInstructionInspection */
Log::debug('Comparing current transaction source+dest names', $transactionSourceNames);
Log::debug('.. with current transfer source+dest names', $transferSource);
if ($transactionSourceNames === $transferSource) {
// @codeCoverageIgnoreStart
++$hits;
Log::debug(sprintf('Source names are the same! (%d)', $hits));
// @codeCoverageIgnoreEnd
}
if ($transactionSourceNames !== $transferSource) {
Log::debug('Source names are not the same.');
}
Log::debug(sprintf('Number of hits is %d', $hits));
if ($hits >= self::REQUIRED_HITS) {
Log::debug(sprintf('Is more than %d, return true.', self::REQUIRED_HITS));
return true;
}
}
Log::debug('Is not an existing transfer, return false.');
return false;
}
/**
* Log about a duplicate transfer.
*
* @param array $transaction
*/
private function logDuplicateTransfer(array $transaction): void
{
Log::info(
'Transaction is a duplicate transfer, and will not be imported (such a transfer exists already).',
[
'description' => $transaction['description'] ?? '',
'amount' => $transaction['transactions'][0]['amount'] ?? 0,
'date' => $transaction['date'] ?? '',
]
);
}
/**
* @param TransactionGroup $transactionGroup
*
* @return array
*/
private function getTransactionFromJournal(TransactionGroup $transactionGroup): array
{
// collect transactions using the journal collector
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->importJob->user);
$collector->setGroup($transactionGroup);
$result = $collector->getExtractedJournals();
return $result;
}
/**
* Link all imported journals to a tag.
*
@ -365,248 +571,50 @@ class ImportArrayStorage
}
/**
* Log about a duplicate object (double hash).
* Applies the users rules to the created journals.
*
* @param Collection $collection
*
* @param array $transaction
* @param int $existingId
*/
private function logDuplicateObject(array $transaction, int $existingId): void
private function applyRules(Collection $collection): void
{
Log::info(
'Transaction is a duplicate, and will not be imported (the hash exists).',
[
'existing' => $existingId,
'description' => $transaction['description'] ?? '',
'amount' => $transaction['transactions'][0]['amount'] ?? 0,
'date' => $transaction['date'] ?? '',
]
);
}
/**
* Log about a duplicate transfer.
*
* @param array $transaction
*/
private function logDuplicateTransfer(array $transaction): void
{
Log::info(
'Transaction is a duplicate transfer, and will not be imported (such a transfer exists already).',
[
'description' => $transaction['description'] ?? '',
'amount' => $transaction['transactions'][0]['amount'] ?? 0,
'date' => $transaction['date'] ?? '',
]
);
}
/**
* Shorthand method to quickly set job status
*
* @param string $status
*/
private function setStatus(string $status): void
{
$this->repository->setStatus($this->importJob, $status);
}
/**
* Store array as journals.
*
* @return Collection
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function storeArray(): Collection
{
/** @var array $array */
$array = $this->repository->getTransactions($this->importJob);
$count = count($array);
$toStore = [];
Log::notice(sprintf('Will now store the transactions. Count of items is %d.', $count));
/*
* Detect duplicates in initial array:
*/
foreach ($array as $index => $transaction) {
Log::debug(sprintf('Now at item %d out of %d', $index + 1, $count));
if ($this->duplicateDetected($index, $transaction)) {
Log::warning(sprintf('Row #%d seems to be a duplicate entry and will be ignored.', $index));
continue;
}
$transaction['import_hash_v2'] = $this->getHash($transaction);
$toStore[] = $transaction;
}
$count = count($toStore);
if (0 === $count) {
Log::info('No transactions to store left!');
return new Collection;
}
Log::notice(sprintf('After a first check for duplicates, the count of items is %d.', $count));
Log::notice('Going to store...');
// now actually store them:
$collection = new Collection;
foreach ($toStore as $index => $store) {
// do duplicate detection again!
if ($this->duplicateDetected($index, $store)) {
Log::warning(sprintf('Row #%d seems to be a imported already and will be ignored.', $index), $store);
continue;
}
Log::debug(sprintf('Going to store entry %d of %d', $index + 1, $count));
// convert the date to an object:
$store['date'] = Carbon::parse($store['date'], config('app.timezone'));
$store['description'] = '' === $store['description'] ? '(empty description)' : $store['description'];
// store the journal.
try {
$journal = $this->journalRepos->store($store);
} catch (FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage()));
continue;
}
Log::info(sprintf('Stored #%d: "%s" (ID #%d)', $index, $journal->description, $journal->id));
Log::debug(sprintf('Stored as journal #%d', $journal->id));
$collection->push($journal);
// add to collection of transfers, if necessary:
if ('transfer' === strtolower($store['type'])) {
$transaction = $this->getTransactionFromJournal($journal);
Log::debug('We just stored a transfer, so add the journal to the list of transfers.');
$this->transfers->push($transaction);
Log::debug(sprintf('List length is now %d', $this->transfers->count()));
}
}
Log::notice(sprintf('Done storing. Firefly III has stored %d transactions.', $collection->count()));
return $collection;
}
/**
* Check if a transfer exists.
*
* @param $transaction
*
* @return bool
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
private function transferExists(array $transaction): bool
{
Log::debug('Check if array is a double transfer.');
if (strtolower(TransactionType::TRANSFER) !== strtolower($transaction['type'])) {
Log::debug(sprintf('Is a %s, not a transfer so no.', $transaction['type']));
$rules = $this->getRules();
if ($rules->count() > 0) {
foreach ($collection as $journal) {
$rules->each(
function (Rule $rule) use ($journal) {
Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id));
/** @var Processor $processor */
$processor = app(Processor::class);
$processor->make($rule);
$processor->handleTransactionJournal($journal);
$journal->refresh();
if ($rule->stop_processing) {
return false;
}
// how many hits do we need?
Log::debug(sprintf('Array has %d transactions.', count($transaction['transactions'])));
Log::debug(sprintf('System has %d existing transfers', count($this->transfers)));
// loop over each split:
Log::debug(sprintf('This transfer has %d split(s)', count($transaction['transactions'])));
foreach ($transaction['transactions'] as $index => $current) {
Log::debug(sprintf('Required hits for transfer comparison is %d', self::REQUIRED_HITS));
Log::debug(sprintf('Now at transfer split %d of %d', $index + 1, count($transaction['transactions'])));
// get the amount:
/** @noinspection UnnecessaryCastingInspection */
$amount = (string)($current['amount'] ?? '0');
if (bccomp($amount, '0') === -1) {
$amount = bcmul($amount, '-1'); // @codeCoverageIgnore
}
// get the description:
$description = '' === (string)$current['description'] ? $transaction['description'] : $current['description'];
// get the source and destination ID's:
$currentSourceIDs = [(int)$current['source_id'], (int)$current['destination_id']];
sort($currentSourceIDs);
// get the source and destination names:
$currentSourceNames = [(string)$current['source_name'], (string)$current['destination_name']];
sort($currentSourceNames);
// then loop all transfers:
/** @var Transaction $transfer */
foreach ($this->transfers as $transfer) {
// number of hits for this split-transfer combination:
$hits = 0;
Log::debug(sprintf('Now looking at transaction journal #%d', $transfer->journal_id));
// compare amount:
Log::debug(sprintf('Amount %s compared to %s', $amount, $transfer->transaction_amount));
if (0 !== bccomp($amount, $transfer->transaction_amount)) {
Log::debug('Amount is not a match, continue with next transfer.');
continue;
}
++$hits;
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
// compare description:
$comparison = '(empty description)' === $transfer->description ? '' : $transfer->description;
Log::debug(sprintf('Comparing "%s" to "%s" (original: "%s")', $description, $transfer->description, $comparison));
if ($description !== $comparison) {
Log::debug('Description is not a match, continue with next transfer.');
continue; // @codeCoverageIgnore
}
++$hits;
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
// compare date:
$transferDate = $transfer->date->format('Y-m-d H:i:s');
Log::debug(sprintf('Comparing dates "%s" to "%s"', $transaction['date'], $transferDate));
if ($transaction['date'] !== $transferDate) {
Log::debug('Date is not a match, continue with next transfer.');
continue; // @codeCoverageIgnore
}
++$hits;
Log::debug(sprintf('Comparison is a hit! (%s)', $hits));
// compare source and destination id's
$transferSourceIDs = [(int)$transfer->account_id, (int)$transfer->opposing_account_id];
sort($transferSourceIDs);
/** @noinspection DisconnectedForeachInstructionInspection */
Log::debug('Comparing current transaction source+dest IDs', $currentSourceIDs);
Log::debug('.. with current transfer source+dest IDs', $transferSourceIDs);
if ($currentSourceIDs === $transferSourceIDs) {
++$hits;
Log::debug(sprintf('Source IDs are the same! (%d)', $hits));
}
Log::debug('Source IDs are not the same.');
unset($transferSourceIDs);
// compare source and destination names
$transferSource = [(string)$transfer->account_name, (string)$transfer->opposing_account_name];
sort($transferSource);
/** @noinspection DisconnectedForeachInstructionInspection */
Log::debug('Comparing current transaction source+dest names', $currentSourceNames);
Log::debug('.. with current transfer source+dest names', $transferSource);
if ($currentSourceNames === $transferSource) {
// @codeCoverageIgnoreStart
++$hits;
Log::debug(sprintf('Source names are the same! (%d)', $hits));
// @codeCoverageIgnoreEnd
}
Log::debug('Source names are not the same.');
Log::debug(sprintf('Number of hits is %d', $hits));
if ($hits >= self::REQUIRED_HITS) {
Log::debug(sprintf('Is more than %d, return true.', self::REQUIRED_HITS));
return true;
}
);
}
}
}
Log::debug('Is not an existing transfer, return false.');
return false;
/**
* Gets the users rules.
*
* @return Collection
*/
private function getRules(): Collection
{
/** @var RuleRepositoryInterface $repository */
$repository = app(RuleRepositoryInterface::class);
$repository->setUser($this->importJob->user);
$set = $repository->getForImport();
Log::debug(sprintf('Found %d user rules.', $set->count()));
return $set;
}
}

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\RuleGroup;
use FireflyIII\TransactionRules\Processor;
use FireflyIII\User;
@ -34,6 +34,7 @@ use Illuminate\Support\Collection;
/**
* Class ExecuteRuleGroupOnExistingTransactions.
* TODO make sure this job honors the "stop_processing" rules.
*/
class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
{
@ -148,19 +149,20 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
public function handle()
{
// Lookup all journals that match the parameters specified
$transactions = $this->collectJournals();
$journals = $this->collectJournals();
// Find processors for each rule within the current rule group
$processors = $this->collectProcessors();
// Execute the rules for each transaction
foreach ($processors as $processor) {
foreach ($transactions as $transaction) {
/** @var array $journal */
foreach ($journals as $journal) {
/** @var Processor $processor */
$processor->handleTransaction($transaction);
$processor->handleJournalArray($journal);
}
// Stop processing this group if the rule specifies 'stop_processing'
// TODO Fix this.
if ($processor->getRule()->stop_processing) {
break;
}
@ -170,16 +172,16 @@ class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue
/**
* Collect all journals that should be processed.
*
* @return Collection
* @return array
*/
protected function collectJournals(): Collection
protected function collectJournals(): array
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAccounts($this->accounts)->setRange($this->startDate, $this->endDate);
return $collector->getTransactions();
return $collector->getExtractedJournals();
}
/**

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Rule;
use FireflyIII\TransactionRules\Processor;
use FireflyIII\User;
@ -161,7 +161,7 @@ class ExecuteRuleOnExistingTransactions extends Job implements ShouldQueue
public function handle()
{
// Lookup all journals that match the parameters specified
$transactions = $this->collectJournals();
$journals = $this->collectJournals();
/** @var Processor $processor */
$processor = app(Processor::class);
$processor->make($this->rule, true);
@ -169,9 +169,10 @@ class ExecuteRuleOnExistingTransactions extends Job implements ShouldQueue
$misses = 0;
$total = 0;
// Execute the rules for each transaction
foreach ($transactions as $transaction) {
/** @var array $journal */
foreach ($journals as $journal) {
++$total;
$result = $processor->handleTransaction($transaction);
$result = $processor->handleJournalArray($journal);
if ($result) {
++$hits;
}
@ -186,15 +187,16 @@ class ExecuteRuleOnExistingTransactions extends Job implements ShouldQueue
/**
* Collect all journals that should be processed.
*
* @return Collection
* @return array
*/
protected function collectJournals(): Collection
protected function collectJournals(): array
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAccounts($this->accounts)->setRange($this->startDate, $this->endDate);
return $collector->getTransactions();
return $collector->getExtractedJournals();
}
}

View File

@ -24,9 +24,7 @@ namespace FireflyIII\Repositories\Account;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\User;

View File

@ -27,14 +27,12 @@ use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@ -164,6 +162,7 @@ class BudgetRepository implements BudgetRepositoryInterface
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
return $collector->getSum();
}
@ -556,18 +555,19 @@ class BudgetRepository implements BudgetRepositoryInterface
}
// get all transactions:
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setBudgets($budgets);
$transactions = $collector->getTransactions();
$journals = $collector->getExtractedJournals();
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$budgetId = max((int)$transaction->transaction_journal_budget_id, (int)$transaction->transaction_budget_id);
$date = $transaction->date->format($carbonFormat);
$data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $transaction->transaction_amount);
/** @var array $journal */
foreach ($journals as $journal) {
$budgetId = (int)$journal['budget_id'];
$date = $journal['date']->format($carbonFormat);
$data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $journal['amount']);
}
return $data;
@ -623,25 +623,28 @@ class BudgetRepository implements BudgetRepositoryInterface
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionType::WITHDRAWAL]);
$collector->withoutBudget();
$transactions = $collector->getTransactions();
$journals = $collector->getExtractedJournals();
$result = [
'entries' => [],
'name' => (string)trans('firefly.no_budget'),
'sum' => '0',
];
foreach ($transactions as $transaction) {
$date = $transaction->date->format($carbonFormat);
/** @var array $journal */
foreach ($journals as $journal) {
$date = $journal['date']->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
$result['entries'][$date] = bcadd($result['entries'][$date], $journal['amount']);
}
return $result;
@ -794,39 +797,41 @@ class BudgetRepository implements BudgetRepositoryInterface
*/
public function spentInPeriodWoBudgetMc(Collection $accounts, Carbon $start, Carbon $end): array
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutBudget();
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (0 === $accounts->count()) {
$collector->setAllAssetAccounts();
}
$set = $collector->getTransactions();
$journals = $collector->getExtractedJournals();
$return = [];
$total = [];
$currencies = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$code = $transaction->transaction_currency_code;
/** @var array $journal */
foreach ($journals as $journal) {
$code = $journal['currency_code'];
if (!isset($currencies[$code])) {
$currencies[$code] = $transaction->transactionCurrency;
$currencies[$code] = [
'id' => $journal['currency_id'],
'name' => $journal['currency_name'],
'symbol' => $journal['currency_symbol'],
'decimal_places' => $journal['currency_decimal_places'],
];
}
$total[$code] = isset($total[$code]) ? bcadd($total[$code], $transaction->transaction_amount) : $transaction->transaction_amount;
$total[$code] = isset($total[$code]) ? bcadd($total[$code], $journal['amount']) : $journal['amount'];
}
foreach ($total as $code => $spent) {
/** @var TransactionCurrency $currency */
$currency = $currencies[$code];
$return[] = [
'currency_id' => $currency->id,
'currency_id' => $currency['id'],
'currency_code' => $code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'amount' => round($spent, $currency->decimal_places),
'currency_symbol' => $currency['symbol'],
'currency_decimal_places' => $currency['decimal_places'],
'amount' => round($spent, $currency['decimal_places']),
];
}

View File

@ -25,9 +25,7 @@ namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use FireflyIII\Factory\CategoryFactory;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Services\Internal\Destroy\CategoryDestroyService;
use FireflyIII\Services\Internal\Update\CategoryUpdateService;
@ -142,43 +140,30 @@ class CategoryRepository implements CategoryRepositoryInterface
*/
public function earnedInPeriodPcWoCategory(Collection $accounts, Carbon $start, Carbon $end): array
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->withoutCategory();
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (0 === $accounts->count()) {
$collector->setAllAssetAccounts();
}
$set = $collector->getTransactions();
$set = $set->filter(
function (Transaction $transaction) {
if (bccomp($transaction->transaction_amount, '0') === 1) {
return $transaction;
}
return null;
}
);
$journals = $collector->getExtractedJournals();
$return = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
if (!isset($return[$currencyId])) {
$return[$currencyId] = [
'spent' => '0',
'currency_id' => $currencyId,
'currency_symbol' => $transaction->transaction_currency_symbol,
'currency_code' => $transaction->transaction_currency_code,
'currency_decimal_places' => $transaction->transaction_currency_dp,
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
}
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $transaction->transaction_amount);
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $journal['amount']);
}
return $return;
@ -194,8 +179,9 @@ class CategoryRepository implements CategoryRepositoryInterface
*/
public function earnedInPeriodPerCurrency(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]);
@ -209,20 +195,12 @@ class CategoryRepository implements CategoryRepositoryInterface
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (0 === $accounts->count()) {
$collector->setAllAssetAccounts();
}
$set = $collector->getTransactions();
$journals = $collector->getExtractedJournals();
$return = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$jrnlCatId = (int)$transaction->transaction_journal_category_id;
$transCatId = (int)$transaction->transaction_category_id;
$categoryId = max($jrnlCatId, $transCatId);
$currencyId = (int)$transaction->transaction_currency_id;
$name = $transaction->transaction_category_name;
$name = '' === (string)$name ? $transaction->transaction_journal_category_name : $name;
foreach ($journals as $journal) {
$categoryId = (int)$journal['category_id'];
$currencyId = (int)$journal['currency_id'];
$name = $journal['category_name'];
// make array for category:
if (!isset($return[$categoryId])) {
$return[$categoryId] = [
@ -234,13 +212,13 @@ class CategoryRepository implements CategoryRepositoryInterface
$return[$categoryId]['earned'][$currencyId] = [
'earned' => '0',
'currency_id' => $currencyId,
'currency_symbol' => $transaction->transaction_currency_symbol,
'currency_code' => $transaction->transaction_currency_code,
'currency_decimal_places' => $transaction->transaction_currency_dp,
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
}
$return[$categoryId]['earned'][$currencyId]['earned']
= bcadd($return[$categoryId]['earned'][$currencyId]['earned'], $transaction->transaction_amount);
= bcadd($return[$categoryId]['earned'][$currencyId]['earned'], $journal['amount']);
}
return $return;
@ -521,23 +499,20 @@ class CategoryRepository implements CategoryRepositoryInterface
}
// get all transactions:
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setCategories($categories)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
->withOpposingAccount();
$transactions = $collector->getTransactions();
->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals();
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
// if positive, skip:
if (1 === bccomp($transaction->transaction_amount, '0')) {
continue;
}
$categoryId = max((int)$transaction->transaction_journal_category_id, (int)$transaction->transaction_category_id);
$date = $transaction->date->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount);
foreach ($journals as $journal) {
$categoryId = (int)$journal['category_id'];
$date = $journal['date']->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $journal['amount']);
}
return $data;
@ -553,29 +528,28 @@ class CategoryRepository implements CategoryRepositoryInterface
public function periodExpensesNoCategory(Collection $accounts, Carbon $start, Carbon $end): array
{
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->withOpposingAccount();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->withAccountInformation();
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]);
$collector->withoutCategory();
$transactions = $collector->getTransactions();
$journals = $collector->getExtractedJournals();
$result = [
'entries' => [],
'name' => (string)trans('firefly.no_category'),
'sum' => '0',
];
foreach ($transactions as $transaction) {
// if positive, skip:
if (1 === bccomp($transaction->transaction_amount, '0')) {
continue;
}
$date = $transaction->date->format($carbonFormat);
/** @var array $journal */
foreach ($journals as $journal) {
$date = $journal['date']->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
$result['entries'][$date] = bcadd($result['entries'][$date], $journal['amount']);
}
return $result;
@ -604,23 +578,20 @@ class CategoryRepository implements CategoryRepositoryInterface
}
// get all transactions:
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setCategories($categories)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER])
->withOpposingAccount();
$transactions = $collector->getTransactions();
->withAccountInformation();
$journals = $collector->getExtractedJournals();
// loop transactions:
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
// if negative, skip:
if (bccomp($transaction->transaction_amount, '0') === -1) {
continue;
}
$categoryId = max((int)$transaction->transaction_journal_category_id, (int)$transaction->transaction_category_id);
$date = $transaction->date->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount);
/** @var array $journal */
foreach ($journals as $journal) {
$categoryId = (int)$journal['category_id'];
$date = $journal['date']->format($carbonFormat);
$data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $journal['amount']);
}
return $data;
@ -637,29 +608,28 @@ class CategoryRepository implements CategoryRepositoryInterface
{
Log::debug('Now in periodIncomeNoCategory()');
$carbonFormat = Navigation::preferredCarbonFormat($start, $end);
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->withOpposingAccount();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->withAccountInformation();
$collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]);
$collector->withoutCategory();
$transactions = $collector->getTransactions();
$journals = $collector->getExtractedJournals();
$result = [
'entries' => [],
'name' => (string)trans('firefly.no_category'),
'sum' => '0',
];
Log::debug('Looping transactions..');
foreach ($transactions as $transaction) {
// if negative, skip:
if (bccomp($transaction->transaction_amount, '0') === -1) {
continue;
}
$date = $transaction->date->format($carbonFormat);
foreach ($journals as $journal) {
$date = $journal['date']->format($carbonFormat);
if (!isset($result['entries'][$date])) {
$result['entries'][$date] = '0';
}
$result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount);
$result['entries'][$date] = bcadd($result['entries'][$date], $journal['amount']);
}
Log::debug('Done looping transactions..');
Log::debug('Finished periodIncomeNoCategory()');
@ -714,6 +684,8 @@ class CategoryRepository implements CategoryRepositoryInterface
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setCategories($categories);
@ -750,7 +722,7 @@ class CategoryRepository implements CategoryRepositoryInterface
$set = $collector->getExtractedJournals();
$return = [];
/** @var array $journal */
foreach($set as $journal) {
foreach ($set as $journal) {
$currencyId = (int)$journal['currency_id'];
if (!isset($return[$currencyId])) {
$return[$currencyId] = [
@ -833,30 +805,17 @@ class CategoryRepository implements CategoryRepositoryInterface
*/
public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end): string
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutCategory();
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (0 === $accounts->count()) {
$collector->setAllAssetAccounts();
}
$set = $collector->getTransactions();
$set = $set->filter(
function (Transaction $transaction) {
if (bccomp($transaction->transaction_amount, '0') === -1) {
return $transaction;
}
return null;
}
);
return (string)$set->sum('transaction_amount');
return $collector->getSum();
}
/**

View File

@ -25,11 +25,6 @@ namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use DB;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionGroupFactory;
use FireflyIII\Factory\TransactionJournalFactory;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Note;
@ -386,6 +381,35 @@ class JournalRepository implements JournalRepositoryInterface
return '';
}
/**
* Return Carbon value of a meta field (or NULL).
*
* @param TransactionJournal $journal
* @param string $field
*
* @return null|Carbon
*/
public function getMetaDate(TransactionJournal $journal, string $field): ?Carbon
{
$cache = new CacheProperties;
$cache->addProperty('journal-meta-updated');
$cache->addProperty($journal->id);
$cache->addProperty($field);
if ($cache->has()) {
return new Carbon($cache->get()); // @codeCoverageIgnore
}
$entry = $journal->transactionJournalMeta()->where('name', $field)->first();
if (null === $entry) {
return null;
}
$value = new Carbon($entry->data);
$cache->store($entry->data);
return $value;
}
/**
* Return a list of all destination accounts related to journal.
*
@ -493,35 +517,6 @@ class JournalRepository implements JournalRepositoryInterface
return '';
}
/**
* Return Carbon value of a meta field (or NULL).
*
* @param TransactionJournal $journal
* @param string $field
*
* @return null|Carbon
*/
public function getMetaDate(TransactionJournal $journal, string $field): ?Carbon
{
$cache = new CacheProperties;
$cache->addProperty('journal-meta-updated');
$cache->addProperty($journal->id);
$cache->addProperty($field);
if ($cache->has()) {
return new Carbon($cache->get()); // @codeCoverageIgnore
}
$entry = $journal->transactionJournalMeta()->where('name', $field)->first();
if (null === $entry) {
return null;
}
$value = new Carbon($entry->data);
$cache->store($entry->data);
return $value;
}
/**
* Return string value of a meta date (or NULL).
*
@ -666,33 +661,6 @@ class JournalRepository implements JournalRepositoryInterface
return $journal->transactionType->type;
}
/**
* @param array $transactionIds
*
* @return Collection
*/
public function getTransactionsById(array $transactionIds): Collection
{
$journalIds = Transaction::whereIn('id', $transactionIds)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
$journals = new Collection;
foreach ($journalIds as $journalId) {
$result = $this->findNull((int)$journalId);
if (null !== $result) {
$journals->push($result);
}
}
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAllAssetAccounts();
$collector->removeFilter(InternalTransferFilter::class);
//$collector->addFilter(TransferFilter::class);
$collector->setJournals($journals)->withOpposingAccount();
return $collector->getTransactions();
}
/**
* Will tell you if journal is reconciled or not.
*
@ -711,6 +679,22 @@ class JournalRepository implements JournalRepositoryInterface
return false;
}
/**
* @param int $transactionId
*
* @return bool
*/
public function reconcileById(int $transactionId): bool
{
/** @var Transaction $transaction */
$transaction = $this->user->transactions()->find($transactionId);
if (null !== $transaction) {
return $this->reconcile($transaction);
}
return false;
}
/**
* @param Transaction $transaction
*
@ -736,22 +720,6 @@ class JournalRepository implements JournalRepositoryInterface
return true;
}
/**
* @param int $transactionId
*
* @return bool
*/
public function reconcileById(int $transactionId): bool
{
/** @var Transaction $transaction */
$transaction = $this->user->transactions()->find($transactionId);
if (null !== $transaction) {
return $this->reconcile($transaction);
}
return false;
}
/**
* @param TransactionJournal $journal
* @param int $order

View File

@ -276,13 +276,6 @@ interface JournalRepositoryInterface
*/
public function getTransactionType(TransactionJournal $journal): string;
/**
* @param array $transactionIds
*
* @return Collection
*/
public function getTransactionsById(array $transactionIds): Collection;
/**
* Will tell you if journal is reconciled or not.
*
@ -319,8 +312,6 @@ interface JournalRepositoryInterface
*/
public function setUser(User $user);
/**
* Update budget for a journal.
*

View File

@ -26,8 +26,7 @@ namespace FireflyIII\Repositories\Recurring;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\RecurrenceFactory;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Note;
use FireflyIII\Models\Preference;
use FireflyIII\Models\Recurrence;
@ -290,25 +289,27 @@ class RecurringRepository implements RecurringRepositoryInterface
->get()->pluck('transaction_journal_id')->toArray();
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = ['id' => (int)$journalId];
$search[] = (int)$journalId;
}
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($recurrence->user);
$collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page);
// filter on specific journals.
$collector->removeFilter(InternalTransferFilter::class);
$collector->setJournals(new Collection($search));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
return $collector->getPaginatedTransactions();
$collector->setUser($recurrence->user);
$collector->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page)
->withAccountInformation();
$collector->setJournalIds($search);
return $collector->getPaginatedGroups();
}
/**
* TODO check usage and verify it still works.
*
* @param Recurrence $recurrence
*
* @return Collection
*/
public function getTransactions(Recurrence $recurrence): Collection
public function getTransactions(Recurrence $recurrence): array
{
$journalMeta = TransactionJournalMeta
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
@ -319,17 +320,17 @@ class RecurringRepository implements RecurringRepositoryInterface
->get()->pluck('transaction_journal_id')->toArray();
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = ['id' => (int)$journalId];
$search[] = (int)$journalId;
}
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($recurrence->user);
$collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation();
// filter on specific journals.
$collector->removeFilter(InternalTransferFilter::class);
$collector->setJournals(new Collection($search));
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
return $collector->getTransactions();
$collector->setUser($recurrence->user);
$collector->withCategoryInformation()->withBudgetInformation()->withAccountInformation();
// filter on specific journals.
$collector->setJournalIds($search);
return $collector->getExtractedJournals();
}
/**

View File

@ -141,9 +141,9 @@ interface RecurringRepositoryInterface
/**
* @param Recurrence $recurrence
*
* @return Collection
* @return array
*/
public function getTransactions(Recurrence $recurrence): Collection;
public function getTransactions(Recurrence $recurrence): array;
/**
* Calculate the next X iterations starting on the date given in $date.

View File

@ -26,7 +26,6 @@ use Carbon\Carbon;
use DB;
use FireflyIII\Factory\TagFactory;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
@ -84,13 +83,13 @@ class TagRepository implements TagRepositoryInterface
*/
public function earnedInPeriod(Tag $tag, Carbon $start, Carbon $end): string
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAllAssetAccounts()->setTag($tag);
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
return (string)$set->sum('transaction_amount');
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setTag($tag);
return $collector->getSum();
}
/**
@ -167,7 +166,7 @@ class TagRepository implements TagRepositoryInterface
public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): array
{
/** @var GroupCollectorInterface $collector */
$collector =app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setTag($tag);
@ -244,13 +243,13 @@ class TagRepository implements TagRepositoryInterface
*/
public function spentInPeriod(Tag $tag, Carbon $start, Carbon $end): string
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAllAssetAccounts()->setTag($tag);
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
return (string)$set->sum('transaction_amount');
$collector->setUser($this->user);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setTag($tag);
return $collector->getSum();
}
/**

View File

@ -25,12 +25,10 @@ namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
@ -86,22 +84,19 @@ trait AugumentData
*/
protected function earnedByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$total = $assets->merge($opposing);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total);
$collector->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = (int)$transaction->transaction_category_id;
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = (int)$transaction->transaction_journal_category_id;
}
foreach ($journals as $journal) {
$currencyId = $journal['currency_id'];
$categoryName = $journal['category_name'];
$categoryId = (int)$journal['category_id'];
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
@ -116,8 +111,8 @@ trait AugumentData
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
'symbol' => $journal['currency_symbol'],
'dp' => $journal['currency_decimal_places'],
],
],
],
@ -126,9 +121,9 @@ trait AugumentData
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $journal['amount']
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $journal['amount']);
}
return $sum;
@ -146,33 +141,34 @@ trait AugumentData
*/
protected function earnedInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$total = $assets->merge($opposing);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total);
$journals = $collector->getExtractedJournals();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
'symbol' => $journal['currency_symbol'],
'decimal_places' => $journal['currency_decimal_places'],
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $journal['amount']);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $journal['amount']);
}
return $sum;
@ -525,19 +521,27 @@ trait AugumentData
/**
* Group set of transactions by name of opposing account.
*
* @param Collection $set
* @param array $array
*
* @return array
*/
protected function groupByName(Collection $set): array // filter + group data
protected function groupByName(array $array): array // filter + group data
{
// group by opposing account name.
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$name = $transaction->opposing_account_name;
/** @var array $journal */
foreach ($array as $journal) {
$name = '(no name)';
if (TransactionType::WITHDRAWAL === $journal['transaction_type_type']) {
$name = $journal['destination_account_name'];
}
if (TransactionType::WITHDRAWAL !== $journal['transaction_type_type']) {
$name = $journal['source_account_name'];
}
$grouped[$name] = $grouped[$name] ?? '0';
$grouped[$name] = bcadd($transaction->transaction_amount, $grouped[$name]);
$grouped[$name] = bcadd($journal['amount'], $grouped[$name]);
}
return $grouped;
@ -587,22 +591,18 @@ trait AugumentData
*/
protected function spentByBudget(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withBudgetInformation();
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$total = $assets->merge($opposing);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($total);
$collector->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$budgetName = $transaction->transaction_budget_name;
$budgetId = (int)$transaction->transaction_budget_id;
// if null, grab from journal:
if (0 === $budgetId) {
$budgetName = $transaction->transaction_journal_budget_name;
$budgetId = (int)$transaction->transaction_journal_budget_id;
}
foreach ($journals as $journal) {
$currencyId = $journal['currency_id'];
$budgetName = $journal['budget_name'];
$budgetId = (int)$journal['budget_id'];
// if not set, set to zero:
if (!isset($sum[$budgetId][$currencyId])) {
@ -617,8 +617,8 @@ trait AugumentData
'name' => $budgetName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
'symbol' => $journal['currency_symbol'],
'dp' => $journal['currency_decimal_places'],
],
],
],
@ -627,9 +627,9 @@ trait AugumentData
// add amount
$sum[$budgetId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$budgetId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
$sum[$budgetId]['per_currency'][$currencyId]['sum'], $journal['amount']
);
$sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $transaction->transaction_amount);
$sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $journal['amount']);
}
return $sum;
@ -652,22 +652,18 @@ trait AugumentData
*/
protected function spentByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing)->withCategoryInformation();
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$total = $assets->merge($opposing);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($total);
$collector->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$sum = [];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = $transaction->transaction_currency_id;
$categoryName = $transaction->transaction_category_name;
$categoryId = (int)$transaction->transaction_category_id;
// if null, grab from journal:
if (0 === $categoryId) {
$categoryName = $transaction->transaction_journal_category_name;
$categoryId = (int)$transaction->transaction_journal_category_id;
}
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$categoryName = $journal['category_name'];
$categoryId = (int)$journal['category_id'];
// if not set, set to zero:
if (!isset($sum[$categoryId][$currencyId])) {
@ -682,8 +678,8 @@ trait AugumentData
'name' => $categoryName,
],
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
'symbol' => $journal['currency_symbol'],
'dp' => $journal['currency_decimal_places'],
],
],
],
@ -692,9 +688,9 @@ trait AugumentData
// add amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd(
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount
$sum[$categoryId]['per_currency'][$currencyId]['sum'], $journal['amount']
);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount);
$sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $journal['amount']);
}
return $sum;
@ -714,33 +710,35 @@ trait AugumentData
*/
protected function spentInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets);
$collector->setOpposingAccounts($opposing);
$set = $collector->getTransactions();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$total = $assets->merge($opposing);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($total);
$journals = $collector->getExtractedJournals();
$sum = [
'grand_sum' => '0',
'per_currency' => [],
];
// loop to support multi currency
foreach ($set as $transaction) {
$currencyId = (int)$transaction->transaction_currency_id;
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
// if not set, set to zero:
if (!isset($sum['per_currency'][$currencyId])) {
$sum['per_currency'][$currencyId] = [
'sum' => '0',
'currency' => [
'symbol' => $transaction->transaction_currency_symbol,
'dp' => $transaction->transaction_currency_dp,
'name' => $journal['currency_name'],
'symbol' => $journal['currency_symbol'],
'decimal_places' => $journal['currency_decimal_places'],
],
];
}
// add amount
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount);
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $journal['amount']);
$sum['grand_sum'] = bcadd($sum['grand_sum'], $journal['amount']);
}
return $sum;
@ -767,6 +765,7 @@ trait AugumentData
$collector = app(GroupCollectorInterface::class);
$types = [TransactionType::WITHDRAWAL];
$collector->setTypes($types)->setRange($start, $end)->withoutBudget();
return $collector->getSum();
}
}

View File

@ -24,11 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Collector\GroupSumCollectorInterface;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\Account;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;

View File

@ -24,29 +24,18 @@ declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Help\HelpInterface;
use FireflyIII\Http\Requests\SplitJournalFormRequest;
use FireflyIII\Http\Requests\TestRuleFormRequest;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Binder\AccountList;
use FireflyIII\Transformers\TransactionTransformer;
use FireflyIII\User;
use Hash;
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use InvalidArgumentException;
use Log;
use Route as RouteFacade;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Trait RequestInformation
@ -54,138 +43,7 @@ use Symfony\Component\HttpFoundation\ParameterBag;
*/
trait RequestInformation
{
/**
* Create data-array from a journal.
*
* @param SplitJournalFormRequest|Request $request
* @param TransactionJournal $journal
*
* @return array
* @throws FireflyException
*/
protected function arrayFromJournal(Request $request, TransactionJournal $journal): array // convert user input.
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$sourceAccounts = $repository->getJournalSourceAccounts($journal);
$destinationAccounts = $repository->getJournalDestinationAccounts($journal);
$array = [
'journal_description' => $request->old('journal_description', $journal->description),
'journal_amount' => '0',
'journal_foreign_amount' => '0',
'sourceAccounts' => $sourceAccounts,
'journal_source_id' => $request->old('journal_source_id', $sourceAccounts->first()->id),
'journal_source_name' => $request->old('journal_source_name', $sourceAccounts->first()->name),
'journal_destination_id' => $request->old('journal_destination_id', $destinationAccounts->first()->id),
'destinationAccounts' => $destinationAccounts,
'what' => strtolower($this->repository->getTransactionType($journal)),
'date' => $request->old('date', $this->repository->getJournalDate($journal, null)),
'tags' => implode(',', $journal->tags->pluck('tag')->toArray()),
// all custom fields:
'interest_date' => $request->old('interest_date', $repository->getMetaField($journal, 'interest_date')),
'book_date' => $request->old('book_date', $repository->getMetaField($journal, 'book_date')),
'process_date' => $request->old('process_date', $repository->getMetaField($journal, 'process_date')),
'due_date' => $request->old('due_date', $repository->getMetaField($journal, 'due_date')),
'payment_date' => $request->old('payment_date', $repository->getMetaField($journal, 'payment_date')),
'invoice_date' => $request->old('invoice_date', $repository->getMetaField($journal, 'invoice_date')),
'internal_reference' => $request->old('internal_reference', $repository->getMetaField($journal, 'internal_reference')),
'notes' => $request->old('notes', $repository->getNoteText($journal)),
// transactions.
'transactions' => $this->getTransactionDataFromJournal($journal),
];
// update transactions array with old request data.
$array['transactions'] = $this->updateWithPrevious($array['transactions'], $request->old());
// update journal amount and foreign amount:
$array['journal_amount'] = array_sum(array_column($array['transactions'], 'amount'));
$array['journal_foreign_amount'] = array_sum(array_column($array['transactions'], 'foreign_amount'));
return $array;
}
/**
* Get transaction overview from journal.
*
* @param TransactionJournal $journal
*
* @return array
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function getTransactionDataFromJournal(TransactionJournal $journal): array // convert object
{
// use collector to collect transactions.
$collector = app(TransactionCollectorInterface::class);
$collector->setUser(auth()->user());
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
// filter on specific journals.
$collector->setJournals(new Collection([$journal]));
$set = $collector->getTransactions();
$transactions = [];
/** @var TransactionTransformer $transformer */
$transformer = app(TransactionTransformer::class);
$transformer->setParameters(new ParameterBag());
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$res = [];
if ((float)$transaction->transaction_amount > 0 && $journal->transactionType->type === TransactionType::DEPOSIT) {
$res = $transformer->transform($transaction);
}
if ((float)$transaction->transaction_amount < 0 && $journal->transactionType->type !== TransactionType::DEPOSIT) {
$res = $transformer->transform($transaction);
}
if (count($res) > 0) {
$res['amount'] = app('steam')->positive((string)$res['amount']);
$res['foreign_amount'] = app('steam')->positive((string)$res['foreign_amount']);
$transactions[] = $res;
}
}
return $transactions;
}
/**
* Get info from old input.
*
* @param $array
* @param $old
*
* @return array
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function updateWithPrevious($array, $old): array // update object with new info
{
if (0 === count($old) || !isset($old['transactions'])) {
return $array;
}
$old = $old['transactions'];
foreach ($old as $index => $row) {
if (isset($array[$index])) {
/** @noinspection SlowArrayOperationsInLoopInspection */
$array[$index] = array_merge($array[$index], $row);
continue;
}
// take some info from first transaction, that should at least exist.
$array[$index] = $row;
$array[$index]['currency_id'] = $array[0]['currency_id'];
$array[$index]['currency_code'] = $array[0]['currency_code'] ?? '';
$array[$index]['currency_symbol'] = $array[0]['currency_symbol'] ?? '';
$array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12);
$array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id'];
$array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code'];
$array[$index]['foreign_currency_symbol'] = $array[0]['foreign_currency_symbol'];
}
return $array;
}
/**
* Get the domain of FF system.

View File

@ -46,14 +46,18 @@ trait TransactionCalculation
*/
protected function getExpensesForOpposing(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): array
{
$total = $accounts->merge($opposing);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)
$collector->setAccounts($total)
->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->setAccounts($opposing);
->withAccountInformation()
->setTypes([TransactionType::WITHDRAWAL]);
return $collector->getExtractedJournals();
$result = $collector->getExtractedJournals();
return $result;
}
/**
@ -156,10 +160,10 @@ trait TransactionCalculation
*/
protected function getIncomeForOpposing(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): array
{
$total =$accounts->merge($opposing);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])
->setAccounts($opposing);
$collector->setAccounts($total)->setRange($start, $end)->withAccountInformation()->setTypes([TransactionType::DEPOSIT]);
return $collector->getExtractedJournals();
}

View File

@ -160,6 +160,12 @@ class ImportTransaction
'opposing-bic' => 'opposingBic',
'opposing-number' => 'opposingNumber',
];
// overrule some old role values.
if ('original-source' === $role) {
$role = 'original_source';
}
if (isset($basics[$role])) {
$field = $basics[$role];
$this->$field = $columnValue->getValue();
@ -229,6 +235,18 @@ class ImportTransaction
}
}
/**
* Returns the mapped value if it exists in the ColumnValue object.
*
* @param ColumnValue $columnValue
*
* @return int
*/
private function getMappedValue(ColumnValue $columnValue): int
{
return $columnValue->getMappedValue() > 0 ? $columnValue->getMappedValue() : (int)$columnValue->getValue();
}
/**
* Calculate the amount of this transaction.
*
@ -276,6 +294,40 @@ class ImportTransaction
return $result;
}
/**
* This methods decides which input value to use for the amount calculation.
*
* @return array
*/
private function selectAmountInput(): array
{
$info = [];
$converterClass = '';
if (null !== $this->amount) {
Log::debug('Amount value is not NULL, assume this is the correct value.');
$converterClass = Amount::class;
$info['amount'] = $this->amount;
}
if (null !== $this->amountDebit) {
Log::debug('Amount DEBIT value is not NULL, assume this is the correct value (overrules Amount).');
$converterClass = AmountDebit::class;
$info['amount'] = $this->amountDebit;
}
if (null !== $this->amountCredit) {
Log::debug('Amount CREDIT value is not NULL, assume this is the correct value (overrules Amount and AmountDebit).');
$converterClass = AmountCredit::class;
$info['amount'] = $this->amountCredit;
}
if (null !== $this->amountNegated) {
Log::debug('Amount NEGATED value is not NULL, assume this is the correct value (overrules Amount and AmountDebit and AmountCredit).');
$converterClass = AmountNegated::class;
$info['amount'] = $this->amountNegated;
}
$info['class'] = $converterClass;
return $info;
}
/**
* The method that calculates the foreign amount isn't nearly as complex,\
* because Firefly III only supports one foreign amount field. So the foreign amount is there
@ -372,50 +424,4 @@ class ImportTransaction
];
}
/**
* Returns the mapped value if it exists in the ColumnValue object.
*
* @param ColumnValue $columnValue
*
* @return int
*/
private function getMappedValue(ColumnValue $columnValue): int
{
return $columnValue->getMappedValue() > 0 ? $columnValue->getMappedValue() : (int)$columnValue->getValue();
}
/**
* This methods decides which input value to use for the amount calculation.
*
* @return array
*/
private function selectAmountInput(): array
{
$info = [];
$converterClass = '';
if (null !== $this->amount) {
Log::debug('Amount value is not NULL, assume this is the correct value.');
$converterClass = Amount::class;
$info['amount'] = $this->amount;
}
if (null !== $this->amountDebit) {
Log::debug('Amount DEBIT value is not NULL, assume this is the correct value (overrules Amount).');
$converterClass = AmountDebit::class;
$info['amount'] = $this->amountDebit;
}
if (null !== $this->amountCredit) {
Log::debug('Amount CREDIT value is not NULL, assume this is the correct value (overrules Amount and AmountDebit).');
$converterClass = AmountCredit::class;
$info['amount'] = $this->amountCredit;
}
if (null !== $this->amountNegated) {
Log::debug('Amount NEGATED value is not NULL, assume this is the correct value (overrules Amount and AmountDebit and AmountCredit).');
$converterClass = AmountNegated::class;
$info['amount'] = $this->amountNegated;
}
$info['class'] = $converterClass;
return $info;
}
}

View File

@ -89,79 +89,11 @@ class ImportableConverter
return $result;
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->config = $importJob->configuration;
// repository is used for error messages
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
// asset account mapper can map asset accounts (makes sense right?)
$this->assetMapper = app(AssetAccountMapper::class);
$this->assetMapper->setUser($importJob->user);
$this->assetMapper->setDefaultAccount($this->config['import-account'] ?? 0);
// asset account repository is used for currency information
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->accountRepository->setUser($importJob->user);
// opposing account mapper:
$this->opposingMapper = app(OpposingAccountMapper::class);
$this->opposingMapper->setUser($importJob->user);
// currency mapper:
$this->currencyMapper = app(CurrencyMapper::class);
$this->currencyMapper->setUser($importJob->user);
$this->defaultCurrency = app('amount')->getDefaultCurrencyByUser($importJob->user);
}
/**
* @codeCoverageIgnore
*
* @param array $mappedValues
*/
public function setMappedValues(array $mappedValues): void
{
$this->mappedValues = $mappedValues;
}
/**
* @param string|null $date
*
* @return string|null
*/
private function convertDateValue(string $date = null): ?string
{
$result = null;
if (null !== $date) {
try {
// add exclamation mark for better parsing. http://php.net/manual/en/datetime.createfromformat.php
$dateFormat = $this->config['date-format'] ?? 'Ymd';
if ('!' !== $dateFormat{0}) {
$dateFormat = '!' . $dateFormat;
}
$object = Carbon::createFromFormat($dateFormat, $date);
$result = $object->format('Y-m-d H:i:s');
Log::debug(sprintf('createFromFormat: Turning "%s" into "%s" using "%s"', $date, $result, $this->config['date-format'] ?? 'Ymd'));
} catch (InvalidDateException|InvalidArgumentException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
}
return $result;
}
/**
* @param ImportTransaction $importable
*
* @throws FireflyException
* @return array
* @throws FireflyException
*/
private function convertSingle(ImportTransaction $importable): array
{
@ -218,14 +150,53 @@ class ImportableConverter
}
return [
'user' => $this->importJob->user_id,
'group_title' => null,
'transactions' => [
[
'user' => $this->importJob->user_id,
'type' => $transactionType,
'date' => $this->convertDateValue($importable->date) ?? Carbon::now()->format('Y-m-d H:i:s'),
'tags' => $importable->tags,
'user' => $this->importJob->user_id,
'notes' => $importable->note,
'order' => 0,
'currency_id' => $currency->id,
'currency_code' => null,
'foreign_currency_id' => $importable->foreignCurrencyId,
'foreign_currency_code' => null === $foreignCurrency ? null : $foreignCurrency->code,
'amount' => $amount,
'foreign_amount' => $foreignAmount,
'description' => $importable->description,
'source_id' => $source->id,
'source_name' => null,
'destination_id' => $destination->id,
'destination_name' => null,
'budget_id' => $importable->budgetId,
'budget_name' => $importable->budgetName,
'category_id' => $importable->categoryId,
'category_name' => $importable->categoryName,
'bill_id' => $importable->billId,
'bill_name' => $importable->billName,
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'reconciled' => false,
'notes' => $importable->note,
'tags' => $importable->tags,
// all custom fields:
'internal_reference' => $importable->meta['internal-reference'] ?? null,
'external_id' => $importable->externalId,
'original_source' => $importable->meta['original-source'] ?? null,
'sepa_cc' => $importable->meta['sepa_cc'] ?? null,
'sepa_ct_op' => $importable->meta['sepa_ct_op'] ?? null,
'sepa_ct_id' => $importable->meta['sepa_ct_id'] ?? null,
@ -234,44 +205,43 @@ class ImportableConverter
'sepa_ep' => $importable->meta['sepa_ep'] ?? null,
'sepa_ci' => $importable->meta['sepa_ci'] ?? null,
'sepa_batch_id' => $importable->meta['sepa_batch_id'] ?? null,
'interest_date' => $this->convertDateValue($importable->meta['date-interest'] ?? null),
'book_date' => $this->convertDateValue($importable->meta['date-book'] ?? null),
'process_date' => $this->convertDateValue($importable->meta['date-process'] ?? null),
'due_date' => $this->convertDateValue($importable->meta['date-due'] ?? null),
'payment_date' => $this->convertDateValue($importable->meta['date-payment'] ?? null),
'invoice_date' => $this->convertDateValue($importable->meta['date-invoice'] ?? null),
'external_id' => $importable->externalId,
'original-source' => $importable->meta['original-source'] ?? null,
// journal data:
'description' => $importable->description,
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'bill_id' => $importable->billId,
'bill_name' => $importable->billName,
// transaction data:
'transactions' => [
[
'currency_id' => $currency->id,
'currency_code' => null,
'description' => null,
'amount' => $amount,
'budget_id' => $importable->budgetId,
'budget_name' => $importable->budgetName,
'category_id' => $importable->categoryId,
'category_name' => $importable->categoryName,
'source_id' => $source->id,
'source_name' => null,
'destination_id' => $destination->id,
'destination_name' => null,
'foreign_currency_id' => $importable->foreignCurrencyId,
'foreign_currency_code' => null === $foreignCurrency ? null : $foreignCurrency->code,
'foreign_amount' => $foreignAmount,
'reconciled' => false,
'identifier' => 0,
],
],
];
}
/**
* @param string $source
* @param string $destination
*
* @return string
*/
private function getTransactionType(string $source, string $destination): string
{
$type = 'unknown';
if ($source === AccountType::ASSET && $destination === AccountType::ASSET) {
Log::debug('Source and destination are asset accounts. This is a transfer.');
$type = 'transfer';
}
if ($source === AccountType::REVENUE) {
Log::debug('Source is a revenue account. This is a deposit.');
$type = 'deposit';
}
if ($destination === AccountType::EXPENSE) {
Log::debug('Destination is an expense account. This is a withdrawal.');
$type = 'withdrawal';
}
return $type;
}
/**
@ -305,28 +275,70 @@ class ImportableConverter
}
/**
* @param string $source
* @param string $destination
* @param string|null $date
*
* @return string
* @return string|null
*/
private function getTransactionType(string $source, string $destination): string
private function convertDateValue(string $date = null): ?string
{
$type = 'unknown';
if ($source === AccountType::ASSET && $destination === AccountType::ASSET) {
Log::debug('Source and destination are asset accounts. This is a transfer.');
$type = 'transfer';
$result = null;
if (null !== $date) {
try {
// add exclamation mark for better parsing. http://php.net/manual/en/datetime.createfromformat.php
$dateFormat = $this->config['date-format'] ?? 'Ymd';
if ('!' !== $dateFormat{0}) {
$dateFormat = '!' . $dateFormat;
}
if ($source === AccountType::REVENUE) {
Log::debug('Source is a revenue account. This is a deposit.');
$type = 'deposit';
$object = Carbon::createFromFormat($dateFormat, $date);
$result = $object->format('Y-m-d H:i:s');
Log::debug(sprintf('createFromFormat: Turning "%s" into "%s" using "%s"', $date, $result, $this->config['date-format'] ?? 'Ymd'));
} catch (InvalidDateException|InvalidArgumentException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
}
if ($destination === AccountType::EXPENSE) {
Log::debug('Destination is an expense account. This is a withdrawal.');
$type = 'withdrawal';
}
return $type;
return $result;
}
/**
* @param ImportJob $importJob
*/
public function setImportJob(ImportJob $importJob): void
{
$this->importJob = $importJob;
$this->config = $importJob->configuration;
// repository is used for error messages
$this->repository = app(ImportJobRepositoryInterface::class);
$this->repository->setUser($importJob->user);
// asset account mapper can map asset accounts (makes sense right?)
$this->assetMapper = app(AssetAccountMapper::class);
$this->assetMapper->setUser($importJob->user);
$this->assetMapper->setDefaultAccount($this->config['import-account'] ?? 0);
// asset account repository is used for currency information
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->accountRepository->setUser($importJob->user);
// opposing account mapper:
$this->opposingMapper = app(OpposingAccountMapper::class);
$this->opposingMapper->setUser($importJob->user);
// currency mapper:
$this->currencyMapper = app(CurrencyMapper::class);
$this->currencyMapper->setUser($importJob->user);
$this->defaultCurrency = app('amount')->getDefaultCurrencyByUser($importJob->user);
}
/**
* @codeCoverageIgnore
*
* @param array $mappedValues
*/
public function setMappedValues(array $mappedValues): void
{
$this->mappedValues = $mappedValues;
}
}

View File

@ -23,9 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Search;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\DoubleTransactionFilter;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
@ -132,6 +130,22 @@ class Search implements SearchInterface
}
}
/**
* @param string $string
*/
private function extractModifier(string $string): void
{
$parts = explode(':', $string);
if (2 === count($parts) && '' !== trim((string)$parts[1]) && '' !== trim((string)$parts[0])) {
$type = trim((string)$parts[0]);
$value = trim((string)$parts[1]);
if (\in_array($type, $this->validModifiers, true)) {
// filter for valid type
$this->modifiers->push(['type' => $type, 'value' => $value]);
}
}
}
/**
* @return float
*/
@ -149,16 +163,13 @@ class Search implements SearchInterface
$pageSize = 50;
$page = 1;
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->withOpposingAccount();
if ($this->hasModifiers()) {
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setLimit($pageSize)->setPage($page)->withAccountInformation();
$collector->withCategoryInformation()->withBudgetInformation();
$collector->setSearchWords($this->words);
$collector->removeFilter(InternalTransferFilter::class);
$collector->addFilter(DoubleTransactionFilter::class);
// Most modifiers can be applied to the collector directly.
$collector = $this->applyModifiers($collector);
@ -168,37 +179,18 @@ class Search implements SearchInterface
}
/**
* @param int $limit
*/
public function setLimit(int $limit): void
{
$this->limit = $limit;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
$this->accountRepository->setUser($user);
$this->billRepository->setUser($user);
$this->categoryRepository->setUser($user);
$this->budgetRepository->setUser($user);
}
/**
* @param TransactionCollectorInterface $collector
* @param GroupCollectorInterface $collector
*
* @return TransactionCollectorInterface
* @return GroupCollectorInterface
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function applyModifiers(TransactionCollectorInterface $collector): TransactionCollectorInterface
private function applyModifiers(GroupCollectorInterface $collector): GroupCollectorInterface
{
/*
* TODO:
* 'bill',
*/
$totalAccounts = new Collection;
foreach ($this->modifiers as $modifier) {
switch ($modifier['type']) {
@ -209,7 +201,7 @@ class Search implements SearchInterface
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE];
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
if ($accounts->count() > 0) {
$collector->setAccounts($accounts);
$totalAccounts = $accounts->merge($totalAccounts);
}
break;
case 'destination':
@ -217,7 +209,7 @@ class Search implements SearchInterface
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE];
$accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
if ($accounts->count() > 0) {
$collector->setOpposingAccounts($accounts);
$totalAccounts = $accounts->merge($totalAccounts);
}
break;
case 'category':
@ -280,23 +272,28 @@ class Search implements SearchInterface
break;
}
}
$collector->setAccounts($totalAccounts);
return $collector;
}
/**
* @param string $string
* @param int $limit
*/
private function extractModifier(string $string): void
public function setLimit(int $limit): void
{
$parts = explode(':', $string);
if (2 === count($parts) && '' !== trim((string)$parts[1]) && '' !== trim((string)$parts[0])) {
$type = trim((string)$parts[0]);
$value = trim((string)$parts[1]);
if (\in_array($type, $this->validModifiers, true)) {
// filter for valid type
$this->modifiers->push(['type' => $type, 'value' => $value]);
}
$this->limit = $limit;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
$this->accountRepository->setUser($user);
$this->billRepository->setUser($user);
$this->categoryRepository->setUser($user);
$this->budgetRepository->setUser($user);
}
}

View File

@ -23,12 +23,11 @@ declare(strict_types=1);
namespace FireflyIII\TransactionRules;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Log;
@ -78,16 +77,16 @@ class TransactionMatcher
* transaction journals matching the given rule. This is accomplished by trying to fire these
* triggers onto each transaction journal until enough matches are found ($limit).
*
* @return Collection
* @return array
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function findTransactionsByRule(): Collection
public function findTransactionsByRule(): array
{
Log::debug('Now in findTransactionsByRule()');
if (0 === count($this->rule->ruleTriggers)) {
Log::error('Rule has no triggers!');
return new Collection;
return [];
}
// Variables used within the loop.
@ -98,7 +97,7 @@ class TransactionMatcher
// If the list of matchingTransactions is larger than the maximum number of results
// (e.g. if a large percentage of the transactions match), truncate the list
$result = $result->slice(0, $this->searchLimit);
$result = array_slice($result, 0, $this->searchLimit);
return $result;
}
@ -254,7 +253,7 @@ class TransactionMatcher
*
* @return Collection
*/
private function runProcessor(Processor $processor): Collection
private function runProcessor(Processor $processor): array
{
Log::debug('Now in runprocessor()');
// since we have a rule in $this->rule, we can add some of the triggers
@ -269,22 +268,23 @@ class TransactionMatcher
$pageSize = min($this->searchLimit, min($this->triggeredLimit, 50));
$processed = 0;
$page = 1;
$result = new Collection;
$result = [];
Log::debug(sprintf('Search limit is %d, triggered limit is %d, so page size is %d', $this->searchLimit, $this->triggeredLimit, $pageSize));
do {
Log::debug('Start of do-loop');
// Fetch a batch of transactions from the database
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$collector->setUser(auth()->user());
$collector->withOpposingAccount();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
/** @var User $user */
$user = auth()->user();
$collector->setUser($user);
// limit asset accounts:
if (0 === $this->accounts->count()) {
$collector->setAllAssetAccounts();
}
if ($this->accounts->count() > 0) {
$collector->setAccounts($this->accounts);
}
@ -305,36 +305,35 @@ class TransactionMatcher
Log::debug(sprintf('Amount must be exactly %s', $this->exactAmount));
$collector->amountIs($this->exactAmount);
}
$collector->removeFilter(InternalTransferFilter::class);
$set = $collector->getPaginatedTransactions();
Log::debug(sprintf('Found %d journals to check. ', $set->count()));
$journals = $collector->getExtractedJournals();
Log::debug(sprintf('Found %d transaction journals to check. ', count($journals)));
// Filter transactions that match the given triggers.
$filtered = $set->filter(
function (Transaction $transaction) use ($processor) {
Log::debug(sprintf('Test the triggers on journal #%d (transaction #%d)', $transaction->transaction_journal_id, $transaction->id));
return $processor->handleTransaction($transaction);
$filtered = [];
/** @var array $journal */
foreach ($journals as $journal) {
$result = $processor->handleJournalArray($journal);
if ($result) {
$filtered[] = $journal;
}
}
);
Log::debug(sprintf('Found %d journals that match.', $filtered->count()));
Log::debug(sprintf('Found %d journals that match.', count($filtered)));
// merge:
/** @var Collection $result */
$result = $result->merge($filtered);
Log::debug(sprintf('Total count is now %d', $result->count()));
$result = $result + $filtered;
Log::debug(sprintf('Total count is now %d', count($result)));
// Update counters
++$page;
$processed += count($set);
$processed += count($journals);
Log::debug(sprintf('Page is now %d, processed is %d', $page, $processed));
// Check for conditions to finish the loop
$reachedEndOfList = $set->count() < 1;
$foundEnough = $result->count() >= $this->triggeredLimit;
$reachedEndOfList = count($journals) < 1;
$foundEnough = count($result) >= $this->triggeredLimit;
$searchedEnough = ($processed >= $this->searchLimit);
Log::debug(sprintf('reachedEndOfList: %s', var_export($reachedEndOfList, true)));

View File

@ -13,7 +13,7 @@
<p style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:13px;">
You can find it in your Firefly III installation:<br />
{% for journal in journals %}
<a href="{{ route('transactions.show', journal.id) }}">{{ journal.description }}</a> ({{ journal|journalTotalAmount }})
<a href="{{ route('transactions.show', journal.id) }}">{{ journal.description }}</a> (TODO)
{% endfor %}
</p>
{% endif %}
@ -25,7 +25,7 @@
<ul>
{% for journal in journals %}
<li>
<a href="{{ route('transactions.show', journal.id) }}">{{ journal.description }}</a> ({{ journal|journalTotalAmount }})
<a href="{{ route('transactions.show', journal.id) }}">{{ journal.description }}</a> (TODO)
</li>
{% endfor %}
</ul>

View File

@ -10,7 +10,7 @@ Firefly III has created {{ journals.count }} transactions for you.
You can find in in your Firefly III installation:
{% for journal in journals %}
{{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }})
{{ journal.description }}: {{ route('transactions.show', journal.id) }} (TODO)
{% endfor %}
{% endif %}
@ -18,7 +18,7 @@ You can find in in your Firefly III installation:
You can find them in your Firefly III installation:
{% for journal in journals %}
- {{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }})
- {{ journal.description }}: {{ route('transactions.show', journal.id) }} (TODO)
{% endfor %}
{% endif %}