Browse Source

Add support to exclude activity from analysis via tag

pull/5297/head
Thomas Kaul 3 weeks ago
parent
commit
de3aa4fa23
  1. 23
      apps/api/src/app/order/order.service.ts
  2. 8
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 3
      apps/api/src/services/tag/tag.service.ts
  4. 2
      libs/common/src/lib/config.ts
  5. 2
      libs/ui/src/lib/activities-table/activities-table.component.html
  6. 28
      libs/ui/src/lib/activities-table/activities-table.component.ts
  7. 1
      libs/ui/src/lib/i18n.ts
  8. 4
      prisma/seed.ts

23
apps/api/src/app/order/order.service.ts

@ -9,7 +9,8 @@ import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_HIGH,
GATHER_ASSET_PROFILE_PROCESS_JOB_NAME, GATHER_ASSET_PROFILE_PROCESS_JOB_NAME,
GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS, GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS,
ghostfolioPrefix ghostfolioPrefix,
TAG_ID_EXCLUDE_FROM_ANALYSIS
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { import {
@ -33,7 +34,7 @@ import {
import { Big } from 'big.js'; import { Big } from 'big.js';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
import { endOfToday, isAfter } from 'date-fns'; import { endOfToday, isAfter } from 'date-fns';
import { groupBy, uniqBy } from 'lodash'; import { groupBy, isArray, uniqBy } from 'lodash';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { Activities } from './interfaces/activities.interface'; import { Activities } from './interfaces/activities.interface';
@ -483,6 +484,24 @@ export class OrderService {
}; };
} }
if (withExcludedAccounts === false) {
const excludeFromAnalysisTagCondition = {
tags: {
none: {
id: TAG_ID_EXCLUDE_FROM_ANALYSIS
}
}
};
if (isArray(where.AND)) {
where.AND.push(excludeFromAnalysisTagCondition);
} else if (where.AND) {
where.AND = [where.AND, excludeFromAnalysisTagCondition];
} else {
where.AND = [excludeFromAnalysisTagCondition];
}
}
if (sortColumn) { if (sortColumn) {
orderBy = [{ [sortColumn]: sortDirection }, { id: sortDirection }]; orderBy = [{ [sortColumn]: sortDirection }, { id: sortDirection }];
} }

8
apps/api/src/app/portfolio/portfolio.service.ts

@ -33,6 +33,7 @@ import {
import { import {
DEFAULT_CURRENCY, DEFAULT_CURRENCY,
TAG_ID_EMERGENCY_FUND, TAG_ID_EMERGENCY_FUND,
TAG_ID_EXCLUDE_FROM_ANALYSIS,
UNKNOWN_KEY UNKNOWN_KEY
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper'; import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper';
@ -1806,7 +1807,12 @@ export class PortfolioService {
const nonExcludedActivities: Activity[] = []; const nonExcludedActivities: Activity[] = [];
for (const activity of activities) { for (const activity of activities) {
if (activity.account?.isExcluded) { if (
activity.account?.isExcluded ||
activity.tags?.some(({ id }) => {
return id === TAG_ID_EXCLUDE_FROM_ANALYSIS;
})
) {
excludedActivities.push(activity); excludedActivities.push(activity);
} else { } else {
nonExcludedActivities.push(activity); nonExcludedActivities.push(activity);

3
apps/api/src/services/tag/tag.service.ts

@ -1,4 +1,5 @@
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Prisma, Tag } from '@prisma/client'; import { Prisma, Tag } from '@prisma/client';
@ -79,7 +80,7 @@ export class TagService {
id, id,
name, name,
userId, userId,
isUsed: _count.activities > 0 isUsed: _count.activities > 0 && id !== TAG_ID_EXCLUDE_FROM_ANALYSIS
})); }));
} }

2
libs/common/src/lib/config.ts

@ -200,6 +200,8 @@ export const SUPPORTED_LANGUAGE_CODES = [
]; ];
export const TAG_ID_EMERGENCY_FUND = '4452656d-9fa4-4bd0-ba38-70492e31d180'; export const TAG_ID_EMERGENCY_FUND = '4452656d-9fa4-4bd0-ba38-70492e31d180';
export const TAG_ID_EXCLUDE_FROM_ANALYSIS =
'f2e868af-8333-459f-b161-cbc6544c24bd';
export const TAG_ID_DEMO = 'efa08cb3-9b9d-4974-ac68-db13a19c4874'; export const TAG_ID_DEMO = 'efa08cb3-9b9d-4974-ac68-db13a19c4874';
export const UNKNOWN_KEY = 'UNKNOWN'; export const UNKNOWN_KEY = 'UNKNOWN';

