From f589ebffba4e7d80a99e3fdde24c73c1bfafdd80 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 15 May 2025 17:51:35 +0200 Subject: [PATCH] excel import --- httpdocs/composer.json | 1 + httpdocs/composer.lock | 583 +++++++++++++++++- httpdocs/import/Import-Template.xlsx | Bin 0 -> 15641 bytes .../src/Command/Import/ImportExcelCommand.php | 359 +++++++++++ 4 files changed, 942 insertions(+), 1 deletion(-) create mode 100644 httpdocs/import/Import-Template.xlsx create mode 100644 httpdocs/src/Command/Import/ImportExcelCommand.php diff --git a/httpdocs/composer.json b/httpdocs/composer.json index 02ea9a0..954d38e 100644 --- a/httpdocs/composer.json +++ b/httpdocs/composer.json @@ -16,6 +16,7 @@ "lexik/jwt-authentication-bundle": "^3.1", "nelmio/cors-bundle": "^2.5", "phpdocumentor/reflection-docblock": "^5.4", + "phpoffice/phpspreadsheet": "^4.2", "phpstan/phpdoc-parser": "^1.32", "symfony/asset": "7.1.*", "symfony/console": "7.1.*", diff --git a/httpdocs/composer.lock b/httpdocs/composer.lock index ba6925c..c9275b5 100644 --- a/httpdocs/composer.lock +++ b/httpdocs/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d97e4496ebfcc30dce08cc0102bd47e", + "content-hash": "c2075b4a00e7b723574dd47fae79eef2", "packages": [ { "name": "api-platform/doctrine-common", @@ -1112,6 +1112,85 @@ }, "time": "2025-03-10T10:30:30+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -2742,6 +2821,191 @@ ], "time": "2024-07-03T20:49:59+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.2" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-01-27T12:07:53+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "nelmio/cors-bundle", "version": "2.5.0", @@ -2979,6 +3243,112 @@ }, "time": "2024-02-23T11:10:43+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "5f6d7410e5fd72cac1aa67d4f05f4fe664d01ba6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/5f6d7410e5fd72cac1aa67d4f05f4fe664d01ba6", + "reference": "5f6d7410e5fd72cac1aa67d4f05f4fe664d01ba6", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/4.2.0" + }, + "time": "2025-04-17T02:41:45+00:00" + }, { "name": "phpstan/phpdoc-parser", "version": "1.32.0", @@ -3226,6 +3596,166 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, { "name": "psr/link", "version": "2.0.1", @@ -3332,6 +3862,57 @@ }, "time": "2024-09-11T13:17:53+00:00" }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, { "name": "symfony/asset", "version": "v7.1.1", diff --git a/httpdocs/import/Import-Template.xlsx b/httpdocs/import/Import-Template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..46d8bc336750992248ba7ebbc1dcbfa0d78a9395 GIT binary patch literal 15641 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 Bv?>4q literal 0 HcmV?d00001 diff --git a/httpdocs/src/Command/Import/ImportExcelCommand.php b/httpdocs/src/Command/Import/ImportExcelCommand.php new file mode 100644 index 0000000..af1be00 --- /dev/null +++ b/httpdocs/src/Command/Import/ImportExcelCommand.php @@ -0,0 +1,359 @@ +params->get('kernel.project_dir') . '/import/Import-Template.xlsx'; + + if (!file_exists($filePath)) { + $io->error('Excel file not found: ' . $filePath); + return Command::FAILURE; + } + + // Starte Transaktion + $this->entityManager->beginTransaction(); + + try { + $io->title('Starting Excel Import'); + + // Lade die Excel-Datei + $spreadsheet = IOFactory::load($filePath); + + // Importiere in der richtigen Reihenfolge (Abhängigkeiten beachten) + $this->importZones($spreadsheet, $io); + $this->entityManager->flush(); // Flush nach Zones + + $this->importLocations($spreadsheet, $io); + $this->entityManager->flush(); // Flush nach Locations + + $this->importShippingCompanies($spreadsheet, $io); + $this->entityManager->flush(); // Flush nach ShippingCompanies + + $this->importVessels($spreadsheet, $io); + $this->entityManager->flush(); // Flush nach Vessels + + $this->importTrips($spreadsheet, $io); + $this->entityManager->flush(); // Finaler Flush + + // Commit Transaktion bei Erfolg + $this->entityManager->commit(); + + $io->success('Excel import completed successfully!'); + return Command::SUCCESS; + + } catch (\Exception $e) { + // Rollback bei Fehler + $this->entityManager->rollback(); + + $io->error('Import failed: ' . $e->getMessage()); + return Command::FAILURE; + } + } + + private function importZones($spreadsheet, SymfonyStyle $io): void + { + $io->section('Importing Zones'); + + $sheet = $spreadsheet->getSheetByName('zones'); + if (!$sheet) { + $io->warning('Sheet "zones" not found'); + return; + } + + $rows = $sheet->toArray(); + $headers = array_shift($rows); + + $count = 0; + foreach ($rows as $row) { + $data = array_combine($headers, $row); + + // Prüfe, ob Zone bereits existiert + $existingZone = $this->entityManager->getRepository(Zone::class) + ->findOneBy(['name' => $data['name']]); + + if (!$existingZone) { + $zone = new Zone($data['name']); + + $this->entityManager->persist($zone); + $count++; + } + } + + $io->success("Imported $count zones"); + } + + private function importLocations($spreadsheet, SymfonyStyle $io): void + { + $io->section('Importing Locations'); + + $sheet = $spreadsheet->getSheetByName('locations'); + if (!$sheet) { + $io->warning('Sheet "locations" not found'); + return; + } + + $rows = $sheet->toArray(); + $headers = array_shift($rows); + + $count = 0; + foreach ($rows as $row) { + $data = array_combine($headers, $row); + + // Prüfe, ob Location bereits existiert + $existingLocation = $this->entityManager->getRepository(Location::class) + ->findOneBy(['name' => $data['name']]); + + if (!$existingLocation) { + // Finde die zugehörige Zone + $zone = $this->entityManager->getRepository(Zone::class) + ->findOneBy(['name' => $data['zone']]); + + if ($zone) { + // Generiere einen Code aus dem Namen (oder verwenden Sie Ihre eigene Logik) + $code = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $data['name']), 0, 3)); + + $location = new Location($zone, $data['name'], $code); + + $this->entityManager->persist($location); + $count++; + } else { + $io->warning("Zone '{$data['zone']}' not found for location '{$data['name']}'"); + } + } + } + + $io->success("Imported $count locations"); + } + + private function importShippingCompanies($spreadsheet, SymfonyStyle $io): void + { + $io->section('Importing Shipping Companies'); + + $sheet = $spreadsheet->getSheetByName('shipping_companies'); + if (!$sheet) { + $io->warning('Sheet "shipping_companies" not found'); + return; + } + + $rows = $sheet->toArray(); + $headers = array_shift($rows); + + $count = 0; + foreach ($rows as $row) { + $data = array_combine($headers, $row); + + // Prüfe, ob ShippingCompany bereits existiert + $existingCompany = $this->entityManager->getRepository(ShippingCompany::class) + ->findOneBy(['code' => $data['code']]); + + if (!$existingCompany) { + $company = new ShippingCompany($data['name'], $data['code']); + + $this->entityManager->persist($company); + $count++; + } + } + + $io->success("Imported $count shipping companies"); + } + + private function importVessels($spreadsheet, SymfonyStyle $io): void + { + $io->section('Importing Vessels'); + + $sheet = $spreadsheet->getSheetByName('vessel'); + if (!$sheet) { + $io->warning('Sheet "vessel" not found'); + return; + } + + $rows = $sheet->toArray(); + $headers = array_shift($rows); + + $count = 0; + foreach ($rows as $row) { + $data = array_combine($headers, $row); + + // Prüfe, ob Vessel bereits existiert + $existingVessel = $this->entityManager->getRepository(Vessel::class) + ->findOneBy(['code' => $data['code']]); + + if (!$existingVessel) { + // Finde die zugehörige Shipping Company + $company = $this->entityManager->getRepository(ShippingCompany::class) + ->findOneBy(['name' => $data['shipping_company']]); + + if ($company) { + $vessel = new Vessel($company, $data['name'], $data['code']); + + // Setze optionale Felder + if (isset($data['length'])) { + $vessel->setLength((float)$data['length']); + } + if (isset($data['breadth'])) { + $vessel->setBreadth((float)$data['breadth']); + } + if (isset($data['draft'])) { + $vessel->setDraft((float)$data['draft']); + } + if (isset($data['gross_tonnage'])) { + $vessel->setGrossTonnage((float)$data['gross_tonnage']); + } + if (isset($data['imo_np'])) { + $vessel->setImoNo($data['imo_np']); + } + if (isset($data['call_sign'])) { + $vessel->setCallSign($data['call_sign']); + } + + $this->entityManager->persist($vessel); + $count++; + } else { + $io->warning("Shipping company '{$data['shipping_company']}' not found for vessel '{$data['name']}'"); + } + } + } + + $io->success("Imported $count vessels"); + } + + private function importTrips($spreadsheet, SymfonyStyle $io): void + { + $io->section('Importing Trips'); + + $sheet = $spreadsheet->getSheetByName('trip'); + if (!$sheet) { + $io->warning('Sheet "trip" not found'); + return; + } + + $rows = $sheet->toArray(); + $headers = array_shift($rows); + + $count = 0; + foreach ($rows as $row) { + $data = array_combine($headers, $row); + + // Finde das zugehörige Vessel + $vessel = $this->entityManager->getRepository(Vessel::class) + ->findOneBy(['name' => $data['vessel']]); + + if (!$vessel) { + $io->warning("Vessel '{$data['vessel']}' not found for trip ID {$data['id']}"); + continue; + } + + // Finde die Start-Location + $startLocation = $this->entityManager->getRepository(Location::class) + ->findOneBy(['name' => $data['start_location']]); + + if (!$startLocation) { + $io->warning("Start location '{$data['start_location']}' not found for trip ID {$data['id']}"); + continue; + } + + // Finde die End-Location + $endLocation = $this->entityManager->getRepository(Location::class) + ->findOneBy(['name' => $data['end_location']]); + + if (!$endLocation) { + $io->warning("End location '{$data['end_location']}' not found for trip ID {$data['id']}"); + continue; + } + + // Konvertiere Excel-Dates und kombiniere mit Zeit + if (is_numeric($data['start_date'])) { + $startDate = Date::excelToDateTimeObject($data['start_date']); + } else { + $startDate = new \DateTime($data['start_date']); + } + + if (is_numeric($data['start_time'])) { + $startTime = Date::excelToDateTimeObject($data['start_time']); + } else { + $startTime = new \DateTime($data['start_time']); + } + + // Kombiniere Datum und Zeit für Start + $startDate->setTime( + (int)$startTime->format('H'), + (int)$startTime->format('i'), + (int)$startTime->format('s') + ); + $startDateImmutable = DateTimeImmutable::createFromMutable($startDate); + + // Konvertiere Excel-Dates und kombiniere mit Zeit für Ende + if (is_numeric($data['end_date'])) { + $endDate = Date::excelToDateTimeObject($data['end_date']); + } else { + $endDate = new \DateTime($data['end_date']); + } + + if (is_numeric($data['end_time'])) { + $endTime = Date::excelToDateTimeObject($data['end_time']); + } else { + $endTime = new \DateTime($data['end_time']); + } + + // Kombiniere Datum und Zeit für Ende + $endDate->setTime( + (int)$endTime->format('H'), + (int)$endTime->format('i'), + (int)$endTime->format('s') + ); + $endDateImmutable = DateTimeImmutable::createFromMutable($endDate); + + // Prüfe, ob Trip bereits existiert (basierend auf Vessel und Datum) + $existingTrip = $this->entityManager->getRepository(Trip::class) + ->findOneBy([ + 'vessel' => $vessel, + 'startDate' => $startDateImmutable, + 'startLocation' => $startLocation + ]); + + if (!$existingTrip) { + $trip = new Trip($vessel, $startLocation, $endLocation, $startDateImmutable, $endDateImmutable); + $trip->setCompleted(); + $this->entityManager->persist($trip); + $count++; + } + } + + $io->success("Imported $count trips"); + } +} \ No newline at end of file