Browse Source

Merge 74f0cfdb31 into 084467ee9a

pull/3211/merge
Fedron 1 year ago
committed by GitHub
parent
commit
d167bcfe31
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 27
      apps/api/src/app/order/create-order.dto.ts
  2. 9
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts
  3. 4
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 1
      apps/api/src/helper/portfolio.helper.ts
  5. 46
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  6. 24
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  7. 5
      libs/ui/src/lib/activity-type/activity-type.component.html
  8. 1
      libs/ui/src/lib/i18n.ts
  9. 2
      prisma/migrations/20240329190053_added_split_to_order_type/migration.sql
  10. 1
      prisma/schema.prisma

27
apps/api/src/app/order/create-order.dto.ts

@ -15,10 +15,33 @@ import {
IsNumber,
IsOptional,
IsString,
Min
Min,
ValidationArguments,
ValidationOptions,
registerDecorator
} from 'class-validator';
import { isString } from 'lodash';
function IsQuantityValid(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isQuantityValid',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const order = args.object as CreateOrderDto;
return order.type !== 'SPLIT' ? value >= 0 : true;
},
defaultMessage(args: ValidationArguments) {
return `Quantity must not be less than 0 unless the type is ${Type.SPLIT}`;
}
}
});
};
}
export class CreateOrderDto {
@IsOptional()
@IsString()
@ -54,7 +77,7 @@ export class CreateOrderDto {
fee: number;
@IsNumber()
@Min(0)
@IsQuantityValid()
quantity: number;
@IsString()

9
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -792,9 +792,12 @@ export class PortfolioCalculator {
if (oldAccumulatedSymbol) {
let investment = oldAccumulatedSymbol.investment;
const newQuantity = quantity
.mul(factor)
.plus(oldAccumulatedSymbol.quantity);
const newQuantity =
type === 'SPLIT'
? quantity.s === 1
? oldAccumulatedSymbol.quantity.mul(quantity)
: oldAccumulatedSymbol.quantity.div(quantity.abs())
: quantity.mul(factor).plus(oldAccumulatedSymbol.quantity);
if (type === 'BUY') {
investment = oldAccumulatedSymbol.investment.plus(

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

@ -724,7 +724,9 @@ export class PortfolioService {
activities: orders.filter((order) => {
tags = tags.concat(order.tags);
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL', 'SPLIT'].includes(
order.type
);
}),
currency: userCurrency,
currentRateService: this.currentRateService,

1
apps/api/src/helper/portfolio.helper.ts

@ -19,6 +19,7 @@ export function getFactor(activityType: ActivityType) {
switch (activityType) {
case 'BUY':
case 'ITEM':
case 'SPLIT':
factor = 1;
break;
case 'LIABILITY':

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

@ -279,7 +279,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
if (this.activityForm.controls['searchSymbol'].invalid) {
this.data.activity.SymbolProfile = null;
} else if (
['BUY', 'DIVIDEND', 'SELL'].includes(
['BUY', 'DIVIDEND', 'SELL', 'SPLIT'].includes(
this.activityForm.controls['type'].value
)
) {
@ -394,6 +394,50 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.activityForm.controls['updateAccountBalance'].disable();
this.activityForm.controls['updateAccountBalance'].setValue(false);
}
} else if (type === 'SPLIT') {
this.activityForm.controls['currency'].removeValidators(
Validators.required
);
this.activityForm.controls['currency'].updateValueAndValidity();
this.activityForm.controls['currencyOfFee'].removeValidators(
Validators.required
);
this.activityForm.controls['currencyOfFee'].updateValueAndValidity();
this.activityForm.controls['currencyOfUnitPrice'].removeValidators(
Validators.required
);
this.activityForm.controls[
'currencyOfUnitPrice'
].updateValueAndValidity();
this.activityForm.controls['fee'].removeValidators(
Validators.required
);
this.activityForm.controls['fee'].updateValueAndValidity();
this.activityForm.controls['feeInCustomCurrency'].removeValidators(
Validators.required
);
this.activityForm.controls[
'feeInCustomCurrency'
].updateValueAndValidity();
this.activityForm.controls['unitPrice'].removeValidators(
Validators.required
);
this.activityForm.controls['unitPrice'].updateValueAndValidity();
this.activityForm.controls[
'unitPriceInCustomCurrency'
].removeValidators(Validators.required);
this.activityForm.controls[
'unitPriceInCustomCurrency'
].updateValueAndValidity();
this.activityForm.controls['updateAccountBalance'].disable();
this.activityForm.controls['updateAccountBalance'].setValue(false);
} else {
this.activityForm.controls['accountId'].setValidators(
Validators.required

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

@ -70,6 +70,14 @@
>Luxury items, real estate, private companies</small
>
</mat-option>
<mat-option value="SPLIT">
<span
><b>{{ typesTranslationMap['SPLIT'] }}</b></span
>
<small class="d-block line-height-1 text-muted text-nowrap" i18n
>Forward split, reverse split</small
>
</mat-option>
</mat-select>
</mat-form-field>
</div>
@ -180,13 +188,22 @@
}"
>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Quantity</mat-label>
<mat-label
><ng-container [ngSwitch]="activityForm.controls['type']?.value">
<ng-container *ngSwitchCase="'SPLIT'" i18n>Ratio</ng-container>
<ng-container *ngSwitchDefault i18n>Quantity</ng-container>
</ng-container>
</mat-label>
<input formControlName="quantity" matInput type="number" />
</mat-form-field>
</div>
<div
class="mb-3"
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' }"
[ngClass]="{
'd-none':
activityForm.controls['type']?.value === 'FEE' ||
activityForm.controls['type']?.value === 'SPLIT'
}"
>
<div class="align-items-start d-flex">
<mat-form-field appearance="outline" class="w-100">
@ -279,7 +296,8 @@
'd-none':
activityForm.controls['type']?.value === 'INTEREST' ||
activityForm.controls['type']?.value === 'ITEM' ||
activityForm.controls['type']?.value === 'LIABILITY'
activityForm.controls['type']?.value === 'LIABILITY' ||
activityForm.controls['type']?.value === 'SPLIT'
}"
>
<mat-form-field appearance="outline" class="w-100">

5
libs/ui/src/lib/activity-type/activity-type.component.html

@ -7,7 +7,8 @@
interest: activityType === 'INTEREST',
item: activityType === 'ITEM',
liability: activityType === 'LIABILITY',
sell: activityType === 'SELL'
sell: activityType === 'SELL',
split: activityType === 'SPLIT'
}"
>
@if (activityType === 'BUY') {
@ -22,6 +23,8 @@
<ion-icon name="flame-outline" />
} @else if (activityType === 'SELL') {
<ion-icon name="arrow-down-circle-outline" />
} @else if (activityType === 'SPLIT') {
<ion-icon name="git-branch-outline" />
}
<span class="d-none d-lg-block mx-1">{{ activityTypeLabel }}</span>
</div>

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

@ -34,6 +34,7 @@ const locales = {
ITEM: $localize`Valuable`,
LIABILITY: $localize`Liability`,
SELL: $localize`Sell`,
SPLIT: $localize`Stock Split`,
// AssetClass (enum)
CASH: $localize`Cash`,

2
prisma/migrations/20240329190053_added_split_to_order_type/migration.sql

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "Type" ADD VALUE 'SPLIT';

1
prisma/schema.prisma

@ -301,6 +301,7 @@ enum Type {
ITEM
LIABILITY
SELL
SPLIT
}
enum ViewMode {

Loading…
Cancel
Save