diff --git a/.gitignore b/.gitignore
index b63da45..5f90047 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ build/
!**/src/test/**/build/
### IntelliJ IDEA ###
+./.idea
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..0194542
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+features
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..d7be335
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..4b2a88c
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..c106d17
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,66 @@
+plugins {
+ id 'java'
+ id 'application'
+}
+
+group = 'org.lab'
+version = '1.0-SNAPSHOT'
+
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+
+
+ testImplementation platform('org.junit:junit-bom:5.10.0')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+}
+
+application {
+
+ mainClass = 'Main'
+
+ applicationDefaultJvmArgs = [
+ '-Dfile.encoding=UTF-8',
+ '-Dsun.stdout.encoding=UTF-8',
+ '-Dsun.stderr.encoding=UTF-8',
+ '-Dconsole.encoding=UTF-8',
+ ]
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+tasks.withType(JavaCompile).configureEach {
+ options.compilerArgs += ['-Xlint:preview', '--enable-preview']
+}
+
+tasks.withType(JavaExec).configureEach {
+ jvmArgs += '--enable-preview'
+}
+
+tasks.withType(Test).configureEach {
+ jvmArgs += '--enable-preview'
+}
+
+test {
+ useJUnitPlatform()
+}
+
+tasks.withType(JavaExec).configureEach {
+ jvmArgs += [
+ '-Dfile.encoding=UTF-8',
+ '-Dsun.stdout.encoding=UTF-8',
+ '-Dsun.stderr.encoding=UTF-8',
+ '--enable-preview'
+ ]
+
+ // Для Windows консоли
+ systemProperty 'file.encoding', 'UTF-8'
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
deleted file mode 100644
index 79bf52a..0000000
--- a/build.gradle.kts
+++ /dev/null
@@ -1,20 +0,0 @@
-plugins {
- id("java")
-}
-
-group = "org.lab"
-version = "1.0-SNAPSHOT"
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- testImplementation(platform("org.junit:junit-bom:5.10.0"))
- testImplementation("org.junit.jupiter:junit-jupiter")
- testRuntimeOnly("org.junit.platform:junit-platform-launcher")
-}
-
-tasks.test {
- useJUnitPlatform()
-}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle
similarity index 100%
rename from settings.gradle.kts
rename to settings.gradle
diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java
index 22028ef..e4dafb9 100644
--- a/src/main/java/org/lab/Main.java
+++ b/src/main/java/org/lab/Main.java
@@ -1,4 +1,117 @@
+
+
+import org.lab.model.milestone.MilestoneStatus;
+import org.lab.model.project.Project;
+import org.lab.model.report.Report;
+import org.lab.model.ticket.Ticket;
+import org.lab.model.user.User;
+import org.lab.service.UserService;
+
+
+
+import java.time.LocalDate;
+
void main() {
- IO.println("Hello and welcome!");
+
+ UserService userService = new UserService();
+ User manager = userService.registerUser("Manager");
+
+
+
+ Project project1 = new Project(manager, "Valhalla");
+
+
+ User developer1 = userService.registerUser("Developer1");
+ User developer2 = userService.registerUser("Developer2");
+ User developer3 = userService.registerUser("Developer3");
+
+ User qa1 = userService.registerUser("QA1");
+ User qa2 = userService.registerUser("QA2");
+ User qa3 = userService.registerUser("QA3");
+
+ User teamLead = userService.registerUser("TeamLead");
+
+
+
+ project1.attachTeamLead(manager, teamLead);
+
+ project1.attachDeveloper(manager, developer1);
+ project1.attachDeveloper(manager, developer2);
+ project1.attachDeveloper(manager, developer3);
+
+ project1.attachQa(manager, qa1);
+ project1.attachQa(manager, qa2);
+ project1.attachQa(manager, qa3);
+
+
+ project1.attachMilestone(manager, LocalDate.now(), LocalDate.now().plusDays(15));
+ project1.changeMilestoneStatus(manager, MilestoneStatus.ACTIVE);
+
+ Ticket ticket1 = project1.addTicket(manager, "Ticket1");
+ Ticket ticket2 = project1.addTicket(manager, "Ticket2");
+ Ticket ticket3 = project1.addTicket(teamLead, "Ticket3");
+
+ System.out.println(project1.getStats());
+
+
+ ticket1.addDeveloper(manager, developer1);
+ ticket2.addDeveloper(manager, developer2);
+ ticket3.addDeveloper(teamLead, developer3);
+
+ ticket1.activeTicket(developer1);
+ ticket2.activeTicket(developer2);
+ ticket3.activeTicket(developer3);
+
+ ticket1.finishTicket(developer1);
+ ticket2.finishTicket(developer2);
+ ticket3.finishTicket(developer3);
+
+
+
+ Report report1 = project1.addReport(qa1, developer1, "bug-report1");
+ Report report2 = project1.addReport(qa2, developer2, "bug-report2");
+ Report report3 = project1.addReport(qa3, developer3, "bug-report3");
+
+ report1.fixedReport(developer1);
+ report2.fixedReport(developer2);
+ report3.fixedReport(developer3);
+
+ report1.checkReport(qa1);
+ report2.checkReport(qa2);
+ report3.checkReport(qa3);
+
+ report1.closeReport(qa1);
+ report2.closeReport(qa2);
+ report3.closeReport(qa3);
+
+
+
+ manager.viewAllProjects();
+ teamLead.viewAllProjects();
+ developer1.viewAllProjects();
+ developer2.viewAllProjects();
+ developer3.viewAllProjects();
+ qa1.viewAllProjects();
+ qa2.viewAllProjects();
+ qa3.viewAllProjects();
+
+ manager.viewAllTasks();
+ teamLead.viewAllTasks();
+ developer1.viewAllTasks();
+ developer2.viewAllTasks();
+ developer3.viewAllTasks();
+ qa1.viewAllTasks();
+ qa2.viewAllTasks();
+ qa3.viewAllTasks();
+
+ developer1.viewAllReport();
+ developer2.viewAllReport();
+ developer3.viewAllReport();
+
+
+
+ project1.changeMilestoneStatus(manager, MilestoneStatus.CLOSE);
+ System.out.println(project1.getMilestones());
+
}
diff --git a/src/main/java/org/lab/model/milestone/Milestone.java b/src/main/java/org/lab/model/milestone/Milestone.java
new file mode 100644
index 0000000..667fefe
--- /dev/null
+++ b/src/main/java/org/lab/model/milestone/Milestone.java
@@ -0,0 +1,57 @@
+package org.lab.model.milestone;
+
+import org.lab.model.ticket.Ticket;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class Milestone {
+
+ private String milestoneId;
+
+ private String projectId;
+
+ private LocalDate startDate;
+
+ private LocalDate endDate;
+
+ private MilestoneStatus status;
+
+ private List tickets = new ArrayList<>();
+
+ public Milestone(String projectId, LocalDate startDate, LocalDate endDate) {
+ milestoneId = UUID.randomUUID().toString();
+ this.projectId = projectId;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ status = MilestoneStatus.OPEN;
+ }
+
+ public MilestoneStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(MilestoneStatus status) {
+ this.status = status;
+ }
+
+ public String getMilestoneId() {
+ return milestoneId;
+ }
+
+ public List getTickets() {
+ return tickets;
+ }
+
+ @Override
+ public String toString() {
+ return "Milestone{" +
+ "startDate=" + startDate +
+ ", milestoneId='" + milestoneId + '\'' +
+ ", endDate=" + endDate +
+ ", status=" + status +
+ '}';
+ }
+}
diff --git a/src/main/java/org/lab/model/milestone/MilestoneStatus.java b/src/main/java/org/lab/model/milestone/MilestoneStatus.java
new file mode 100644
index 0000000..cb1cc72
--- /dev/null
+++ b/src/main/java/org/lab/model/milestone/MilestoneStatus.java
@@ -0,0 +1,7 @@
+package org.lab.model.milestone;
+
+public enum MilestoneStatus {
+
+ OPEN, ACTIVE, CLOSE
+
+}
diff --git a/src/main/java/org/lab/model/project/Project.java b/src/main/java/org/lab/model/project/Project.java
new file mode 100644
index 0000000..35f444e
--- /dev/null
+++ b/src/main/java/org/lab/model/project/Project.java
@@ -0,0 +1,284 @@
+package org.lab.model.project;
+
+import org.lab.model.milestone.Milestone;
+import org.lab.model.milestone.MilestoneStatus;
+import org.lab.model.report.Report;
+import org.lab.model.role.Developer;
+import org.lab.model.role.Manager;
+import org.lab.model.role.QA;
+import org.lab.model.role.TeamLead;
+import org.lab.model.ticket.Ticket;
+import org.lab.model.ticket.TicketStatus;
+import org.lab.model.user.User;
+
+import java.time.LocalDate;
+import java.util.*;
+
+public class Project {
+
+ private String projectId;
+
+ private User manager;
+
+ private User teamLead;
+
+ private Set developers = new HashSet<>();
+
+ private String description;
+
+ private Set qa = new HashSet<>();
+
+ private LinkedList milestones = new LinkedList<>();
+
+ private List reports = new ArrayList<>();
+
+
+ private Milestone currentMilestone;
+
+ /**
+ * Создание проекта пользователем.
+ *
+ * @param user пользователь, от имени которого создаётся проект (он будет менеджером)
+ */
+ public Project(User user, String description) {
+
+ projectId = UUID.randomUUID().toString();
+ manager = user;
+ this.description = description;
+ user.addProject(this, new Manager());
+
+ }
+
+ /**
+ * Добавление тимлида к проекту.
+ */
+ public void attachTeamLead(User manager, User teamLead) {
+
+ if (!manager.equals(this.manager)) {
+
+ String errorMessage = StringTemplate.STR."""
+ User with name: \{manager.getFullName()}
+ and id: \{manager.getId()}
+ is not a manager of project with id: \{projectId}.
+ Insufficient permissions to perform this action.
+ """;
+ System.out.println(errorMessage);
+ return;
+ }
+
+ this.teamLead = teamLead;
+ teamLead.addProject(this, new TeamLead());
+
+ }
+
+ /**
+ * Добавление разработчика к проекту.
+ */
+ public void attachDeveloper(User manager, User developer) {
+
+ if (!manager.equals(this.manager)) {
+ String errorMessage = StringTemplate.STR."""
+ User with name: \{manager.getFullName()}
+ and id: \{manager.getId()}
+ is not a manager of project with id: \{projectId}.
+ Insufficient permissions to perform this action.
+ """;
+ System.out.println(errorMessage);
+ return;
+ }
+
+ this.developers.add(developer);
+ developer.addProject(this, new Developer());
+
+ }
+
+ /**
+ * Добавление тестировщика к проекту.
+ */
+ public void attachQa(User manager, User qa) {
+
+ if (!manager.equals(this.manager)) {
+ String errorMessage = StringTemplate.STR."""
+ User with name: \{manager.getFullName()}
+ and id: \{manager.getId()}
+ is not a manager of project with id: \{projectId}.
+ Insufficient permissions to perform this action.
+ """;
+ System.out.println(errorMessage);
+ return;
+ }
+
+ this.qa.add(qa);
+ qa.addProject(this, new QA());
+
+ }
+
+ /**
+ * Создать новый Milestone.
+ *
+ * @param start начало milestone.
+ * @param finish окончание milestone.
+ */
+ public void attachMilestone(User manager, LocalDate start, LocalDate finish) {
+
+ if (!manager.equals(this.manager)) {
+ String errorMessage = StringTemplate.STR."""
+ User with name: \{manager.getFullName()}
+ and id: \{manager.getId()}
+ is not a manager of project with id: \{projectId}.
+ Insufficient permissions to perform this action.
+ """;
+ System.out.println(errorMessage);
+ return;
+ }
+
+ if (Objects.nonNull(currentMilestone)) {
+ System.out.println("The current milestone has not yet ended, so you cannot create a new one.");
+ return;
+ }
+
+ Milestone milestone = new Milestone(projectId, start, finish);
+ currentMilestone = milestone;
+ milestones.push(milestone);
+
+ }
+
+ /**
+ * Изменить статус текущего milestone
+ */
+ public void changeMilestoneStatus(User manager, MilestoneStatus newStatus) {
+
+ if (!manager.equals(this.manager)) {
+ String errorMessage = StringTemplate.STR."""
+ User with name: \{manager.getFullName()}
+ and id: \{manager.getId()}
+ is not a manager of project with id: \{projectId}.
+ Insufficient permissions to perform this action.
+ """;
+ System.out.println(errorMessage);
+ return;
+ }
+
+ if (Objects.isNull(currentMilestone)) {
+ System.out.println("First, create the current milestone");
+ return;
+ }
+
+
+ if (newStatus == MilestoneStatus.CLOSE) {
+
+ for (Ticket ticket : currentMilestone.getTickets()) {
+
+ if (ticket.getStatus() != TicketStatus.COMPLETED) {
+
+ System.out.println("You cannot close the milestone because not all tickets have been completed.");
+ return;
+ }
+
+ }
+
+ currentMilestone.setStatus(newStatus);
+ currentMilestone = null;
+ return;
+
+ }
+
+
+ currentMilestone.setStatus(newStatus);
+
+ }
+
+ /**
+ * Создание нового тикета (прикрепляется к текущему milestone)
+ */
+ public Ticket addTicket(User user, String description) {
+
+ if (!user.equals(this.manager) && !(Objects.nonNull(teamLead) && teamLead.equals(user))) {
+ String errorMessage = StringTemplate.STR."""
+ User with name: \{manager.getFullName()}
+ and id: \{manager.getId()}
+ is not a manager of project with id: \{projectId}.
+ Insufficient permissions to perform this action.
+ """;
+ System.out.println(errorMessage);
+ return null;
+ }
+
+ if (Objects.isNull(currentMilestone)) {
+ System.out.println("First, create the current milestone");
+ return null;
+ }
+
+ Ticket ticket = new Ticket(this, currentMilestone, description, user);
+
+ currentMilestone.getTickets().add(ticket);
+
+ return ticket;
+
+ }
+
+ /**
+ * Здесь разработчик или тестировщик может создать bug-report
+ */
+ public Report addReport(User createdUser, User fixedUser, String description) {
+
+ Report report = new Report(description, createdUser, fixedUser, this);
+ reports.add(report);
+
+ return report;
+
+ }
+
+ public ProjectStatus getStats() {
+
+ int totalTickets = milestones.stream()
+ .mapToInt(m -> m.getTickets().size())
+ .sum();
+
+ int completedTickets = milestones.stream()
+ .flatMap(m -> m.getTickets().stream())
+ .filter(t -> t.getStatus() == TicketStatus.COMPLETED)
+ .toList()
+ .size();
+
+ return new ProjectStatus(
+ description,
+ totalTickets,
+ completedTickets,
+ totalTickets - completedTickets,
+ developers.size(),
+ qa.size()
+ );
+ }
+
+
+ public Set getDevelopers() {
+ return developers;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public LinkedList getMilestones() {
+ return milestones;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Project project = (Project) o;
+ return Objects.equals(projectId, project.projectId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(projectId);
+ }
+
+ public List getReports() {
+ return reports;
+ }
+
+}
diff --git a/src/main/java/org/lab/model/project/ProjectStatus.java b/src/main/java/org/lab/model/project/ProjectStatus.java
new file mode 100644
index 0000000..80084c2
--- /dev/null
+++ b/src/main/java/org/lab/model/project/ProjectStatus.java
@@ -0,0 +1,34 @@
+package org.lab.model.project;
+
+public record ProjectStatus(
+ String projectName,
+ int totalTickets,
+ int completedTickets,
+ int openTickets,
+ int totalDevelopers,
+ int totalQA
+) {
+
+ public double completionPercentage() {
+ return totalTickets > 0 ? (completedTickets * 100.0) / totalTickets : 0.0;
+ }
+
+ @Override
+ public String toString() {
+ return STR."""
+
+ PROJECT STATUS REPORT
+ Project: \{projectName}
+
+ TICKETS STATISTICS:
+ Total tickets: \{totalTickets}
+ Completed: \{completedTickets}
+ Open: \{openTickets}
+ Completion rate: \{String.format("%.1f", completionPercentage())}%
+ TEAM COMPOSITION:
+ Developers: \{totalDevelopers}
+ QA Engineers: \{totalQA}
+ """;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/lab/model/report/Report.java b/src/main/java/org/lab/model/report/Report.java
new file mode 100644
index 0000000..5274ea5
--- /dev/null
+++ b/src/main/java/org/lab/model/report/Report.java
@@ -0,0 +1,137 @@
+package org.lab.model.report;
+
+import org.lab.model.project.Project;
+import org.lab.model.user.User;
+
+import java.util.Objects;
+import java.util.UUID;
+
+public class Report {
+
+ private String reportId;
+
+ private String description;
+
+ /**
+ * Пользователь, кто создал сообщение об ошибки
+ */
+ private User createdUser;
+
+ /**
+ * Пользователь, кто исправил сообщение об ошибки
+ */
+ private User fixedUser;
+
+ private Project project;
+
+ private ReportStatus status;
+
+ public Report(String description, User createdUser, User fixedUser, Project project) {
+ reportId = UUID.randomUUID().toString();
+ this.description = description;
+ this.createdUser = createdUser;
+ this.project = project;
+ this.fixedUser = fixedUser;
+ status = ReportStatus.NEW;
+ }
+
+
+ /**
+ * Разработчик устраняет сообщение об ошибке.
+ */
+ public void fixedReport(User developer) {
+
+ if (!project.getDevelopers().contains(developer)) {
+
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+
+ }
+
+ if (!fixedUser.equals(developer)) {
+
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+
+ }
+
+ status = ReportStatus.FIXED;
+
+ }
+
+ /**
+ * Тестировщик проверяет как исправил bug-report разработчик.
+ */
+ public void checkReport(User user) {
+
+ if (!user.equals(createdUser)) {
+
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+ }
+
+ status = ReportStatus.TESTED;
+
+ }
+
+ /**
+ * Тестировщик закрывает bug-report
+ */
+ public void closeReport(User user) {
+
+ if (!user.equals(createdUser)) {
+
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+ }
+
+ status = ReportStatus.CLOSED;
+
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Report report = (Report) o;
+ return Objects.equals(reportId, report.reportId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(reportId);
+ }
+
+ public User getCreatedUser() {
+ return createdUser;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public User getFixedUser() {
+ return fixedUser;
+ }
+
+ @Override
+ public String toString() {
+ return STR."""
+ Report [
+ ID: \{reportId}
+ Description: \{description}
+ Status: \{status}
+ Created by: \{createdUser.getFullName()}
+ Assigned to: \{fixedUser.getFullName()}
+ Project: \{project.getDescription()}
+ ]
+ """;
+ }
+
+
+}
diff --git a/src/main/java/org/lab/model/report/ReportStatus.java b/src/main/java/org/lab/model/report/ReportStatus.java
new file mode 100644
index 0000000..1eaafd9
--- /dev/null
+++ b/src/main/java/org/lab/model/report/ReportStatus.java
@@ -0,0 +1,8 @@
+package org.lab.model.report;
+
+public enum ReportStatus {
+
+ NEW, FIXED, TESTED, CLOSED
+
+
+}
diff --git a/src/main/java/org/lab/model/role/Developer.java b/src/main/java/org/lab/model/role/Developer.java
new file mode 100644
index 0000000..4baadc7
--- /dev/null
+++ b/src/main/java/org/lab/model/role/Developer.java
@@ -0,0 +1,10 @@
+package org.lab.model.role;
+
+public final class Developer implements Role {
+
+ @Override
+ public String getRoleName() {
+ return "Developer";
+ }
+
+}
diff --git a/src/main/java/org/lab/model/role/Manager.java b/src/main/java/org/lab/model/role/Manager.java
new file mode 100644
index 0000000..f028c5a
--- /dev/null
+++ b/src/main/java/org/lab/model/role/Manager.java
@@ -0,0 +1,10 @@
+package org.lab.model.role;
+
+public final class Manager implements Role {
+
+ @Override
+ public String getRoleName() {
+ return "Manager";
+ }
+
+}
diff --git a/src/main/java/org/lab/model/role/QA.java b/src/main/java/org/lab/model/role/QA.java
new file mode 100644
index 0000000..5a74f06
--- /dev/null
+++ b/src/main/java/org/lab/model/role/QA.java
@@ -0,0 +1,10 @@
+package org.lab.model.role;
+
+public final class QA implements Role {
+
+ @Override
+ public String getRoleName() {
+ return "QA";
+ }
+
+}
diff --git a/src/main/java/org/lab/model/role/Role.java b/src/main/java/org/lab/model/role/Role.java
new file mode 100644
index 0000000..47d6c30
--- /dev/null
+++ b/src/main/java/org/lab/model/role/Role.java
@@ -0,0 +1,13 @@
+package org.lab.model.role;
+
+
+
+/**
+ * Роли участников проекта.
+ */
+public sealed interface Role permits Manager, Developer, TeamLead, QA {
+
+ String getRoleName();
+
+}
+
diff --git a/src/main/java/org/lab/model/role/TeamLead.java b/src/main/java/org/lab/model/role/TeamLead.java
new file mode 100644
index 0000000..18a728f
--- /dev/null
+++ b/src/main/java/org/lab/model/role/TeamLead.java
@@ -0,0 +1,10 @@
+package org.lab.model.role;
+
+public final class TeamLead implements Role {
+
+ @Override
+ public String getRoleName() {
+ return "TeamLead";
+ }
+
+}
diff --git a/src/main/java/org/lab/model/ticket/Ticket.java b/src/main/java/org/lab/model/ticket/Ticket.java
new file mode 100644
index 0000000..34ced7f
--- /dev/null
+++ b/src/main/java/org/lab/model/ticket/Ticket.java
@@ -0,0 +1,144 @@
+package org.lab.model.ticket;
+
+import org.lab.model.milestone.Milestone;
+import org.lab.model.project.Project;
+import org.lab.model.user.User;
+
+import java.util.*;
+
+public class Ticket {
+
+ private String ticketId;
+
+ private Project project;
+
+ private Milestone milestone;
+
+ private String description;
+
+ private Set developers = new HashSet<>();
+
+ private int count = 0;
+
+ private TicketStatus status;
+
+ /**
+ * TeamLead или менеджер проекта, кто создал тикет.
+ */
+ private User createdUser;
+
+ public Ticket(Project project, Milestone milestone, String description, User createdUser) {
+ ticketId = UUID.randomUUID().toString();
+ this.project = project;
+ this.milestone = milestone;
+ this.description = description;
+ this.createdUser = createdUser;
+ status = TicketStatus.NEW;
+ }
+
+ /**
+ * Посмотреть статус тикета
+ */
+ public TicketStatus getStatus() {
+ return status;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Привязка разработчика к тикету.
+ */
+ public void addDeveloper(User createdUser, User developer) {
+
+ if (!createdUser.equals(this.createdUser)) {
+
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+
+ }
+
+
+ if (!project.getDevelopers().contains(developer)) {
+
+ String errorMessage = "The developer is not part of the project they are attached to this ticket.";
+ System.out.println(errorMessage);
+ return;
+ }
+
+
+ developers.add(developer);
+ status = TicketStatus.ACCEPTED;
+ count++;
+ }
+
+ /**
+ * Разработчик выполняет задачу в тикете.
+ */
+ public void activeTicket(User developer) {
+
+ if (!developers.contains(developer)) {
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+ }
+
+ if (status == TicketStatus.COMPLETED) {
+
+ String errorMessage = "The task in the ticket has already been completed.";
+ System.out.println(errorMessage);
+ return;
+
+ }
+
+
+ status = TicketStatus.ACTIVE;
+
+
+ }
+
+ /**
+ * Разработчик выполнил задачу в тикете.
+ */
+ public void finishTicket(User developer) {
+
+ if (!developers.contains(developer)) {
+ String errorMessage = "Insufficient permissions to perform this action.";
+ System.out.println(errorMessage);
+ return;
+ }
+
+ count--;
+ if (count == 0) {
+ status = TicketStatus.COMPLETED;
+ }
+
+ }
+
+ public String getStatusDescription() {
+ return switch(status) {
+ case NEW -> "New task";
+ case ACCEPTED -> "Accepted for work";
+ case ACTIVE -> "In progress";
+ case COMPLETED -> "Completed";
+ };
+ }
+
+ public Set getDevelopers() {
+ return developers;
+ }
+
+ @Override
+ public String toString() {
+ return StringTemplate.STR."""
+ Ticket: \{description}
+ Status: \{getStatusDescription()}
+ Developers: \{developers.size()}
+ Created by: \{createdUser.getFullName()}
+ """;
+ }
+
+
+}
diff --git a/src/main/java/org/lab/model/ticket/TicketStatus.java b/src/main/java/org/lab/model/ticket/TicketStatus.java
new file mode 100644
index 0000000..fdafac1
--- /dev/null
+++ b/src/main/java/org/lab/model/ticket/TicketStatus.java
@@ -0,0 +1,7 @@
+package org.lab.model.ticket;
+
+public enum TicketStatus {
+
+ NEW, ACCEPTED, ACTIVE, COMPLETED
+
+}
diff --git a/src/main/java/org/lab/model/user/User.java b/src/main/java/org/lab/model/user/User.java
new file mode 100644
index 0000000..84ed437
--- /dev/null
+++ b/src/main/java/org/lab/model/user/User.java
@@ -0,0 +1,144 @@
+package org.lab.model.user;
+
+import org.lab.model.project.Project;
+import org.lab.model.role.Role;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+public class User {
+
+ /**
+ * Уникальные идентификатор пользователя в системе.
+ */
+ private String id;
+
+ /**
+ * Полное имя пользователя.
+ */
+ private String fullName;
+
+ /**
+ *
+ * Список ролей в разных проектах. (допускается в одном проекте иметь несколько ролей)
+ */
+ private Map> roles = new HashMap<>();
+
+
+ public User(String fullName) {
+ this.fullName = fullName;
+ }
+
+
+ /**
+ * Назначить роль в проекте.
+ */
+ public void addProject(Project project, Role role) {
+
+ Set currentRoles = roles.computeIfAbsent(project, k -> new HashSet<>());
+ currentRoles.add(role);
+
+ }
+
+ /**
+ * Просмотреть все проекты
+ */
+ public void viewAllProjects() {
+
+ roles.forEach((project, roleSet) -> {
+ String rolesStr = roleSet.stream()
+ .map(Role::getRoleName)
+ .collect(Collectors.joining(", "));
+
+ System.out.println(STR."""
+ Project: \{project.getDescription()}
+ Roles: \{rolesStr}
+ """);
+ });
+
+ }
+
+ /**
+ * Просмотреть все задачи
+ */
+ public void viewAllTasks() {
+
+ System.out.println("User: " + fullName);
+
+ roles.forEach((project, _) -> {
+ System.out.println("Project: " + project.getDescription());
+
+
+ project.getMilestones().forEach(milestone -> {
+ System.out.println("Milestone " + milestone.getMilestoneId());
+ System.out.println("Tasks:");
+
+ milestone.getTickets().stream()
+ .filter(ticket -> ticket.getDevelopers().contains(this))
+ .forEach(System.out::println);
+
+ System.out.println("--------------");
+ });
+
+
+ System.out.println("Reports");
+ project.getReports().stream()
+ .filter(report -> report.getCreatedUser().equals(this))
+ .forEach(System.out::println);
+
+ System.out.println("--------------");
+ });
+
+
+ }
+
+ public void viewAllReport() {
+
+ System.out.println("User: " + fullName);
+
+ roles.forEach((project, _) -> {
+ System.out.println("Project: " + project.getDescription());
+
+ System.out.println("Reports");
+ project.getReports().stream()
+ .filter(report -> report.getFixedUser().equals(this))
+ .map(report -> "Description: " + report.getDescription())
+ .forEach(System.out::println);
+
+ System.out.println("--------------");
+ });
+
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ User user = (User) o;
+ return Objects.equals(id, user.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+
+}
+
+
+
+
+
diff --git a/src/main/java/org/lab/service/UserService.java b/src/main/java/org/lab/service/UserService.java
new file mode 100644
index 0000000..39ef954
--- /dev/null
+++ b/src/main/java/org/lab/service/UserService.java
@@ -0,0 +1,30 @@
+package org.lab.service;
+
+import org.lab.model.user.User;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class UserService {
+
+ private Map users;
+
+ public UserService() {
+ users = new HashMap<>();
+ }
+
+ public User registerUser(String fullName) {
+
+ String id = UUID.randomUUID().toString();
+ User user = new User(fullName);
+ user.setId(id);
+
+ users.put(id, user);
+
+ return user;
+
+ }
+
+
+}