| 
						
						
						
					 | 
				
				 | 
				
					@ -1,13 +1,14 @@ | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfFileDropModule } from '@ghostfolio/client/directives/file-drop/file-drop.module'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { DataService } from '@ghostfolio/client/services/data.service'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfDialogFooterComponent } from '../../../../components/dialog-footer/dialog-footer.component'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfDialogHeaderComponent } from '../../../../components/dialog-header/dialog-header.component'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfFileDropModule } from '../../../../directives/file-drop/file-drop.module'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfSymbolModule } from '../../../../pipes/symbol/symbol.module'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { DataService } from '../../../../services/data.service'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { ImportActivitiesService } from '../../../../services/import-activities.service'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { PortfolioPosition } from '@ghostfolio/common/interfaces'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
				 | 
				
					@ -209,7 +210,13 @@ export class GfImportActivitiesDialog implements OnDestroy { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      return; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    this.handleFile({ stepper, file: files[0] }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    if (files.length === 1) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      // Single file import (original behavior)
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.handleFile({ stepper, file: files[0] }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } else { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      // Multiple files import (bulk import)
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.handleMultipleFiles({ stepper, files }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  public onImportStepChange(event: StepperSelectionEvent) { | 
				
			
			
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
				 | 
				
					@ -257,11 +264,19 @@ export class GfImportActivitiesDialog implements OnDestroy { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const input = document.createElement('input'); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    input.accept = 'application/JSON, .csv'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    input.type = 'file'; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    input.multiple = true; // Allow multiple file selection
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    input.onchange = (event) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      // Getting the file reference
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      const file = (event.target as HTMLInputElement).files[0]; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.handleFile({ file, stepper }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      const files = (event.target as HTMLInputElement).files; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      if (files && files.length > 0) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (files.length === 1) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          // Single file import (original behavior)
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          this.handleFile({ file: files[0], stepper }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } else { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          // Multiple files import (bulk import)
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          this.handleMultipleFiles({ stepper, files }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    }; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    input.click(); | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -278,6 +293,127 @@ export class GfImportActivitiesDialog implements OnDestroy { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    this.unsubscribeSubject.complete(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  private async handleMultipleFiles({ | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    files, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    stepper | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  }: { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    files: FileList; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    stepper: MatStepper; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  }): Promise<void> { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    this.snackBar.open(`⏳ Processing ${files.length} files...`); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const allActivities: CreateOrderDto[] = []; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const allAccounts: CreateAccountWithBalancesDto[] = []; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const allAssetProfiles: CreateAssetProfileWithMarketDataDto[] = []; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    const allTags: CreateTagDto[] = []; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    let filesProcessed = 0; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    let hasErrors = false; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    for (let i = 0; i < files.length; i++) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      const file = files[i]; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      const fileExtension = file.name.split('.').pop()?.toLowerCase(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      if (fileExtension !== 'json') { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        continue; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      try { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        const fileContent = await this.readFileContent(file); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        const content = JSON.parse(fileContent); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        // Merge accounts, avoiding duplicates
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (content.accounts) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          content.accounts.forEach((account: CreateAccountWithBalancesDto) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            if (!allAccounts.find(a => a.id === account.id)) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					              allAccounts.push(account); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        // Merge asset profiles, avoiding duplicates
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (content.assetProfiles) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          content.assetProfiles.forEach((profile: CreateAssetProfileWithMarketDataDto) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            if (!allAssetProfiles.find(p => p.symbol === profile.symbol)) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					              allAssetProfiles.push(profile); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        // Merge tags, avoiding duplicates
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (content.tags) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          content.tags.forEach((tag: CreateTagDto) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            if (!allTags.find(t => t.id === tag.id)) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					              allTags.push(tag); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        // Add activities
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        if (isArray(content.activities)) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          const cleanedActivities = content.activities.map((activity: any) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            if (activity.id) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					              delete activity.id; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					            return activity as CreateOrderDto; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					          allActivities.push(...cleanedActivities); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        filesProcessed++; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        this.snackBar.open(`⏳ Processed ${filesProcessed}/${files.length} files...`); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      } catch (error) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        hasErrors = true; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    if (hasErrors) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.snackBar.open(`⚠️ Some files could not be processed`); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    // Store merged data
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    this.accounts = allAccounts; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    this.assetProfiles = allAssetProfiles; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    this.tags = allTags; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    try { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      // Validate all activities at once
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      const { activities } = await this.importActivitiesService.importJson({ | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        accounts: allAccounts, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        activities: allActivities, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        assetProfiles: allAssetProfiles, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        isDryRun: true, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        tags: allTags | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.activities = activities; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.dataSource = new MatTableDataSource(activities.reverse()); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.totalItems = activities.length; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.snackBar.open(`✅ Successfully processed ${filesProcessed} files with ${activities.length} activities`); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					       | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } catch (error) { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.handleImportError({ error, activities: allActivities as any }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.snackBar.open(`❌ Validation failed for bulk import`); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } finally { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.importStep = ImportStep.SELECT_ACTIVITIES; | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.snackBar.dismiss(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.updateSelection(this.activities); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      stepper.next(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      this.changeDetectorRef.markForCheck(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  private readFileContent(file: File): Promise<string> { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    return new Promise((resolve, reject) => { | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      const reader = new FileReader(); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      reader.onload = (event) => resolve(event.target?.result as string); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      reader.onerror = () => reject(reader.error); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      reader.readAsText(file, 'UTF-8'); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    }); | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  private async handleFile({ | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    file, | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    stepper | 
				
			
			
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
				 | 
				
					
  |