Feature/improve asset profile management (#1485)
* Improve asset profile management (Add note, fix filter) * Update changelog
This commit is contained in:
parent
de3e0fad83
commit
b5b7af7741
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Supported a note for asset profiles
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the filter by asset sub class for the asset profiles in the admin control
|
||||||
|
|
||||||
## 1.215.0 - 2022-11-27
|
## 1.215.0 - 2022-11-27
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@ -538,7 +548,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support a note for activities
|
- Supported a note for activities
|
||||||
|
|
||||||
### Todo
|
### Todo
|
||||||
|
|
||||||
|
@ -116,6 +116,7 @@ export class AdminService {
|
|||||||
},
|
},
|
||||||
assetClass: true,
|
assetClass: true,
|
||||||
assetSubClass: true,
|
assetSubClass: true,
|
||||||
|
comment: true,
|
||||||
countries: true,
|
countries: true,
|
||||||
dataSource: true,
|
dataSource: true,
|
||||||
Order: {
|
Order: {
|
||||||
@ -150,6 +151,7 @@ export class AdminService {
|
|||||||
activitiesCount: symbolProfile._count.Order,
|
activitiesCount: symbolProfile._count.Order,
|
||||||
assetClass: symbolProfile.assetClass,
|
assetClass: symbolProfile.assetClass,
|
||||||
assetSubClass: symbolProfile.assetSubClass,
|
assetSubClass: symbolProfile.assetSubClass,
|
||||||
|
comment: symbolProfile.comment,
|
||||||
dataSource: symbolProfile.dataSource,
|
dataSource: symbolProfile.dataSource,
|
||||||
date: symbolProfile.Order?.[0]?.date,
|
date: symbolProfile.Order?.[0]?.date,
|
||||||
symbol: symbolProfile.symbol
|
symbol: symbolProfile.symbol
|
||||||
@ -190,11 +192,13 @@ export class AdminService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async patchAssetProfileData({
|
public async patchAssetProfileData({
|
||||||
|
comment,
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
symbolMapping
|
symbolMapping
|
||||||
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
|
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
|
||||||
await this.symbolProfileService.updateSymbolProfile({
|
await this.symbolProfileService.updateSymbolProfile({
|
||||||
|
comment,
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
symbolMapping
|
symbolMapping
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { IsObject, IsOptional } from 'class-validator';
|
import { IsObject, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateAssetProfileDto {
|
export class UpdateAssetProfileDto {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
comment?: string;
|
||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
symbolMapping?: {
|
symbolMapping?: {
|
||||||
|
@ -104,12 +104,13 @@ export class SymbolProfileService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updateSymbolProfile({
|
public updateSymbolProfile({
|
||||||
|
comment,
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
symbolMapping
|
symbolMapping
|
||||||
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
|
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
|
||||||
return this.prismaService.symbolProfile.update({
|
return this.prismaService.symbolProfile.update({
|
||||||
data: { symbolMapping },
|
data: { comment, symbolMapping },
|
||||||
where: { dataSource_symbol: { dataSource, symbol } }
|
where: { dataSource_symbol: { dataSource, symbol } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|||||||
import { DATE_FORMAT, getDateFormatString } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, getDateFormatString } from '@ghostfolio/common/helper';
|
||||||
import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
|
import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
|
||||||
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
||||||
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { AssetSubClass, DataSource } from '@prisma/client';
|
import { AssetSubClass, DataSource } from '@prisma/client';
|
||||||
import { format, parseISO } from 'date-fns';
|
import { format, parseISO } from 'date-fns';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
@ -44,10 +45,10 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
|||||||
AssetSubClass.PRECIOUS_METAL,
|
AssetSubClass.PRECIOUS_METAL,
|
||||||
AssetSubClass.PRIVATE_EQUITY,
|
AssetSubClass.PRIVATE_EQUITY,
|
||||||
AssetSubClass.STOCK
|
AssetSubClass.STOCK
|
||||||
].map((id) => {
|
].map((assetSubClass) => {
|
||||||
return {
|
return {
|
||||||
id,
|
id: assetSubClass,
|
||||||
label: id,
|
label: translate(assetSubClass),
|
||||||
type: 'ASSET_SUB_CLASS'
|
type: 'ASSET_SUB_CLASS'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -67,6 +68,7 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
|||||||
'marketDataItemCount',
|
'marketDataItemCount',
|
||||||
'sectorsCount',
|
'sectorsCount',
|
||||||
'countriesCount',
|
'countriesCount',
|
||||||
|
'comment',
|
||||||
'actions'
|
'actions'
|
||||||
];
|
];
|
||||||
public filters$ = new Subject<Filter[]>();
|
public filters$ = new Subject<Filter[]>();
|
||||||
|
@ -100,6 +100,22 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="comment">
|
||||||
|
<th
|
||||||
|
*matHeaderCellDef
|
||||||
|
class="px-1"
|
||||||
|
mat-header-cell
|
||||||
|
mat-sort-header
|
||||||
|
></th>
|
||||||
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="element.comment"
|
||||||
|
class="d-block"
|
||||||
|
name="document-text-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
||||||
<button
|
<button
|
||||||
|
@ -30,6 +30,7 @@ import { AssetProfileDialogParams } from './interfaces/interfaces';
|
|||||||
export class AssetProfileDialog implements OnDestroy, OnInit {
|
export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||||
public assetProfile: EnhancedSymbolProfile;
|
public assetProfile: EnhancedSymbolProfile;
|
||||||
public assetProfileForm = this.formBuilder.group({
|
public assetProfileForm = this.formBuilder.group({
|
||||||
|
comment: '',
|
||||||
symbolMapping: ''
|
symbolMapping: ''
|
||||||
});
|
});
|
||||||
public countries: {
|
public countries: {
|
||||||
@ -86,6 +87,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.assetProfileForm.setValue({
|
this.assetProfileForm.setValue({
|
||||||
|
comment: this.assetProfile?.comment,
|
||||||
symbolMapping: JSON.stringify(this.assetProfile?.symbolMapping)
|
symbolMapping: JSON.stringify(this.assetProfile?.symbolMapping)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,7 +131,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
const assetProfileData: UpdateAssetProfileDto = {
|
const assetProfileData: UpdateAssetProfileDto = {
|
||||||
symbolMapping
|
symbolMapping,
|
||||||
|
comment: this.assetProfileForm.controls['comment'].value ?? null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.adminService
|
this.adminService
|
||||||
|
@ -148,6 +148,18 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
|
<mat-label i18n>Note</mat-label>
|
||||||
|
<textarea
|
||||||
|
cdkAutosizeMinRows="2"
|
||||||
|
cdkTextareaAutosize
|
||||||
|
formControlName="comment"
|
||||||
|
matInput
|
||||||
|
(keyup.enter)="$event.stopPropagation()"
|
||||||
|
></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end" mat-dialog-actions>
|
<div class="d-flex justify-content-end" mat-dialog-actions>
|
||||||
|
@ -127,13 +127,14 @@ export class AdminService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public patchAssetProfile({
|
public patchAssetProfile({
|
||||||
|
comment,
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
symbolMapping
|
symbolMapping
|
||||||
}: UniqueAsset & UpdateAssetProfileDto) {
|
}: UniqueAsset & UpdateAssetProfileDto) {
|
||||||
return this.http.patch<EnhancedSymbolProfile>(
|
return this.http.patch<EnhancedSymbolProfile>(
|
||||||
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
|
`/api/v1/admin/profile-data/${dataSource}/${symbol}`,
|
||||||
{ symbolMapping }
|
{ comment, symbolMapping }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +360,7 @@ export class DataService {
|
|||||||
const {
|
const {
|
||||||
ACCOUNT: filtersByAccount,
|
ACCOUNT: filtersByAccount,
|
||||||
ASSET_CLASS: filtersByAssetClass,
|
ASSET_CLASS: filtersByAssetClass,
|
||||||
|
ASSET_SUB_CLASS: filtersByAssetSubClass,
|
||||||
TAG: filtersByTag
|
TAG: filtersByTag
|
||||||
} = groupBy(filters, (filter) => {
|
} = groupBy(filters, (filter) => {
|
||||||
return filter.type;
|
return filter.type;
|
||||||
@ -387,6 +388,17 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filtersByAssetSubClass) {
|
||||||
|
params = params.append(
|
||||||
|
'assetSubClasses',
|
||||||
|
filtersByAssetSubClass
|
||||||
|
.map(({ id }) => {
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
.join(',')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (filtersByTag) {
|
if (filtersByTag) {
|
||||||
params = params.append(
|
params = params.append(
|
||||||
'tags',
|
'tags',
|
||||||
|
@ -8,6 +8,7 @@ export interface EnhancedSymbolProfile {
|
|||||||
activitiesCount: number;
|
activitiesCount: number;
|
||||||
assetClass: AssetClass;
|
assetClass: AssetClass;
|
||||||
assetSubClass: AssetSubClass;
|
assetSubClass: AssetSubClass;
|
||||||
|
comment?: string;
|
||||||
countries: Country[];
|
countries: Country[];
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
currency: string | null;
|
currency: string | null;
|
||||||
|
@ -299,6 +299,35 @@
|
|||||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="comment">
|
||||||
|
<th
|
||||||
|
*matHeaderCellDef
|
||||||
|
class="d-none d-lg-table-cell px-1"
|
||||||
|
mat-header-cell
|
||||||
|
mat-sort-header
|
||||||
|
></th>
|
||||||
|
<td
|
||||||
|
*matCellDef="let element"
|
||||||
|
class="d-none d-lg-table-cell px-1"
|
||||||
|
mat-cell
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
*ngIf="element.comment"
|
||||||
|
class="mx-1 no-min-width px-2"
|
||||||
|
mat-button
|
||||||
|
title="Note"
|
||||||
|
(click)="onOpenComment(element.comment); $event.stopPropagation()"
|
||||||
|
>
|
||||||
|
<ion-icon name="document-text-outline"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
*matFooterCellDef
|
||||||
|
class="d-none d-lg-table-cell px-1"
|
||||||
|
mat-footer-cell
|
||||||
|
></td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell>
|
||||||
<button
|
<button
|
||||||
@ -345,15 +374,6 @@
|
|||||||
</mat-menu>
|
</mat-menu>
|
||||||
</th>
|
</th>
|
||||||
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
||||||
<button
|
|
||||||
*ngIf="element.comment && !this.showActions"
|
|
||||||
class="mx-1 no-min-width px-2"
|
|
||||||
mat-button
|
|
||||||
title="Note"
|
|
||||||
(click)="onOpenComment(element.comment)"
|
|
||||||
>
|
|
||||||
<ion-icon name="document-text-outline"></ion-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
*ngIf="this.showActions"
|
*ngIf="this.showActions"
|
||||||
class="mx-1 no-min-width px-2"
|
class="mx-1 no-min-width px-2"
|
||||||
|
@ -94,6 +94,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
|||||||
'currency',
|
'currency',
|
||||||
'valueInBaseCurrency',
|
'valueInBaseCurrency',
|
||||||
'account',
|
'account',
|
||||||
|
'comment',
|
||||||
'actions'
|
'actions'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "SymbolProfile" ADD COLUMN "comment" TEXT;
|
@ -112,6 +112,7 @@ model Settings {
|
|||||||
model SymbolProfile {
|
model SymbolProfile {
|
||||||
assetClass AssetClass?
|
assetClass AssetClass?
|
||||||
assetSubClass AssetSubClass?
|
assetSubClass AssetSubClass?
|
||||||
|
comment String?
|
||||||
countries Json?
|
countries Json?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
currency String
|
currency String
|
||||||
|
Loading…
x
Reference in New Issue
Block a user