From 809acb47bf2ddeeb97e45d78958fb645bded37e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kopeck=C3=BD?= Date: Sun, 8 Mar 2020 12:34:17 +0100 Subject: [PATCH] Import groups from Kimai v1 (#1532) --- src/Command/KimaiImporterCommand.php | 214 +++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) mode change 100644 => 100755 src/Command/KimaiImporterCommand.php diff --git a/src/Command/KimaiImporterCommand.php b/src/Command/KimaiImporterCommand.php old mode 100644 new mode 100755 index 2ab9821f..cddabeda --- a/src/Command/KimaiImporterCommand.php +++ b/src/Command/KimaiImporterCommand.php @@ -18,6 +18,7 @@ use App\Entity\CustomerMeta; use App\Entity\Project; use App\Entity\ProjectMeta; use App\Entity\ProjectRate; +use App\Entity\Team; use App\Entity\Timesheet; use App\Entity\User; use App\Entity\UserPreference; @@ -100,6 +101,10 @@ final class KimaiImporterCommand extends Command * @var array */ private $activities = []; + /** + * @var Team[] + */ + protected $teams = []; /** * @var bool */ @@ -252,6 +257,38 @@ final class KimaiImporterCommand extends Command return 1; } + try { + $groups = $this->fetchAllFromImport('groups'); + } catch (Exception $ex) { + $io->error('Failed to load groups: ' . $ex->getMessage()); + + return 1; + } + + try { + $groupToCustomer = $this->fetchAllFromImport('groups_customers'); + } catch (Exception $ex) { + $io->error('Failed to load groups-customers mappings: ' . $ex->getMessage()); + + return 1; + } + + try { + $groupToProject = $this->fetchAllFromImport('groups_projects'); + } catch (Exception $ex) { + $io->error('Failed to load groups-projects mappings: ' . $ex->getMessage()); + + return 1; + } + + try { + $groupToUser = $this->fetchAllFromImport('groups_users'); + } catch (Exception $ex) { + $io->error('Failed to load groups-users mappings: ' . $ex->getMessage()); + + return 1; + } + $bytesCached = memory_get_usage(true); $io->success('Fetched Kimai v1 data, trying to import now ...'); @@ -298,6 +335,16 @@ final class KimaiImporterCommand extends Command return 1; } + try { + $counter = $this->importGroups($io, $groups, $groupToCustomer, $groupToProject, $groupToUser); + $allImports += $counter; + $io->success('Imported groups/teams: ' . $counter); + } catch (Exception $ex) { + $io->error('Failed to import groups/teams: ' . $ex->getMessage() . PHP_EOL . $ex->getTraceAsString()); + + return 1; + } + try { $counter = $this->importTimesheetRecords($io, $records, $fixedRates, $rates); $allImports += $counter; @@ -1216,4 +1263,171 @@ final class KimaiImporterCommand extends Command return $counter; } + + /** Imports Kimai v1 groups as teams and connects teams with users, customers and projects + * + * -- are currently unsupported fields that can't be mapped + * + * $groups + * ["groupID"] => int(10) "1" + * ["name"] => varchar(160) "a group name" + * -- ["trash"] => tinyint(1) 1/0 + * + * $groups_customers + * ["groupID"] => int(10) "1" + * ["customerID"] => int(10) "1" + * + * $groups_projects + * ["groupID"] => int(10) "1" + * ["projectID"] => int(10) "1" + * + * $groups_users + * ["groupID"] => int(10) "1" + * ["customerID"] => int(10) "1" + * -- ["membershipRoleID"] => int(10) "1" + * + * @param SymfonyStyle $io + * @param array $groups + * @param array $groupToCustomer + * @param array $groupToProject + * @param array $groupToUser + * + * @return int + * @throws Exception + */ + protected function importGroups(SymfonyStyle $io, array $groups, array $groupToCustomer, array $groupToProject, array $groupToUser) + { + $counter = 0; + $skippedTrashed = 0; + $skippedEmpty = 0; + $failed = 0; + + $newTeams = []; + // create teams just with names of groups + foreach ($groups as $group) { + if ($group['trash'] === 1) { + $io->warning(sprintf('Didn\'t import team: "%s" because it is trashed.', $group['name'])); + $skippedTrashed++; + continue; + } + + $team = new Team(); + $team->setName($group['name']); + + $newTeams[$group['groupID']] = $team; + } + + // connect groups with users + foreach ($groupToUser as $row) { + if (!isset($newTeams[$row['groupID']])) { + continue; + } + $team = $newTeams[$row['groupID']]; + + if (!isset($this->users[$row['userID']])) { + continue; + } + $user = $this->users[$row['userID']]; + + $team->addUser($user); + + // first user in the team will become team lead + if ($team->getTeamLead() == null) { + $team->setTeamLead($user); + } + + // any other user with admin role in the team will become team lead + // should be the last added admin of the source group + if ($row['membershipRoleID'] === 1) { + $team->setTeamLead($user); + } + } + + // if team has no users it will not be persisted + foreach ($newTeams as $oldId => $team) { + if ($team->getTeamLead() === null) { + $io->warning(sprintf('Didn\'t import team: %s because it has no users.', $team->getName())); + ++$skippedEmpty; + unset($newTeams[$oldId]); + } + } + + // connect groups with customers + foreach ($groupToCustomer as $row) { + if (!isset($newTeams[$row['groupID']])) { + continue; + } + $team = $newTeams[$row['groupID']]; + + if (!isset($this->customers[$row['customerID']])) { + continue; + } + $customer = $this->customers[$row['customerID']]; + + $team->addCustomer($customer); + } + + // connect groups with projects + foreach ($groupToProject as $row) { + if (!isset($newTeams[$row['groupID']])) { + continue; + } + $team = $newTeams[$row['groupID']]; + + if (!isset($this->projects[$row['projectID']])) { + continue; + } + $project = $this->projects[$row['projectID']]; + + $team->addProject($project); + + if ($project->getCustomer() !== null) { + $team->addCustomer($project->getCustomer()); + } + } + + $entityManager = $this->getDoctrine()->getManager(); + + // validate and persist each team + foreach ($newTeams as $oldId => $team) { + if (!$this->validateImport($io, $team)) { + throw new Exception('Failed to validate team: ' . $team->getName()); + } + + try { + $entityManager->persist($team); + if ($this->debug) { + $io->success( + sprintf( + 'Created team: %s with %s users, %s projects and %s customers.', + $team->getName(), + count($team->getUsers()), + count($team->getProjects()), + count($team->getCustomers()) + ) + ); + } + ++$counter; + $this->teams[$oldId] = $team; + } catch (Exception $ex) { + $io->error('Failed to create team: ' . $team->getName()); + $io->error('Reason: ' . $ex->getMessage()); + ++$failed; + } + } + + $entityManager->flush(); + + if ($skippedTrashed > 0) { + $io->warning('Didn\'t import teams because they are trashed: ' . $skippedTrashed); + } + if ($skippedEmpty > 0) { + $io->warning('Didn\'t import teams because they have no users: ' . $skippedEmpty); + } + if ($failed > 0) { + $io->error('Failed importing teams: ' . $failed); + } + + return $counter; + } }