Browse Source

Merge branch 'feature/add-accounts-import-export' of https://github.com/yksolanki9/ghostfolio into feature/add-accounts-import-export

pull/1635/head
yksolanki9 3 years ago
parent
commit
17a78a81a4
  1. 13
      CHANGELOG.md
  2. 3
      apps/api/src/app/export/export.service.ts
  3. 4
      apps/api/src/app/import/import.controller.ts
  4. 22
      apps/api/src/app/import/import.service.ts
  5. 14
      apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html
  6. 6
      apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts
  7. 16
      apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss
  8. 2
      apps/client/src/app/pages/portfolio/activities/activities-page.html
  9. 4
      apps/client/src/app/pages/portfolio/activities/activities-page.module.ts
  10. 2
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  11. 28
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  12. 14
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.module.ts
  13. 35
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.scss
  14. 48
      test/import/ok-without-accounts.json
  15. 21
      test/import/ok.json

13
CHANGELOG.md

@ -5,6 +5,19 @@ 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
- Added support to export accounts
- Added suport to import accounts
### Changed
- Migrated the style of `ActivitiesPageModule` to `@angular/material` `15` (mdc)
- Migrated the style of `GfCreateOrUpdateActivityDialogModule` to `@angular/material` `15` (mdc)
- Migrated the style of `GfMarketDataDetailDialogModule` to `@angular/material` `15` (mdc)
## 1.231.0 - 2023-02-04 ## 1.231.0 - 2023-02-04
### Added ### Added

3
apps/api/src/app/export/export.service.ts

