mirror of https://github.com/ghostfolio/ghostfolio
				
				
			
				 6 changed files with 257 additions and 34 deletions
			
			
		@ -0,0 +1,193 @@ | 
				
			|||
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); | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
					Loading…
					
					
				
		Reference in new issue