2
libs/ui/src/lib/activities-table/activities-table.component.html

@ -468,7 +468,7 @@
[ngClass]="{ [ngClass]="{
'cursor-pointer': 'cursor-pointer':
hasPermissionToOpenDetails && hasPermissionToOpenDetails &&
row.account?.isExcluded !== true && isExcludedFromAnalysis(row) === false &&
row.isDraft === false && row.isDraft === false &&
['BUY', 'DIVIDEND', 'SELL'].includes(row.type) ['BUY', 'DIVIDEND', 'SELL'].includes(row.type)
}" }"

28
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -2,7 +2,10 @@ import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interf
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import {
DEFAULT_PAGE_SIZE,
TAG_ID_EXCLUDE_FROM_ANALYSIS
} from '@ghostfolio/common/config';
import { getDateFormatString, getLocale } from '@ghostfolio/common/helper'; import { getDateFormatString, getLocale } from '@ghostfolio/common/helper';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { OrderWithAccount } from '@ghostfolio/common/types'; import { OrderWithAccount } from '@ghostfolio/common/types';
@ -171,12 +174,6 @@ export class GfActivitiesTableComponent
}); });
} }
public areAllRowsSelected() {
const numSelectedRows = this.selectedRows.selected.length;
const numTotalRows = this.dataSource.data.length;
return numSelectedRows === numTotalRows;
}
public ngOnChanges() { public ngOnChanges() {
this.defaultDateFormat = getDateFormatString(this.locale); this.defaultDateFormat = getDateFormatString(this.locale);
@ -215,6 +212,21 @@ export class GfActivitiesTableComponent
} }
} }
public areAllRowsSelected() {
const numSelectedRows = this.selectedRows.selected.length;
const numTotalRows = this.dataSource.data.length;
return numSelectedRows === numTotalRows;
}
public isExcludedFromAnalysis(activity: Activity) {
return (
activity.account?.isExcluded ||
activity.tags?.some(({ id }) => {
return id === TAG_ID_EXCLUDE_FROM_ANALYSIS;
})
);
}
public onChangePage(page: PageEvent) { public onChangePage(page: PageEvent) {
this.pageChanged.emit(page); this.pageChanged.emit(page);
} }
@ -226,7 +238,7 @@ export class GfActivitiesTableComponent
} }
} else if ( } else if (
this.hasPermissionToOpenDetails && this.hasPermissionToOpenDetails &&
activity.account?.isExcluded !== true && this.isExcludedFromAnalysis(activity) === false &&
activity.isDraft === false && activity.isDraft === false &&
['BUY', 'DIVIDEND', 'SELL'].includes(activity.type) ['BUY', 'DIVIDEND', 'SELL'].includes(activity.type)
) { ) {

1
libs/ui/src/lib/i18n.ts

@ -13,6 +13,7 @@ const locales = {
DATA_IMPORT_AND_EXPORT_TOOLTIP_OSS: $localize`Switch to Ghostfolio Premium easily`, DATA_IMPORT_AND_EXPORT_TOOLTIP_OSS: $localize`Switch to Ghostfolio Premium easily`,
DATA_IMPORT_AND_EXPORT_TOOLTIP_PREMIUM: $localize`Switch to Ghostfolio Open Source or Ghostfolio Basic easily`, DATA_IMPORT_AND_EXPORT_TOOLTIP_PREMIUM: $localize`Switch to Ghostfolio Open Source or Ghostfolio Basic easily`,
EMERGENCY_FUND: $localize`Emergency Fund`, EMERGENCY_FUND: $localize`Emergency Fund`,
EXCLUDE_FROM_ANALYSIS: $localize`Exclude from Analysis`,
Global: $localize`Global`, Global: $localize`Global`,
GRANT: $localize`Grant`, GRANT: $localize`Grant`,
HIGHER_RISK: $localize`Higher Risk`, HIGHER_RISK: $localize`Higher Risk`,

4
prisma/seed.ts

@ -8,6 +8,10 @@ async function main() {
{ {
id: '4452656d-9fa4-4bd0-ba38-70492e31d180', id: '4452656d-9fa4-4bd0-ba38-70492e31d180',
name: 'EMERGENCY_FUND' name: 'EMERGENCY_FUND'
},
{
id: 'f2e868af-8333-459f-b161-cbc6544c24bd',
name: 'EXCLUDE_FROM_ANALYSIS'
} }
], ],
skipDuplicates: true skipDuplicates: true

Loading…
Cancel
Save