mirror of https://github.com/ghostfolio/ghostfolio
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							193 lines
						
					
					
						
							4.8 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							193 lines
						
					
					
						
							4.8 KiB
						
					
					
				
								import { HttpClient } from '@angular/common/http';
							 | 
						|
								import { Injectable } from '@angular/core';
							 | 
						|
								import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
							 | 
						|
								import { Type } from '@prisma/client';
							 | 
						|
								import { parse } from 'date-fns';
							 | 
						|
								import { isNumber } from 'lodash';
							 | 
						|
								import { parse as csvToJson } from 'papaparse';
							 | 
						|
								import { EMPTY } from 'rxjs';
							 | 
						|
								import { catchError } from 'rxjs/operators';
							 | 
						|
								
							 | 
						|
								@Injectable({
							 | 
						|
								  providedIn: 'root'
							 | 
						|
								})
							 | 
						|
								export class ImportTransactionsService {
							 | 
						|
								  private static CURRENCY_KEYS = ['ccy', 'currency'];
							 | 
						|
								  private static DATE_KEYS = ['date'];
							 | 
						|
								  private static FEE_KEYS = ['fee'];
							 | 
						|
								  private static QUANTITY_KEYS = ['qty', 'quantity'];
							 | 
						|
								  private static SYMBOL_KEYS = ['code', 'symbol'];
							 | 
						|
								  private static TYPE_KEYS = ['action', 'type'];
							 | 
						|
								  private static UNIT_PRICE_KEYS = ['price', 'unitprice'];
							 | 
						|
								
							 | 
						|
								  public constructor(private http: HttpClient) {}
							 | 
						|
								
							 | 
						|
								  public async importCsv({
							 | 
						|
								    defaultAccountId,
							 | 
						|
								    fileContent
							 | 
						|
								  }: {
							 | 
						|
								    defaultAccountId: string;
							 | 
						|
								    fileContent: string;
							 | 
						|
								  }) {
							 | 
						|
								    const content = csvToJson(fileContent, {
							 | 
						|
								      dynamicTyping: true,
							 | 
						|
								      header: true,
							 | 
						|
								      skipEmptyLines: true
							 | 
						|
								    }).data;
							 | 
						|
								
							 | 
						|
								    const orders: CreateOrderDto[] = [];
							 | 
						|
								
							 | 
						|
								    for (const item of content) {
							 | 
						|
								      orders.push({
							 | 
						|
								        accountId: defaultAccountId,
							 | 
						|
								        currency: this.parseCurrency(item),
							 | 
						|
								        dataSource: 'YAHOO',
							 | 
						|
								        date: this.parseDate(item),
							 | 
						|
								        fee: this.parseFee(item),
							 | 
						|
								        quantity: this.parseQuantity(item),
							 | 
						|
								        symbol: this.parseSymbol(item),
							 | 
						|
								        type: this.parseType(item),
							 | 
						|
								        unitPrice: this.parseUnitPrice(item)
							 | 
						|
								      });
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    await this.importJson({ defaultAccountId, content: orders });
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  public importJson({
							 | 
						|
								    content,
							 | 
						|
								    defaultAccountId
							 | 
						|
								  }: {
							 | 
						|
								    content: CreateOrderDto[];
							 | 
						|
								    defaultAccountId: string;
							 | 
						|
								  }): Promise<void> {
							 | 
						|
								    return new Promise((resolve, reject) => {
							 | 
						|
								      this.postImport({
							 | 
						|
								        orders: content.map((order) => {
							 | 
						|
								          return { ...order, accountId: defaultAccountId };
							 | 
						|
								        })
							 | 
						|
								      })
							 | 
						|
								        .pipe(
							 | 
						|
								          catchError((error) => {
							 | 
						|
								            reject(error);
							 | 
						|
								            return EMPTY;
							 | 
						|
								          })
							 | 
						|
								        )
							 | 
						|
								        .subscribe({
							 | 
						|
								          next: () => {
							 | 
						|
								            resolve();
							 | 
						|
								          }
							 | 
						|
								        });
							 | 
						|
								    });
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private lowercaseKeys(aObject: any) {
							 | 
						|
								    return Object.keys(aObject).reduce((acc, key) => {
							 | 
						|
								      acc[key.toLowerCase()] = aObject[key];
							 | 
						|
								      return acc;
							 | 
						|
								    }, {});
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseCurrency(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.CURRENCY_KEYS) {
							 | 
						|
								      if (item[key]) {
							 | 
						|
								        return item[key];
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse currency');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseDate(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								    let date: string;
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.DATE_KEYS) {
							 | 
						|
								      if (item[key]) {
							 | 
						|
								        try {
							 | 
						|
								          date = parse(item[key], 'dd-MM-yyyy', new Date()).toISOString();
							 | 
						|
								        } catch {}
							 | 
						|
								
							 | 
						|
								        try {
							 | 
						|
								          date = parse(item[key], 'dd/MM/yyyy', new Date()).toISOString();
							 | 
						|
								        } catch {}
							 | 
						|
								
							 | 
						|
								        if (date) {
							 | 
						|
								          return date;
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse date');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseFee(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.FEE_KEYS) {
							 | 
						|
								      if ((item[key] || item[key] === 0) && isNumber(item[key])) {
							 | 
						|
								        return item[key];
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse fee');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseQuantity(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.QUANTITY_KEYS) {
							 | 
						|
								      if (item[key] && isNumber(item[key])) {
							 | 
						|
								        return item[key];
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse quantity');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseSymbol(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.SYMBOL_KEYS) {
							 | 
						|
								      if (item[key]) {
							 | 
						|
								        return item[key];
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse symbol');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseType(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.TYPE_KEYS) {
							 | 
						|
								      if (item[key]) {
							 | 
						|
								        if (item[key].toLowerCase() === 'buy') {
							 | 
						|
								          return Type.BUY;
							 | 
						|
								        } else if (item[key].toLowerCase() === 'sell') {
							 | 
						|
								          return Type.SELL;
							 | 
						|
								        }
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse type');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private parseUnitPrice(aItem: any) {
							 | 
						|
								    const item = this.lowercaseKeys(aItem);
							 | 
						|
								
							 | 
						|
								    for (const key of ImportTransactionsService.UNIT_PRICE_KEYS) {
							 | 
						|
								      if (item[key] && isNumber(item[key])) {
							 | 
						|
								        return item[key];
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    throw new Error('Could not parse unit price (unitPrice)');
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  private postImport(aImportData: { orders: CreateOrderDto[] }) {
							 | 
						|
								    return this.http.post<void>('/api/import', aImportData);
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								
							 |