@ -15,6 +15,9 @@ export class ExportService {
userId: string; userId: string;
}): Promise<Export> { }): Promise<Export> {
const accounts = await this.prismaService.account.findMany({ const accounts = await this.prismaService.account.findMany({
orderBy: {
name: 'asc'
},
select: { select: {
accountType: true, accountType: true,
balance: true, balance: true,

4
apps/api/src/app/import/import.controller.ts

@ -68,11 +68,11 @@ export class ImportController {
try { try {
const activities = await this.importService.import({ const activities = await this.importService.import({
accountsDto: importData.accounts || [],
activitiesDto: importData.activities,
isDryRun, isDryRun,
maxActivitiesToImport, maxActivitiesToImport,
userCurrency, userCurrency,
accountsDto: importData.accounts ?? [],
activitiesDto: importData.activities,
userId: this.request.user.id userId: this.request.user.id
}); });

22
apps/api/src/app/import/import.service.ts

@ -120,20 +120,22 @@ export class ImportService {
const existingAccounts = await this.accountService.accounts({ const existingAccounts = await this.accountService.accounts({
where: { where: {
id: { id: {
in: accountsDto.map((account) => account.id) in: accountsDto.map(({ id }) => {
return id;
})
} }
} }
}); });
//Create new accounts during dryRun so that new account IDs don't get invalidated // Create new accounts during dryRun so that new account IDs don't get invalidated
if (isDryRun && accountsDto?.length) { if (isDryRun && accountsDto?.length) {
for (let account of accountsDto) { for (const account of accountsDto) {
//Check if there is any existing account with the same ID // Check if there is any existing account with the same ID
const accountWithSameId = existingAccounts.find( const accountWithSameId = existingAccounts.find(
(existingAccount) => existingAccount.id === account.id (existingAccount) => existingAccount.id === account.id
); );
//If there is no account or if the account belongs to a different user then create a new account // If there is no account or if the account belongs to a different user then create a new account
if (!accountWithSameId || accountWithSameId.userId !== userId) { if (!accountWithSameId || accountWithSameId.userId !== userId) {
let oldAccountId: string; let oldAccountId: string;
const platformId = account.platformId; const platformId = account.platformId;
@ -145,23 +147,23 @@ export class ImportService {
delete account.id; delete account.id;
} }
const newAccountObj = { const newAccountObject = {
...account, ...account,
User: { connect: { id: userId } } User: { connect: { id: userId } }
}; };
if (platformId) { if (platformId) {
Object.assign(newAccountObj, { Object.assign(newAccountObject, {
Platform: { connect: { id: platformId } } Platform: { connect: { id: platformId } }
}); });
} }
const newAccount = await this.accountService.createAccount( const newAccount = await this.accountService.createAccount(
newAccountObj, newAccountObject,
userId userId
); );
//Store the new to old account ID mappings for updating activities // Store the new to old account ID mappings for updating activities
if (accountWithSameId && oldAccountId) { if (accountWithSameId && oldAccountId) {
accountIdMapping[oldAccountId] = newAccount.id; accountIdMapping[oldAccountId] = newAccount.id;
} }
@ -178,7 +180,7 @@ export class ImportService {
} }
} }
//If a new account is created, then update the accountId in all activities // If a new account is created, then update the accountId in all activities
if (isDryRun) { if (isDryRun) {
if (Object.keys(accountIdMapping).includes(activity.accountId)) { if (Object.keys(accountIdMapping).includes(activity.accountId)) {
activity.accountId = accountIdMapping[activity.accountId]; activity.accountId = accountIdMapping[activity.accountId];

14
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html

@ -1,6 +1,6 @@
<form class="d-flex flex-column h-100"> <form class="d-flex flex-column h-100">
<h1 i18n mat-dialog-title>Details for {{ data.symbol }}</h1> <h1 i18n mat-dialog-title>Details for {{ data.symbol }}</h1>
<div class="flex-grow-1" mat-dialog-content> <div class="flex-grow-1 pt-3" mat-dialog-content>
<div class="mb-3"> <div class="mb-3">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Date</mat-label> <mat-label i18n>Date</mat-label>
@ -11,7 +11,7 @@
[matDatepicker]="date" [matDatepicker]="date"
[(ngModel)]="data.date" [(ngModel)]="data.date"
/> />
<mat-datepicker-toggle matSuffix [for]="date"> <mat-datepicker-toggle class="mr-2" matSuffix [for]="date">
<ion-icon <ion-icon
class="text-muted" class="text-muted"
matDatepickerToggleIcon matDatepickerToggleIcon
@ -21,7 +21,7 @@
<mat-datepicker #date disabled="true"></mat-datepicker> <mat-datepicker #date disabled="true"></mat-datepicker>
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="align-items-start d-flex">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Market Price</mat-label> <mat-label i18n>Market Price</mat-label>
<input <input
@ -30,16 +30,16 @@
type="number" type="number"
[(ngModel)]="data.marketPrice" [(ngModel)]="data.marketPrice"
/> />
<span class="ml-2" matSuffix>{{ data.currency }}</span> <span class="ml-2" matTextSuffix>{{ data.currency }}</span>
</mat-form-field>
<button <button
mat-icon-button class="apply-current-market-price ml-2 no-min-width"
matSuffix mat-button
title="Fetch market price" title="Fetch market price"
(click)="onFetchSymbolForDate()" (click)="onFetchSymbolForDate()"
> >
<ion-icon class="text-muted" name="refresh-outline"></ion-icon> <ion-icon class="text-muted" name="refresh-outline"></ion-icon>
</button> </button>
</mat-form-field>
</div> </div>
</div> </div>
<div class="justify-content-end" mat-dialog-actions> <div class="justify-content-end" mat-dialog-actions>

6
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts

@ -2,10 +2,10 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; import { MatButtonModule } from '@angular/material/button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog'; import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input'; import { MatInputModule } from '@angular/material/input';
import { MarketDataDetailDialog } from './market-data-detail-dialog.component'; import { MarketDataDetailDialog } from './market-data-detail-dialog.component';

16
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss

@ -4,19 +4,9 @@
.mat-dialog-content { .mat-dialog-content {
max-height: unset; max-height: unset;
.mat-form-field-appearance-outline { .mat-mdc-button {
::ng-deep { &.apply-current-market-price {
.mat-form-field-suffix { height: 56px;
top: -0.3rem;
}
.mat-form-field-wrapper {
padding-bottom: 0;
}
}
ion-icon {
font-size: 130%;
} }
} }
} }

2
apps/client/src/app/pages/portfolio/activities/activities-page.html

@ -33,7 +33,7 @@
[queryParams]="{ createDialog: true }" [queryParams]="{ createDialog: true }"
[routerLink]="[]" [routerLink]="[]"
> >
<ion-icon name="add-outline" size="large"></ion-icon> <ion-icon class="mt-2" name="add-outline" size="large"></ion-icon>
</a> </a>
</div> </div>
</div> </div>

4
apps/client/src/app/pages/portfolio/activities/activities-page.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; import { MatButtonModule } from '@angular/material/button';
import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module'; import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';

2
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -10,7 +10,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MatLegacyAutocompleteSelectedEvent as MatAutocompleteSelectedEvent } from '@angular/material/legacy-autocomplete'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef MatLegacyDialogRef as MatDialogRef

28
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -6,7 +6,7 @@
> >
<h1 *ngIf="data.activity.id" i18n mat-dialog-title>Update activity</h1> <h1 *ngIf="data.activity.id" i18n mat-dialog-title>Update activity</h1>
<h1 *ngIf="!data.activity.id" i18n mat-dialog-title>Add activity</h1> <h1 *ngIf="!data.activity.id" i18n mat-dialog-title>Add activity</h1>
<div class="flex-grow-1" mat-dialog-content> <div class="flex-grow-1 pt-3" mat-dialog-content>
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Type</mat-label> <mat-label i18n>Type</mat-label>
@ -76,7 +76,7 @@
<div class="d-none"> <div class="d-none">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Currency</mat-label> <mat-label i18n>Currency</mat-label>
<mat-select class="no-arrow" formControlName="currency"> <mat-select formControlName="currency">
<mat-option *ngFor="let currency of currencies" [value]="currency" <mat-option *ngFor="let currency of currencies" [value]="currency"
>{{ currency }}</mat-option >{{ currency }}</mat-option
> >
@ -93,7 +93,7 @@
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Date</mat-label> <mat-label i18n>Date</mat-label>
<input formControlName="date" matInput [matDatepicker]="date" /> <input formControlName="date" matInput [matDatepicker]="date" />
<mat-datepicker-toggle matSuffix [for]="date"> <mat-datepicker-toggle class="mr-2" matSuffix [for]="date">
<ion-icon <ion-icon
class="text-muted" class="text-muted"
matDatepickerToggleIcon matDatepickerToggleIcon
@ -109,7 +109,7 @@
<input formControlName="quantity" matInput type="number" /> <input formControlName="quantity" matInput type="number" />
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div class="align-items-start d-flex">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label <mat-label
><ng-container [ngSwitch]="activityForm.controls['type']?.value"> ><ng-container [ngSwitch]="activityForm.controls['type']?.value">
@ -121,20 +121,20 @@
</ng-container> </ng-container>
</mat-label> </mat-label>
<input formControlName="unitPrice" matInput type="number" /> <input formControlName="unitPrice" matInput type="number" />
<span class="ml-2" matSuffix <span class="ml-2" matTextSuffix
>{{ activityForm.controls['currency'].value }}</span >{{ activityForm.controls['currency'].value }}</span
> >
</mat-form-field>
<button <button
*ngIf="currentMarketPrice && (data.activity.type === 'BUY' || data.activity.type === 'SELL')" *ngIf="currentMarketPrice && (data.activity.type === 'BUY' || data.activity.type === 'SELL')"
mat-icon-button class="apply-current-market-price ml-2 no-min-width"
matSuffix mat-button
title="Apply current market price" title="Apply current market price"
type="button" type="button"
(click)="applyCurrentMarketPrice()" (click)="applyCurrentMarketPrice()"
> >
<ion-icon class="text-muted" name="refresh-outline"></ion-icon> <ion-icon class="text-muted" name="refresh-outline"></ion-icon>
</button> </button>
</mat-form-field>
</div> </div>
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
@ -142,7 +142,7 @@
<input formControlName="feeInCustomCurrency" matInput type="number" /> <input formControlName="feeInCustomCurrency" matInput type="number" />
<div <div
class="ml-2" class="ml-2"
matSuffix matTextSuffix
[ngClass]="{ 'd-none': !activityForm.controls['currency']?.value }" [ngClass]="{ 'd-none': !activityForm.controls['currency']?.value }"
> >
<mat-select formControlName="currencyOfFee"> <mat-select formControlName="currencyOfFee">
@ -157,7 +157,7 @@
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Fee</mat-label> <mat-label i18n>Fee</mat-label>
<input formControlName="fee" matInput type="number" /> <input formControlName="fee" matInput type="number" />
<span class="ml-2" matSuffix <span class="ml-2" matTextSuffix
>{{ activityForm.controls['currency'].value }}</span >{{ activityForm.controls['currency'].value }}</span
> >
</mat-form-field> </mat-form-field>
@ -207,8 +207,8 @@
<div [ngClass]="{ 'd-none': tags?.length <= 0 }"> <div [ngClass]="{ 'd-none': tags?.length <= 0 }">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Tags</mat-label> <mat-label i18n>Tags</mat-label>
<mat-chip-list #tagsChipList> <mat-chip-grid #tagsChipList>
<mat-chip <mat-chip-row
*ngFor="let tag of activityForm.controls['tags']?.value" *ngFor="let tag of activityForm.controls['tags']?.value"
matChipRemove matChipRemove
[removable]="true" [removable]="true"
@ -216,7 +216,7 @@
> >
{{ tag.name }} {{ tag.name }}
<ion-icon class="ml-2" matPrefix name="close-outline"></ion-icon> <ion-icon class="ml-2" matPrefix name="close-outline"></ion-icon>
</mat-chip> </mat-chip-row>
<input <input
#tagInput #tagInput
name="close-outline" name="close-outline"
@ -224,7 +224,7 @@
[matChipInputFor]="tagsChipList" [matChipInputFor]="tagsChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
/> />
</mat-chip-list> </mat-chip-grid>
<mat-autocomplete <mat-autocomplete
#autocompleteTags="matAutocomplete" #autocompleteTags="matAutocomplete"
(optionSelected)="onAddTag($event)" (optionSelected)="onAddTag($event)"

14
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.module.ts

@ -2,14 +2,14 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; import { MatButtonModule } from '@angular/material/button';
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog'; import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input'; import { MatInputModule } from '@angular/material/input';
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select'; import { MatSelectModule } from '@angular/material/select';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { GfValueModule } from '@ghostfolio/ui/value'; import { GfValueModule } from '@ghostfolio/ui/value';

35
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.scss

@ -20,36 +20,15 @@
} }
} }
.mat-chip { .mat-datepicker-input {
cursor: pointer; &.mat-mdc-input-element:disabled {
min-height: 1.5rem !important; color: var(--dark-primary-text);
}
.mat-form-field-appearance-outline {
::ng-deep {
.mat-form-field-suffix {
top: -0.3rem;
}
}
ion-icon {
font-size: 130%;
}
}
.mat-select {
&.no-arrow {
::ng-deep {
.mat-select-arrow {
opacity: 0;
}
}
} }
} }
.mat-datepicker-input { .mat-mdc-button {
&.mat-input-element:disabled { &.apply-current-market-price {
color: var(--dark-primary-text); height: 56px;
} }
} }
} }
@ -58,7 +37,7 @@
:host-context(.is-dark-theme) { :host-context(.is-dark-theme) {
.mat-dialog-content { .mat-dialog-content {
.mat-datepicker-input { .mat-datepicker-input {
&.mat-input-element:disabled { &.mat-mdc-input-element:disabled {
color: var(--light-primary-text); color: var(--light-primary-text);
} }
} }

48
test/import/ok-without-accounts.json

@ -0,0 +1,48 @@
{
"meta": {
"date": "2022-04-01T00:00:00.000Z",
"version": "dev"
},
"activities": [
{
"fee": 0,
"quantity": 0,
"type": "BUY",
"unitPrice": 0,
"currency": "USD",
"dataSource": "YAHOO",
"date": "2050-06-05T22:00:00.000Z",
"symbol": "MSFT"
},
{
"fee": 0,
"quantity": 1,
"type": "ITEM",
"unitPrice": 500000,
"currency": "USD",
"dataSource": "MANUAL",
"date": "2021-12-31T22:00:00.000Z",
"symbol": "Penthouse Apartment"
},
{
"fee": 0,
"quantity": 5,
"type": "DIVIDEND",
"unitPrice": 0.62,
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-11-16T22:00:00.000Z",
"symbol": "MSFT"
},
{
"fee": 19,
"quantity": 5,
"type": "BUY",
"unitPrice": 298.58,
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-09-15T22:00:00.000Z",
"symbol": "MSFT"
}
]
}

21
test/import/ok.json

@ -1,10 +1,23 @@
{ {
"meta": { "meta": {
"date": "2022-04-01T00:00:00.000Z", "date": "2023-02-05T00:00:00.000Z",
"version": "dev" "version": "dev"
}, },
"accounts": [
{
"accountType": "SECURITIES",
"balance": 2000,
"currency": "USD",
"id": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0",
"isExcluded": false,
"name": "My Online Trading Account",
"platformId": null
}
],
"activities": [ "activities": [
{ {
"accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0",
"comment": null,
"fee": 0, "fee": 0,
"quantity": 0, "quantity": 0,
"type": "BUY", "type": "BUY",
@ -15,6 +28,8 @@
"symbol": "MSFT" "symbol": "MSFT"
}, },
{ {
"accountId": null,
"comment": null,
"fee": 0, "fee": 0,
"quantity": 1, "quantity": 1,
"type": "ITEM", "type": "ITEM",
@ -25,6 +40,8 @@
"symbol": "Penthouse Apartment" "symbol": "Penthouse Apartment"
}, },
{ {
"accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0",
"comment": null,
"fee": 0, "fee": 0,
"quantity": 5, "quantity": 5,
"type": "DIVIDEND", "type": "DIVIDEND",
@ -35,6 +52,8 @@
"symbol": "MSFT" "symbol": "MSFT"
}, },
{ {
"accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0",
"comment": "My first order",
"fee": 19, "fee": 19,
"quantity": 5, "quantity": 5,
"type": "BUY", "type": "BUY",

Loading…
Cancel
Save