From 337fccfe9834a4d89bfe0ae77cfe108b717e8b7c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 09:49:52 +0900 Subject: [PATCH 01/30] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 572a1800..3ed9c50d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,73 @@ # java-attendance-precourse -스크린샷 2025-11-27 20 49 53 -스크린샷 2025-11-27 20 49 55 -스크린샷 2025-11-27 20 49 56 -스크린샷 2025-11-27 20 49 57 -스크린샷 2025-11-27 20 49 58 -스크린샷 2025-11-27 20 50 05 -스크린샷 2025-11-27 20 50 06 -스크린샷 2025-11-27 20 50 08 -스크린샷 2025-11-27 20 50 09 -스크린샷 2025-11-27 20 50 10 -스크린샷 2025-11-27 20 50 11 -스크린샷 2025-11-27 20 51 49 +## 기능 구현 목록 +> 기능 작동 순서대로 작성 + +1. 파일 입력 및 크루별 출석 기록 저장 + 1. 1일부터 어제까지 순회하면서 기록 저장 + - 주말 및 공휴일은 패스 + - 기록이 없으면 결석으로 처리(시간은 00:00) +2. 오늘 날짜 출력 및 기능 선택 입력 및 검증 + 1. 오늘 날짜 출력 + 2. 기능 선택 입력 및 검증 + - 예외 사항 + - 1,2,3,4,Q가 아닌 경우 + - 잘못된 형식을 입력하였습니다. +3. 출석 확인 + - 예외 사항 + - 등교일이 아닌 경우 + - 12월 %d일 %s요일은 등교일이 아닙니다. + 1. 닉네임 입력 및 검증 + - 예외 사항 + - 등록되지 않은 닉네임인 경우 + - 등록되지 않은 닉네임입니다. + - 이미 출석을 하였는데 다시 출석 확인을 하는 경우 + - 이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요. + 2. 등교 시간 입력 + - 예외 사항 + - 잘못된 형식인 경우 + - 잘못된 형식을 입력하였습니다. + - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 + - 캠퍼스 운영 시간에만 출석이 가능합니다. + 3. 출석 체크 + 4. 결과 출력 + - 날짜, 요일, 시간, 출석체크결과 +4. 출석 수정 + 1. 닉네임 입력 및 검증 + - 예외 사항 + - 등록되지 않은 닉네임인 경우 + - 등록되지 않은 닉네임입니다. + 2. 수정하려는 날짜 입력 + - 예외 사항 + - 잘못된 형식인 경우 + - 잘못된 형식을 입력하였습니다. + - 미래 날짜로 출석을 수정하는 경우 + - 아직 수정할 수 없습니다. + 3. 수정하려는 시간 입력 + - 예외 사항 + - 잘못된 형식인 경우 + - 잘못된 형식을 입력하였습니다. + - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 + - 캠퍼스 운영 시간에만 출석이 가능합니다. + 4. 출석 내용 수정 + 5. 결과 출력 + - 날짜, 요일, 변경전 시간, 변경전 상태, 변경후 시간, 변경후 상태 +5. 크루별 출석 기록 확인 + 1. 닉네임 입력 + - 예외 사항 + - 등록되지 않은 닉네임인 경우 + - 등록되지 않은 닉네임입니다. + 2. 출석 기록 출력 + - 등교일만 출력 + - 날짜, 요일, 시간, 상태 + - 출석, 지각, 결석 횟수 + - 제적 위험자인 경우! -> 어떤 대상자인지 +6. 제적 위험자 확인 + 1. 제적 위험자 출력 + - 이름, 결석 횟수, 지각 횟수, 어떤 대상자인지 + - 정렬 기준 + 1. 결석+지각3회 내림차순 + 2. 결석 내림차순 + 3. 지각 내림차순 + 4. 이름 오름차순 +7. 종료 From 6a03b20110d815a528c6c5576197c7e9a7184eb2 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 11:02:53 +0900 Subject: [PATCH 02/30] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=ED=81=AC=EB=A3=A8=EB=B3=84=20=EC=B6=9C?= =?UTF-8?q?=EC=84=9D=20=EA=B8=B0=EB=A1=9D=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AttendanceController.java | 52 +++++++++++++++++ .../java/attendance/domain/Attendance.java | 45 +++++++++++++++ src/main/java/attendance/domain/Crew.java | 24 ++++++++ src/main/java/attendance/domain/Crews.java | 56 +++++++++++++++++++ .../attendance/service/AttendanceService.java | 17 ++++++ .../java/attendance/util/InputParser.java | 51 +++++++++++++++++ .../java/attendance/util/file/FileReader.java | 32 +++++++++++ 7 files changed, 277 insertions(+) create mode 100644 src/main/java/attendance/controller/AttendanceController.java create mode 100644 src/main/java/attendance/domain/Attendance.java create mode 100644 src/main/java/attendance/domain/Crew.java create mode 100644 src/main/java/attendance/domain/Crews.java create mode 100644 src/main/java/attendance/service/AttendanceService.java create mode 100644 src/main/java/attendance/util/InputParser.java create mode 100644 src/main/java/attendance/util/file/FileReader.java diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java new file mode 100644 index 00000000..20794e5e --- /dev/null +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -0,0 +1,52 @@ +package attendance.controller; + +import attendance.command.MenuCommandRegistry; +import attendance.command.MenuOption; +import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.util.file.FileReader; +import attendance.view.InputView; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AttendanceController { + + private final MenuCommandRegistry registry; + private final AttendanceService service; + + public AttendanceController(MenuCommandRegistry registry, AttendanceService service) { + this.registry = registry; + this.service = service; + } + + public void run() throws IOException { + registerFileInfo(); + + while (true) { + MenuOption option = readOption(); + + if (option.equals(MenuOption.QUIT)) { + return; + } + + registry.execute(option); + } + } + + private void registerFileInfo() throws IOException { + FileReader fileReader = new FileReader("src/main/resources/attendances.csv"); + List readLines = fileReader.readLines(); + + Map> attendanceRecords = InputParser.getAttendanceRecords(readLines); + + service.registerFileInfo(attendanceRecords); + } + + private MenuOption readOption() { + String selection = InputView.readMenuSelection(); + return MenuOption.from(selection); + } +} diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java new file mode 100644 index 00000000..6ca8aba0 --- /dev/null +++ b/src/main/java/attendance/domain/Attendance.java @@ -0,0 +1,45 @@ +package attendance.domain; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +public class Attendance implements Comparable { + + private LocalDateTime dateTime; + + private Attendance(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + + public static Attendance from(LocalDateTime dateTime) { + return new Attendance(dateTime); + } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } + + public LocalTime modifyTime(LocalTime newTime) { + LocalTime oldTime = dateTime.toLocalTime(); + dateTime = LocalDateTime.of(dateTime.toLocalDate(), newTime); + return oldTime; + } + + @Override + public int compareTo(Attendance o) { + if (dateTime.toLocalDate().isBefore(o.dateTime.toLocalDate())){ + return -1; + } + + if (dateTime.toLocalDate().isEqual(o.dateTime.toLocalDate())){ + return 0; + } + + return 1; + } +} diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java new file mode 100644 index 00000000..a6e7515c --- /dev/null +++ b/src/main/java/attendance/domain/Crew.java @@ -0,0 +1,24 @@ +package attendance.domain; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class Crew { + + private final String name; + private final List attendances; + + public Crew(String name) { + this.name = name; + this.attendances = new ArrayList<>(); + } + + public static Crew from(String crewName) { + return new Crew(crewName); + } + + public void addAttendanceRecord(LocalDateTime dateTime) { + attendances.add(Attendance.from(dateTime)); + } +} diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java new file mode 100644 index 00000000..d36eee54 --- /dev/null +++ b/src/main/java/attendance/domain/Crews.java @@ -0,0 +1,56 @@ +package attendance.domain; + +import static java.util.Locale.KOREA; + +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Crews { + + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); + + private List crews; + + public static Crews newInstance() { + return new Crews(); + } + + public void registerRecord(String crewName, List attendanceRecords) { + Map dateTimes = new HashMap<>(); + setRecords(attendanceRecords, dateTimes); + + Crew crew = setCrew(crewName, dateTimes); + crews.add(crew); + } + + private void setRecords(List attendanceRecords, Map dateTimes) { + for (String attendanceRecord : attendanceRecords) { + LocalDateTime localDateTime = LocalDateTime.parse(attendanceRecord, DATETIME_FMT); + LocalDate localDate = localDateTime.toLocalDate(); + + dateTimes.put(localDate, localDateTime); + } + } + + private Crew setCrew(String crewName, Map dateTimes) { + Crew crew = Crew.from(crewName); + LocalDate now = DateTimes.now().toLocalDate(); + for (LocalDate date = LocalDate.of(24,12,1); date.isBefore(now) ; date.plusDays(1)) { + + if (dateTimes.containsKey(date)) { // 해당 날짜 출석 기록이 있으면 + crew.addAttendanceRecord(dateTimes.get(date)); + continue; + } + + crew.addAttendanceRecord(LocalDateTime.of(date, LocalTime.of(22,59))); + } + return crew; + } +} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java new file mode 100644 index 00000000..a8e8e572 --- /dev/null +++ b/src/main/java/attendance/service/AttendanceService.java @@ -0,0 +1,17 @@ +package attendance.service; + +import attendance.domain.Crews; +import java.util.List; +import java.util.Map; + +public class AttendanceService { + + private Crews crews; + + public void registerFileInfo(Map> attendanceRecords) { + crews = Crews.newInstance(); + for (String crewName : attendanceRecords.keySet()) { + crews.registerRecord(crewName, attendanceRecords.get(crewName)); + } + } +} diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java new file mode 100644 index 00000000..d05c166f --- /dev/null +++ b/src/main/java/attendance/util/InputParser.java @@ -0,0 +1,51 @@ +package attendance.util; + +import attendance.command.MenuOption; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class InputParser { + + private static final String DELIMITER = ","; + + private InputParser() { + } + + public static MenuOption parseMenu(String rawInput) { + return MenuOption.from(rawInput.strip()); + } + + public static String parseName(String rawInput) { + return rawInput.strip(); + } + + public static LocalTime parseTime(String rawInput) { + rawInput = rawInput.strip(); + + Validator.validateTimeFormat(rawInput); + + return LocalTime.parse(rawInput); + } + + public static LocalDate parseDay(String rawInput) { + rawInput = rawInput.strip(); + + Validator.validateDayFormat(rawInput); + + return LocalDate.of(24,12, NumberConvertor.convertToNumber(rawInput)); + } + + public static Map> getAttendanceRecords(List readLines) { + readLines.removeFirst(); + Map> attendanceRecords = new HashMap<>(); + for (String readLine : readLines) { + String[] split = readLine.split(DELIMITER); + attendanceRecords.getOrDefault(split[0], new ArrayList<>()).add(split[1]); + } + return attendanceRecords; + } +} diff --git a/src/main/java/attendance/util/file/FileReader.java b/src/main/java/attendance/util/file/FileReader.java new file mode 100644 index 00000000..621174fb --- /dev/null +++ b/src/main/java/attendance/util/file/FileReader.java @@ -0,0 +1,32 @@ +package attendance.util.file; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class FileReader { + + private static final int BUFFER_SIZE = 8192; + + private final java.io.FileReader fr; + + public FileReader(String fileName) throws IOException { + fr = new java.io.FileReader(fileName, UTF_8); + } + + public List readLines() throws IOException { + List contents = new ArrayList<>(); + BufferedReader br = new BufferedReader(fr, BUFFER_SIZE); + + String line; + while ((line = br.readLine()) != null) { + contents.add(line); + } + br.close(); + + return new ArrayList<>(contents); + } +} From 07b4491b57236caccc481a0201d90d3fc80259d4 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 11:05:59 +0900 Subject: [PATCH 03/30] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=ED=81=AC=EB=A3=A8=EB=B3=84=20=EC=B6=9C?= =?UTF-8?q?=EC=84=9D=20=EA=B8=B0=EB=A1=9D=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/domain/Crews.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index d36eee54..0de40d58 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -3,6 +3,7 @@ import static java.util.Locale.KOREA; import camp.nextstep.edu.missionutils.DateTimes; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -44,12 +45,17 @@ private Crew setCrew(String crewName, Map dateTimes) { LocalDate now = DateTimes.now().toLocalDate(); for (LocalDate date = LocalDate.of(24,12,1); date.isBefore(now) ; date.plusDays(1)) { + if (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY || date.isEqual( + LocalDate.of(24,12,25))) { + continue; + } + if (dateTimes.containsKey(date)) { // 해당 날짜 출석 기록이 있으면 crew.addAttendanceRecord(dateTimes.get(date)); continue; } - crew.addAttendanceRecord(LocalDateTime.of(date, LocalTime.of(22,59))); + crew.addAttendanceRecord(LocalDateTime.of(date, LocalTime.of(23,59))); } return crew; } From bcef7c04151e09cff94096198b547dc00fbd2d55 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 11:13:20 +0900 Subject: [PATCH 04/30] =?UTF-8?q?feat:=20=EC=98=A4=EB=8A=98=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/Application.java | 14 ++++++++- src/main/java/attendance/command/Command.java | 5 ++++ .../command/MenuCommandRegistry.java | 30 +++++++++++++++++++ .../java/attendance/command/MenuOption.java | 25 ++++++++++++++++ .../attendance/command/impl/CheckCommand.java | 18 +++++++++++ .../command/impl/DangerQueryCommand.java | 18 +++++++++++ .../command/impl/ModificationCommand.java | 18 +++++++++++ .../command/impl/RecordQueryCommand.java | 18 +++++++++++ .../attendance/constant/ErrorMessage.java | 23 ++++++++++++++ .../controller/AttendanceController.java | 12 ++++---- .../java/attendance/util/NumberConvertor.java | 14 +++++++++ src/main/java/attendance/util/Validator.java | 23 ++++++++++++++ src/main/java/attendance/view/InputView.java | 18 +++++++++++ 13 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 src/main/java/attendance/command/Command.java create mode 100644 src/main/java/attendance/command/MenuCommandRegistry.java create mode 100644 src/main/java/attendance/command/MenuOption.java create mode 100644 src/main/java/attendance/command/impl/CheckCommand.java create mode 100644 src/main/java/attendance/command/impl/DangerQueryCommand.java create mode 100644 src/main/java/attendance/command/impl/ModificationCommand.java create mode 100644 src/main/java/attendance/command/impl/RecordQueryCommand.java create mode 100644 src/main/java/attendance/constant/ErrorMessage.java create mode 100644 src/main/java/attendance/util/NumberConvertor.java create mode 100644 src/main/java/attendance/util/Validator.java create mode 100644 src/main/java/attendance/view/InputView.java diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index e9b866ea..1097af77 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -1,7 +1,19 @@ package attendance; +import attendance.command.MenuCommandRegistry; +import attendance.controller.AttendanceController; +import attendance.service.AttendanceService; +import java.io.IOException; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + AttendanceService service = new AttendanceService(); + MenuCommandRegistry registry = MenuCommandRegistry.from(service); + AttendanceController controller = new AttendanceController(registry, service); + try { + controller.run(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/attendance/command/Command.java b/src/main/java/attendance/command/Command.java new file mode 100644 index 00000000..cee363fe --- /dev/null +++ b/src/main/java/attendance/command/Command.java @@ -0,0 +1,5 @@ +package attendance.command; + +public interface Command { + void execute(); +} diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java new file mode 100644 index 00000000..f13183d3 --- /dev/null +++ b/src/main/java/attendance/command/MenuCommandRegistry.java @@ -0,0 +1,30 @@ +package attendance.command; + +import attendance.command.impl.CheckCommand; +import attendance.command.impl.DangerQueryCommand; +import attendance.command.impl.ModificationCommand; +import attendance.command.impl.RecordQueryCommand; +import attendance.service.AttendanceService; +import java.util.EnumMap; + +public class MenuCommandRegistry { + + private final EnumMap commands; + + private MenuCommandRegistry(EnumMap commands) { + this.commands = commands; + } + + public static MenuCommandRegistry from(AttendanceService service) { + EnumMap map = new EnumMap<>(MenuOption.class); + map.put(MenuOption.A, new CheckCommand(service)); + map.put(MenuOption.B, new ModificationCommand(service)); + map.put(MenuOption.C, new RecordQueryCommand(service)); + map.put(MenuOption.D, new DangerQueryCommand(service)); + return new MenuCommandRegistry(map); + } + + public void execute(MenuOption option) { + commands.get(option).execute(); + } +} diff --git a/src/main/java/attendance/command/MenuOption.java b/src/main/java/attendance/command/MenuOption.java new file mode 100644 index 00000000..6e6a9bd5 --- /dev/null +++ b/src/main/java/attendance/command/MenuOption.java @@ -0,0 +1,25 @@ +package attendance.command; + +import attendance.constant.ErrorMessage; +import java.util.Arrays; + +public enum MenuOption { + A("1"), + B("2"), + C("3"), + D("4"), + QUIT("Q"); + + private final String command; + + MenuOption(String command) { + this.command = command; + } + + public static MenuOption from(String command) { + return Arrays.stream(values()) + .filter(opt -> opt.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } +} diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java new file mode 100644 index 00000000..28298d53 --- /dev/null +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class CheckCommand implements Command { + + private final AttendanceService service; + + public CheckCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/command/impl/DangerQueryCommand.java b/src/main/java/attendance/command/impl/DangerQueryCommand.java new file mode 100644 index 00000000..e0fca9a3 --- /dev/null +++ b/src/main/java/attendance/command/impl/DangerQueryCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class DangerQueryCommand implements Command { + + private final AttendanceService service; + + public DangerQueryCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java new file mode 100644 index 00000000..92aa3f25 --- /dev/null +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class ModificationCommand implements Command { + + private final AttendanceService service; + + public ModificationCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/command/impl/RecordQueryCommand.java b/src/main/java/attendance/command/impl/RecordQueryCommand.java new file mode 100644 index 00000000..d87257c1 --- /dev/null +++ b/src/main/java/attendance/command/impl/RecordQueryCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class RecordQueryCommand implements Command { + + private final AttendanceService service; + + public RecordQueryCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/constant/ErrorMessage.java b/src/main/java/attendance/constant/ErrorMessage.java new file mode 100644 index 00000000..acef1c83 --- /dev/null +++ b/src/main/java/attendance/constant/ErrorMessage.java @@ -0,0 +1,23 @@ +package attendance.constant; + +public enum ErrorMessage { + + FORMAT_ERROR("잘못된 형식입니다."), + NO_ATTENDANCE_DAY_ERROR("%s은 등교일이 아닙니다."), + NO_EXIST_NAME_ERROR("등록되지 않은 닉네임입니다."), + ALREADY_ATTENDANCE_ERROR("이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."), + NO_OPERATION_TIME_ERROR("캠퍼스 운영 시간에만 출석이 가능합니다."), + FUTURE_DAY_ERROR("아직 수정할 수 없습니다."), + ; + + private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage(Object... args) { + return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); + } +} diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java index 20794e5e..2d2f26dc 100644 --- a/src/main/java/attendance/controller/AttendanceController.java +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -6,9 +6,9 @@ import attendance.util.InputParser; import attendance.util.file.FileReader; import attendance.view.InputView; +import camp.nextstep.edu.missionutils.DateTimes; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; +import java.time.LocalDate; import java.util.List; import java.util.Map; @@ -24,9 +24,10 @@ public AttendanceController(MenuCommandRegistry registry, AttendanceService serv public void run() throws IOException { registerFileInfo(); + LocalDate now = DateTimes.now().toLocalDate(); while (true) { - MenuOption option = readOption(); + MenuOption option = readOption(now); if (option.equals(MenuOption.QUIT)) { return; @@ -45,8 +46,7 @@ private void registerFileInfo() throws IOException { service.registerFileInfo(attendanceRecords); } - private MenuOption readOption() { - String selection = InputView.readMenuSelection(); - return MenuOption.from(selection); + private MenuOption readOption(LocalDate now) { + return InputParser.parseMenu(InputView.readMenuSelection(now)); } } diff --git a/src/main/java/attendance/util/NumberConvertor.java b/src/main/java/attendance/util/NumberConvertor.java new file mode 100644 index 00000000..b48fbc20 --- /dev/null +++ b/src/main/java/attendance/util/NumberConvertor.java @@ -0,0 +1,14 @@ +package attendance.util; + +import main.java.lotto.constant.ErrorMessage; + +public final class NumberConvertor { + + public static int convertToNumber(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + } +} diff --git a/src/main/java/attendance/util/Validator.java b/src/main/java/attendance/util/Validator.java new file mode 100644 index 00000000..2592fa9b --- /dev/null +++ b/src/main/java/attendance/util/Validator.java @@ -0,0 +1,23 @@ +package attendance.util; + +import attendance.constant.ErrorMessage; + +public final class Validator { + + private static final String DAY_FORMAT = "^[1-9]|[1-2]\\d|3[0-1]$"; + private static final String TIME_FORMAT = "([01]\\d|2[0-3]):[0-5]\\d"; + + private Validator() {} + + public static void validateTimeFormat(String input) { + if (!input.matches(TIME_FORMAT)) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + } + + public static void validateDayFormat(String input) { + if (!input.matches(DAY_FORMAT)) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + } +} diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java new file mode 100644 index 00000000..270234b4 --- /dev/null +++ b/src/main/java/attendance/view/InputView.java @@ -0,0 +1,18 @@ +package attendance.view; + +import static java.util.Locale.KOREA; + +import camp.nextstep.edu.missionutils.Console; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class InputView { + + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); + + public static String readMenuSelection(LocalDate now) { + System.out.printf("오늘은 %s입니다. 기능을 선택해 주세요.\n", now.format(DATETIME_FMT)); + return Console.readLine(); + } +} From c73fcd9dd1158e14781eb8a45976c79b510d087a Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 12:19:10 +0900 Subject: [PATCH 05/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/command/impl/CheckCommand.java | 32 ++++++++++++++++ src/main/java/attendance/constant/Check.java | 35 +++++++++++++++++ src/main/java/attendance/constant/Danger.java | 31 +++++++++++++++ .../java/attendance/constant/Holiday.java | 37 ++++++++++++++++++ .../attendance/constant/OperationTime.java | 20 ++++++++++ .../java/attendance/constant/Standard.java | 38 +++++++++++++++++++ src/main/java/attendance/domain/Crew.java | 29 +++++++++++++- src/main/java/attendance/domain/Crews.java | 30 +++++++++++---- src/main/java/attendance/dto/CheckResult.java | 11 ++++++ .../attendance/service/AttendanceService.java | 27 +++++++++++++ .../java/attendance/util/InputParser.java | 6 ++- src/main/java/attendance/view/InputView.java | 17 ++++++++- src/main/java/attendance/view/OutputView.java | 32 ++++++++++++++++ 13 files changed, 335 insertions(+), 10 deletions(-) create mode 100644 src/main/java/attendance/constant/Check.java create mode 100644 src/main/java/attendance/constant/Danger.java create mode 100644 src/main/java/attendance/constant/Holiday.java create mode 100644 src/main/java/attendance/constant/OperationTime.java create mode 100644 src/main/java/attendance/constant/Standard.java create mode 100644 src/main/java/attendance/dto/CheckResult.java create mode 100644 src/main/java/attendance/view/OutputView.java diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index 28298d53..2385bc3b 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -1,10 +1,25 @@ package attendance.command.impl; +import static java.util.Locale.KOREA; + import attendance.command.Command; +import attendance.constant.ErrorMessage; +import attendance.constant.Holiday; +import attendance.dto.CheckResult; import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.view.InputView; +import attendance.view.OutputView; +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; public class CheckCommand implements Command { + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); + private final AttendanceService service; public CheckCommand(AttendanceService service) { @@ -13,6 +28,23 @@ public CheckCommand(AttendanceService service) { @Override public void execute() { + LocalDate now = DateTimes.now().toLocalDate(); + if (isHoliday(now)) { + throw new IllegalArgumentException(ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); + } + + String name = InputParser.parseName(InputView.readName()); + service.validateCheckPossible(name, now); + + LocalTime time = InputParser.parseTime(InputView.readTime()); + service.validatePossibleTime(time); + + CheckResult checkResult = service.checkAttendance(name, now, time); + + OutputView.printCheckResult(checkResult); + } + private boolean isHoliday(LocalDate now) { + return !Holiday.from(now).equals(Holiday.NONE); } } diff --git a/src/main/java/attendance/constant/Check.java b/src/main/java/attendance/constant/Check.java new file mode 100644 index 00000000..ecb232fb --- /dev/null +++ b/src/main/java/attendance/constant/Check.java @@ -0,0 +1,35 @@ +package attendance.constant; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; + +public enum Check { + + ABSENCE(30, "결석"), + LATE(5, "지각"), + ATTENDANCE(0, "출석"), + ; + + private final int lateMinutes; + private final String name; + + Check(int lateMinutes, String name) { + this.lateMinutes = lateMinutes; + this.name = name; + } + + public static Check from(LocalDateTime dateTime) { + LocalTime time = dateTime.toLocalTime(); + LocalTime standardTime = Standard.from(dateTime.toLocalDate()).getStartTime(); + + return Arrays.stream(values()) + .filter(check -> time.isAfter(standardTime.plusMinutes(check.lateMinutes))) + .findFirst() + .orElse(ATTENDANCE); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/attendance/constant/Danger.java b/src/main/java/attendance/constant/Danger.java new file mode 100644 index 00000000..7f057195 --- /dev/null +++ b/src/main/java/attendance/constant/Danger.java @@ -0,0 +1,31 @@ +package attendance.constant; + +import java.util.Arrays; + +public enum Danger { + + EXPULSION(6, "제적"), + TALK(3, "면담"), + WARNING(2, "경고"), + NONE(0, ""), + ; + + private final int absenceCount; + private final String name; + + Danger(int absenceCount, String name) { + this.absenceCount = absenceCount; + this.name = name; + } + + public static Danger from(int count) { + return Arrays.stream(values()) + .filter(danger -> danger.absenceCount <= count) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/attendance/constant/Holiday.java b/src/main/java/attendance/constant/Holiday.java new file mode 100644 index 00000000..caf782d4 --- /dev/null +++ b/src/main/java/attendance/constant/Holiday.java @@ -0,0 +1,37 @@ +package attendance.constant; + +import java.time.DayOfWeek; +import java.time.LocalDate; + +public enum Holiday { + + SATURDAY(DayOfWeek.SATURDAY, LocalDate.of(1, 1, 1)), + SUNDAY(DayOfWeek.SATURDAY, LocalDate.of(1, 1, 1)), + CHRISTMAS(DayOfWeek.WEDNESDAY, LocalDate.of(2024, 12, 25)), + NONE(null, null) + ; + + private final DayOfWeek dayOfWeek; + private final LocalDate date; + + Holiday(DayOfWeek dayOfWeek, LocalDate date) { + this.dayOfWeek = dayOfWeek; + this.date = date; + } + + public static Holiday from(LocalDate date) { + if (date.getDayOfWeek() == DayOfWeek.SATURDAY) { + return SATURDAY; + } + + if (date.getDayOfWeek() == DayOfWeek.SUNDAY) { + return SUNDAY; + } + + if (date.isEqual(CHRISTMAS.date)) { + return CHRISTMAS; + } + + return NONE; + } +} diff --git a/src/main/java/attendance/constant/OperationTime.java b/src/main/java/attendance/constant/OperationTime.java new file mode 100644 index 00000000..28389521 --- /dev/null +++ b/src/main/java/attendance/constant/OperationTime.java @@ -0,0 +1,20 @@ +package attendance.constant; + +import java.time.LocalTime; + +public enum OperationTime { + + START(LocalTime.of(8,0)), + END(LocalTime.of(23,0)) + ; + + private final LocalTime time; + + OperationTime(LocalTime time) { + this.time = time; + } + + public LocalTime getTime() { + return time; + } +} diff --git a/src/main/java/attendance/constant/Standard.java b/src/main/java/attendance/constant/Standard.java new file mode 100644 index 00000000..63204248 --- /dev/null +++ b/src/main/java/attendance/constant/Standard.java @@ -0,0 +1,38 @@ +package attendance.constant; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; + +public enum Standard { + + MONDAY(DayOfWeek.MONDAY, LocalTime.of(13,0), LocalTime.of(23,0)), + OTHER(null, LocalTime.of(10,0), LocalTime.of(23,0)), + ; + + private final DayOfWeek dayOfWeek; + private final LocalTime startTime; + private final LocalTime endTime; + + Standard(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) { + this.dayOfWeek = dayOfWeek; + this.startTime = startTime; + this.endTime = endTime; + } + + public static Standard from(LocalDate date) { + if (date.getDayOfWeek() == DayOfWeek.MONDAY) { + return MONDAY; + } + + return OTHER; + } + + public LocalTime getStartTime() { + return startTime; + } + + public LocalTime getEndTime() { + return endTime; + } +} diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index a6e7515c..a9dd03eb 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -1,8 +1,11 @@ package attendance.domain; +import attendance.constant.Check; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class Crew { @@ -18,7 +21,31 @@ public static Crew from(String crewName) { return new Crew(crewName); } - public void addAttendanceRecord(LocalDateTime dateTime) { + public Check addAttendanceRecord(LocalDateTime dateTime) { attendances.add(Attendance.from(dateTime)); + return Check.from(dateTime); + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + Crew crew = (Crew) object; + return Objects.equals(name, crew.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + + public boolean containsDate(LocalDate now) { + return attendances.stream() + .anyMatch(attendance -> attendance.getDate().isEqual(now)); } } diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 0de40d58..713a5a03 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -2,12 +2,14 @@ import static java.util.Locale.KOREA; +import attendance.constant.ErrorMessage; +import attendance.constant.Holiday; import camp.nextstep.edu.missionutils.DateTimes; -import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,7 +19,11 @@ public class Crews { private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); - private List crews; + private final List crews; + + public Crews() { + crews = new ArrayList<>(); + } public static Crews newInstance() { return new Crews(); @@ -43,10 +49,8 @@ private void setRecords(List attendanceRecords, Map dateTimes) { Crew crew = Crew.from(crewName); LocalDate now = DateTimes.now().toLocalDate(); - for (LocalDate date = LocalDate.of(24,12,1); date.isBefore(now) ; date.plusDays(1)) { - - if (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY || date.isEqual( - LocalDate.of(24,12,25))) { + for (LocalDate date = LocalDate.of(2024, 12, 1); date.isBefore(now); date = date.plusDays(1)) { + if (isHoliDay(date)) { continue; } @@ -55,8 +59,20 @@ private Crew setCrew(String crewName, Map dateTimes) { continue; } - crew.addAttendanceRecord(LocalDateTime.of(date, LocalTime.of(23,59))); + crew.addAttendanceRecord(LocalDateTime.of(date, LocalTime.of(23, 59))); } return crew; } + + private static boolean isHoliDay(LocalDate date) { + return !Holiday.from(date).equals(Holiday.NONE); + } + + public Crew getCrew(String findName) { + Crew findCrew = Crew.from(findName); + return crews.stream() + .filter(crew -> crew.equals(findCrew)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_NAME_ERROR.getErrorMessage())); + } } diff --git a/src/main/java/attendance/dto/CheckResult.java b/src/main/java/attendance/dto/CheckResult.java new file mode 100644 index 00000000..b8e0b8a5 --- /dev/null +++ b/src/main/java/attendance/dto/CheckResult.java @@ -0,0 +1,11 @@ +package attendance.dto; + +import attendance.constant.Check; +import java.time.LocalDateTime; + +public record CheckResult(LocalDateTime dateTime, Check check) { + + public static CheckResult of(LocalDateTime dateTime, Check check) { + return new CheckResult(dateTime, check); + } +} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index a8e8e572..ce8b7d51 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -1,6 +1,14 @@ package attendance.service; +import attendance.constant.Check; +import attendance.constant.ErrorMessage; +import attendance.constant.OperationTime; +import attendance.domain.Crew; import attendance.domain.Crews; +import attendance.dto.CheckResult; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import java.util.Map; @@ -14,4 +22,23 @@ public void registerFileInfo(Map> attendanceRecords) { crews.registerRecord(crewName, attendanceRecords.get(crewName)); } } + + public void validateCheckPossible(String name, LocalDate now) { + Crew crew = crews.getCrew(name); + if (crew.containsDate(now)) { + throw new IllegalArgumentException(ErrorMessage.ALREADY_ATTENDANCE_ERROR.getErrorMessage()); + } + } + + public CheckResult checkAttendance(String name, LocalDate now, LocalTime time) { + Crew crew = crews.getCrew(name); + Check check = crew.addAttendanceRecord(LocalDateTime.of(now, time)); + return CheckResult.of(LocalDateTime.of(now, time), check); + } + + public void validatePossibleTime(LocalTime time) { + if (time.isBefore(OperationTime.START.getTime()) || time.isAfter(OperationTime.END.getTime())) { + throw new IllegalArgumentException(ErrorMessage.NO_OPERATION_TIME_ERROR.getErrorMessage()); + } + } } diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java index d05c166f..f22d558f 100644 --- a/src/main/java/attendance/util/InputParser.java +++ b/src/main/java/attendance/util/InputParser.java @@ -44,7 +44,11 @@ public static Map> getAttendanceRecords(List readLi Map> attendanceRecords = new HashMap<>(); for (String readLine : readLines) { String[] split = readLine.split(DELIMITER); - attendanceRecords.getOrDefault(split[0], new ArrayList<>()).add(split[1]); + if (attendanceRecords.containsKey(split[0])) { + attendanceRecords.get(split[0]).add(split[1]); + continue; + } + attendanceRecords.put(split[0], new ArrayList<>()); } return attendanceRecords; } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 270234b4..9331c8d6 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -12,7 +12,22 @@ public class InputView { DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); public static String readMenuSelection(LocalDate now) { - System.out.printf("오늘은 %s입니다. 기능을 선택해 주세요.\n", now.format(DATETIME_FMT)); + System.out.printf("\n오늘은 %s입니다. 기능을 선택해 주세요.\n" + + "1. 출석 확인\n" + + "2. 출석 수정\n" + + "3. 크루별 출석 기록 확인\n" + + "4. 제적 위험자 확인\n" + + "Q. 종료\n", now.format(DATETIME_FMT)); + return Console.readLine(); + } + + public static String readName() { + System.out.println("\n닉네임을 입력해 주세요."); + return Console.readLine(); + } + + public static String readTime() { + System.out.println("\n등교 시간을 입력해 주세요."); return Console.readLine(); } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java new file mode 100644 index 00000000..5b26e7e1 --- /dev/null +++ b/src/main/java/attendance/view/OutputView.java @@ -0,0 +1,32 @@ +package attendance.view; + +import attendance.constant.Check; +import attendance.dto.CheckResult; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class OutputView { + + private static final Locale KOREA = Locale.KOREA; + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일 HH:mm", KOREA); + private static final DateTimeFormatter DATE_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); + private static final DateTimeFormatter TIME_FMT = + DateTimeFormatter.ofPattern("HH:mm", KOREA); + + private OutputView() { + } + + public static void printErrorMessage(IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + + public static void printCheckResult(CheckResult checkResult) { + Check check = checkResult.check(); + LocalDateTime dateTime = checkResult.dateTime(); + + System.out.printf("\n%s (%s)\n", dateTime.format(DATETIME_FMT), check.getName()); + } +} From 063ec20ee9c19e783f5495cd21c6594aabf60ec1 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 13:11:53 +0900 Subject: [PATCH 06/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/command/impl/CheckCommand.java | 7 ++++--- .../command/impl/ModificationCommand.java | 19 +++++++++++++++++++ .../attendance/constant/ErrorMessage.java | 3 ++- .../controller/AttendanceController.java | 5 ++--- src/main/java/attendance/domain/Crew.java | 13 ++++++++++++- src/main/java/attendance/domain/Crews.java | 6 +++--- .../attendance/dto/ModificationResult.java | 11 +++++++++++ .../attendance/service/AttendanceService.java | 18 ++++++++++++++++++ .../java/attendance/util/InputParser.java | 7 +++++-- .../java/attendance/util/NumberConvertor.java | 2 +- src/main/java/attendance/view/InputView.java | 17 ++++++++++++++++- src/main/java/attendance/view/OutputView.java | 19 +++++++++++++++---- 12 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 src/main/java/attendance/dto/ModificationResult.java diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index 2385bc3b..4a7ea257 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -10,7 +10,6 @@ import attendance.util.InputParser; import attendance.view.InputView; import attendance.view.OutputView; -import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -28,9 +27,11 @@ public CheckCommand(AttendanceService service) { @Override public void execute() { - LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = DateTimes.now().toLocalDate(); + LocalDate now = LocalDate.of(2024, 12, 13); if (isHoliday(now)) { - throw new IllegalArgumentException(ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); + throw new IllegalArgumentException( + ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); } String name = InputParser.parseName(InputView.readName()); diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index 92aa3f25..084642da 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -1,7 +1,13 @@ package attendance.command.impl; import attendance.command.Command; +import attendance.dto.ModificationResult; import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.view.InputView; +import attendance.view.OutputView; +import java.time.LocalDate; +import java.time.LocalTime; public class ModificationCommand implements Command { @@ -13,6 +19,19 @@ public ModificationCommand(AttendanceService service) { @Override public void execute() { + String name = InputParser.parseName(InputView.readModificationName()); + service.validateModificationPossibleName(name); + LocalDate date = InputParser.parseDay(InputView.readModificationDay()); +// LocalDate now = DateTimes.now().toLocalDate(); + LocalDate now = LocalDate.of(2024, 12, 13); + service.validateModificationPossibleDate(now, date); + + LocalTime newTime = InputParser.parseTime(InputView.readModificationTime()); + service.validatePossibleTime(newTime); + + ModificationResult modificationResult = service.modifyRecord(name, date, newTime); + + OutputView.printModificationResult(modificationResult); } } diff --git a/src/main/java/attendance/constant/ErrorMessage.java b/src/main/java/attendance/constant/ErrorMessage.java index acef1c83..50d91258 100644 --- a/src/main/java/attendance/constant/ErrorMessage.java +++ b/src/main/java/attendance/constant/ErrorMessage.java @@ -2,12 +2,13 @@ public enum ErrorMessage { - FORMAT_ERROR("잘못된 형식입니다."), + FORMAT_ERROR("잘못된 형식을 입력하였습니다."), NO_ATTENDANCE_DAY_ERROR("%s은 등교일이 아닙니다."), NO_EXIST_NAME_ERROR("등록되지 않은 닉네임입니다."), ALREADY_ATTENDANCE_ERROR("이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."), NO_OPERATION_TIME_ERROR("캠퍼스 운영 시간에만 출석이 가능합니다."), FUTURE_DAY_ERROR("아직 수정할 수 없습니다."), + NO_EXIST_ATTENDANCE_CHECK_RECORD("출석 기록이 없습니다."), ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java index 2d2f26dc..8781db58 100644 --- a/src/main/java/attendance/controller/AttendanceController.java +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -6,7 +6,6 @@ import attendance.util.InputParser; import attendance.util.file.FileReader; import attendance.view.InputView; -import camp.nextstep.edu.missionutils.DateTimes; import java.io.IOException; import java.time.LocalDate; import java.util.List; @@ -24,8 +23,8 @@ public AttendanceController(MenuCommandRegistry registry, AttendanceService serv public void run() throws IOException { registerFileInfo(); - LocalDate now = DateTimes.now().toLocalDate(); - +// LocalDate now = DateTimes.now().toLocalDate(); + LocalDate now = LocalDate.of(2024, 12, 13); while (true) { MenuOption option = readOption(now); diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index a9dd03eb..b8e98e62 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -1,8 +1,10 @@ package attendance.domain; import attendance.constant.Check; +import attendance.constant.ErrorMessage; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -12,7 +14,7 @@ public class Crew { private final String name; private final List attendances; - public Crew(String name) { + private Crew(String name) { this.name = name; this.attendances = new ArrayList<>(); } @@ -48,4 +50,13 @@ public boolean containsDate(LocalDate now) { return attendances.stream() .anyMatch(attendance -> attendance.getDate().isEqual(now)); } + + public LocalTime modifyRecord(LocalDate date, LocalTime time) { + Attendance record = attendances.stream() + .filter(attendance -> attendance.getDate().isEqual(date)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + ErrorMessage.NO_EXIST_ATTENDANCE_CHECK_RECORD.getErrorMessage())); + return record.modifyTime(time); + } } diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 713a5a03..69daaf1e 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -4,7 +4,6 @@ import attendance.constant.ErrorMessage; import attendance.constant.Holiday; -import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -21,7 +20,7 @@ public class Crews { private final List crews; - public Crews() { + private Crews() { crews = new ArrayList<>(); } @@ -48,7 +47,8 @@ private void setRecords(List attendanceRecords, Map dateTimes) { Crew crew = Crew.from(crewName); - LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = DateTimes.now().toLocalDate(); + LocalDate now = LocalDate.of(2024, 12, 13); for (LocalDate date = LocalDate.of(2024, 12, 1); date.isBefore(now); date = date.plusDays(1)) { if (isHoliDay(date)) { continue; diff --git a/src/main/java/attendance/dto/ModificationResult.java b/src/main/java/attendance/dto/ModificationResult.java new file mode 100644 index 00000000..cfcd1b48 --- /dev/null +++ b/src/main/java/attendance/dto/ModificationResult.java @@ -0,0 +1,11 @@ +package attendance.dto; + +import java.time.LocalDate; +import java.time.LocalTime; + +public record ModificationResult(LocalDate date, LocalTime oldTime, LocalTime newTime) { + + public static ModificationResult of(LocalDate date, LocalTime oldTime, LocalTime newTime) { + return new ModificationResult(date, oldTime, newTime); + } +} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index ce8b7d51..afaee130 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -6,6 +6,7 @@ import attendance.domain.Crew; import attendance.domain.Crews; import attendance.dto.CheckResult; +import attendance.dto.ModificationResult; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -41,4 +42,21 @@ public void validatePossibleTime(LocalTime time) { throw new IllegalArgumentException(ErrorMessage.NO_OPERATION_TIME_ERROR.getErrorMessage()); } } + + public void validateModificationPossibleName(String name) { + crews.getCrew(name); + } + + public void validateModificationPossibleDate(LocalDate now, LocalDate date) { + if (date.isAfter(now)) { + throw new IllegalArgumentException(ErrorMessage.FUTURE_DAY_ERROR.getErrorMessage()); + } + } + + public ModificationResult modifyRecord(String name, LocalDate date, LocalTime newTime) { + Crew crew = crews.getCrew(name); + LocalTime oldTime = crew.modifyRecord(date, newTime); + + return ModificationResult.of(date, oldTime, newTime); + } } diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java index f22d558f..0a0ec07f 100644 --- a/src/main/java/attendance/util/InputParser.java +++ b/src/main/java/attendance/util/InputParser.java @@ -1,6 +1,7 @@ package attendance.util; import attendance.command.MenuOption; +import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; @@ -36,7 +37,9 @@ public static LocalDate parseDay(String rawInput) { Validator.validateDayFormat(rawInput); - return LocalDate.of(24,12, NumberConvertor.convertToNumber(rawInput)); + LocalDate now = DateTimes.now().toLocalDate(); + return LocalDate.of(2024, 12, NumberConvertor.convertToNumber(rawInput)); +// return LocalDate.of(now.getYear(), now.getMonthValue(), NumberConvertor.convertToNumber(rawInput)); } public static Map> getAttendanceRecords(List readLines) { @@ -48,7 +51,7 @@ public static Map> getAttendanceRecords(List readLi attendanceRecords.get(split[0]).add(split[1]); continue; } - attendanceRecords.put(split[0], new ArrayList<>()); + attendanceRecords.put(split[0], new ArrayList<>(List.of(split[1]))); } return attendanceRecords; } diff --git a/src/main/java/attendance/util/NumberConvertor.java b/src/main/java/attendance/util/NumberConvertor.java index b48fbc20..b68a85f7 100644 --- a/src/main/java/attendance/util/NumberConvertor.java +++ b/src/main/java/attendance/util/NumberConvertor.java @@ -1,6 +1,6 @@ package attendance.util; -import main.java.lotto.constant.ErrorMessage; +import attendance.constant.ErrorMessage; public final class NumberConvertor { diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 9331c8d6..46fcc9f0 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -27,7 +27,22 @@ public static String readName() { } public static String readTime() { - System.out.println("\n등교 시간을 입력해 주세요."); + System.out.println("등교 시간을 입력해 주세요."); + return Console.readLine(); + } + + public static String readModificationName() { + System.out.println("\n닉네임을 입력해 주세요."); + return Console.readLine(); + } + + public static String readModificationDay() { + System.out.println("수정하려는 날짜(일)를 입력해 주세요."); + return Console.readLine(); + } + + public static String readModificationTime() { + System.out.println("언제로 변경하겠습니까?"); return Console.readLine(); } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 5b26e7e1..903ea88e 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -2,7 +2,10 @@ import attendance.constant.Check; import attendance.dto.CheckResult; +import attendance.dto.ModificationResult; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Locale; @@ -19,14 +22,22 @@ public class OutputView { private OutputView() { } - public static void printErrorMessage(IllegalArgumentException e) { - System.out.println(e.getMessage()); - } - public static void printCheckResult(CheckResult checkResult) { Check check = checkResult.check(); LocalDateTime dateTime = checkResult.dateTime(); System.out.printf("\n%s (%s)\n", dateTime.format(DATETIME_FMT), check.getName()); } + + public static void printModificationResult(ModificationResult modificationResult) { + LocalDate date = modificationResult.date(); + LocalTime oldTime = modificationResult.oldTime(); + LocalTime newTime = modificationResult.newTime(); + + System.out.printf("\n%s %s (%s) -> %s (%s) 수정 완료!\n", + date.format(DATE_FMT), + oldTime.format(TIME_FMT), Check.from(LocalDateTime.of(date, oldTime)).getName(), + newTime.format(TIME_FMT), Check.from(LocalDateTime.of(date, newTime)).getName() + ); + } } From 532d1951789b539863a1c40fa7cf22bb0b49db64 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 13:36:31 +0900 Subject: [PATCH 07/30] =?UTF-8?q?feat:=20=EC=A0=9C=EC=A0=81=20=EC=9C=84?= =?UTF-8?q?=ED=97=98=EC=9E=90=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/impl/ModificationCommand.java | 2 +- .../command/impl/RecordQueryCommand.java | 9 ++++ src/main/java/attendance/constant/Danger.java | 2 +- .../java/attendance/domain/Attendance.java | 17 ++++++++ src/main/java/attendance/domain/Crew.java | 43 +++++++++++++++++++ .../attendance/dto/RecordQueryResult.java | 9 ++++ .../attendance/service/AttendanceService.java | 8 +++- src/main/java/attendance/view/OutputView.java | 32 ++++++++++++++ 8 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/main/java/attendance/dto/RecordQueryResult.java diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index 084642da..66d47031 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -20,7 +20,7 @@ public ModificationCommand(AttendanceService service) { @Override public void execute() { String name = InputParser.parseName(InputView.readModificationName()); - service.validateModificationPossibleName(name); + service.validateNameExists(name); LocalDate date = InputParser.parseDay(InputView.readModificationDay()); // LocalDate now = DateTimes.now().toLocalDate(); diff --git a/src/main/java/attendance/command/impl/RecordQueryCommand.java b/src/main/java/attendance/command/impl/RecordQueryCommand.java index d87257c1..bb048d8d 100644 --- a/src/main/java/attendance/command/impl/RecordQueryCommand.java +++ b/src/main/java/attendance/command/impl/RecordQueryCommand.java @@ -1,7 +1,11 @@ package attendance.command.impl; import attendance.command.Command; +import attendance.dto.RecordQueryResult; import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.view.InputView; +import attendance.view.OutputView; public class RecordQueryCommand implements Command { @@ -13,6 +17,11 @@ public RecordQueryCommand(AttendanceService service) { @Override public void execute() { + String name = InputParser.parseName(InputView.readName()); + service.validateNameExists(name); + RecordQueryResult result = service.getAttendanceRecords(name); + + OutputView.printRecordQuery(result); } } diff --git a/src/main/java/attendance/constant/Danger.java b/src/main/java/attendance/constant/Danger.java index 7f057195..72807ebf 100644 --- a/src/main/java/attendance/constant/Danger.java +++ b/src/main/java/attendance/constant/Danger.java @@ -22,7 +22,7 @@ public static Danger from(int count) { return Arrays.stream(values()) .filter(danger -> danger.absenceCount <= count) .findFirst() - .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + .orElse(NONE); } public String getName() { diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index 6ca8aba0..9fcdff6c 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.Check; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -42,4 +43,20 @@ public int compareTo(Attendance o) { return 1; } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public boolean isAttendance() { + return Check.from(this.dateTime).equals(Check.ATTENDANCE); + } + + public boolean isLate() { + return Check.from(this.dateTime).equals(Check.LATE); + } + + public boolean isAbsence() { + return Check.from(this.dateTime).equals(Check.ABSENCE); + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index b8e98e62..2656e318 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -1,6 +1,7 @@ package attendance.domain; import attendance.constant.Check; +import attendance.constant.Danger; import attendance.constant.ErrorMessage; import java.time.LocalDate; import java.time.LocalDateTime; @@ -59,4 +60,46 @@ public LocalTime modifyRecord(LocalDate date, LocalTime time) { ErrorMessage.NO_EXIST_ATTENDANCE_CHECK_RECORD.getErrorMessage())); return record.modifyTime(time); } + + public List getAttendances() { + return attendances; + } + + public int getAttendanceCount() { + int count = 0; + for (Attendance attendance : attendances) { + if (attendance.isAttendance()) { + count++; + } + } + return count; + } + + public int getLateCount() { + int count = 0; + for (Attendance attendance : attendances) { + if (attendance.isLate()) { + count++; + } + } + return count; + } + + public int getAbsenceCount() { + int count = 0; + for (Attendance attendance : attendances) { + if (attendance.isAbsence()) { + count++; + } + } + return count; + } + + public boolean isDangerCrew() { + return getAbsenceCount() + getLateCount() / 3 >= 2; + } + + public String getDangerState() { + return Danger.from(getAbsenceCount() + getLateCount() / 3).getName(); + } } diff --git a/src/main/java/attendance/dto/RecordQueryResult.java b/src/main/java/attendance/dto/RecordQueryResult.java new file mode 100644 index 00000000..9c38b43d --- /dev/null +++ b/src/main/java/attendance/dto/RecordQueryResult.java @@ -0,0 +1,9 @@ +package attendance.dto; + +import attendance.domain.Crew; + +public record RecordQueryResult(Crew crew) { + public static RecordQueryResult from(Crew crew) { + return new RecordQueryResult(crew); + } +} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index afaee130..b9506518 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -7,6 +7,7 @@ import attendance.domain.Crews; import attendance.dto.CheckResult; import attendance.dto.ModificationResult; +import attendance.dto.RecordQueryResult; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -43,7 +44,7 @@ public void validatePossibleTime(LocalTime time) { } } - public void validateModificationPossibleName(String name) { + public void validateNameExists(String name) { crews.getCrew(name); } @@ -59,4 +60,9 @@ public ModificationResult modifyRecord(String name, LocalDate date, LocalTime ne return ModificationResult.of(date, oldTime, newTime); } + + public RecordQueryResult getAttendanceRecords(String name) { + Crew crew = crews.getCrew(name); + return RecordQueryResult.from(crew); + } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 903ea88e..993002ba 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -1,12 +1,16 @@ package attendance.view; import attendance.constant.Check; +import attendance.domain.Attendance; +import attendance.domain.Crew; import attendance.dto.CheckResult; import attendance.dto.ModificationResult; +import attendance.dto.RecordQueryResult; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Locale; public class OutputView { @@ -40,4 +44,32 @@ public static void printModificationResult(ModificationResult modificationResult newTime.format(TIME_FMT), Check.from(LocalDateTime.of(date, newTime)).getName() ); } + + public static void printRecordQuery(RecordQueryResult queryResult) { + Crew crew = queryResult.crew(); + List attendances = crew.getAttendances(); + attendances.sort(null); + + System.out.println("이번 달 빙티의 출석 기록입니다.\n"); + for (Attendance attendance : attendances) { + LocalDateTime dateTime = attendance.getDateTime(); + LocalDate date = dateTime.toLocalDate(); + + if (!dateTime.toLocalTime().isBefore(LocalTime.of(23, 59))) { + System.out.printf("%s --:-- (%s)", date.format(DATE_FMT), + Check.from(attendance.getDateTime()).getName()); + continue; + } + System.out.printf("%s (%s)", dateTime.format(DATETIME_FMT), Check.from(attendance.getDateTime()).getName()); + } + System.out.println(); + + System.out.printf("출석: %d회\n", crew.getAttendanceCount()); + System.out.printf("지각: %d회\n", crew.getLateCount()); + System.out.printf("결석: %d회\n", crew.getAbsenceCount()); + + if (crew.isDangerCrew()) { + System.out.printf("\n%s 대상자입니다.\n", crew.getDangerState()); + } + } } From 0991e35a519540de7340c44bd1b5c638325ae19c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 13:50:09 +0900 Subject: [PATCH 08/30] =?UTF-8?q?feat:=20=EC=A0=9C=EC=A0=81=20=EC=9C=84?= =?UTF-8?q?=ED=97=98=EC=9E=90=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/impl/DangerQueryCommand.java | 5 +++++ src/main/java/attendance/domain/Crew.java | 4 ++++ src/main/java/attendance/domain/Crews.java | 10 ++++++++++ .../java/attendance/service/AttendanceService.java | 4 ++++ src/main/java/attendance/view/OutputView.java | 14 ++++++++++++++ 5 files changed, 37 insertions(+) diff --git a/src/main/java/attendance/command/impl/DangerQueryCommand.java b/src/main/java/attendance/command/impl/DangerQueryCommand.java index e0fca9a3..e33d5acd 100644 --- a/src/main/java/attendance/command/impl/DangerQueryCommand.java +++ b/src/main/java/attendance/command/impl/DangerQueryCommand.java @@ -1,7 +1,10 @@ package attendance.command.impl; import attendance.command.Command; +import attendance.domain.Crew; import attendance.service.AttendanceService; +import attendance.view.OutputView; +import java.util.List; public class DangerQueryCommand implements Command { @@ -13,6 +16,8 @@ public DangerQueryCommand(AttendanceService service) { @Override public void execute() { + List dangers = service.getDangers(); + OutputView.printDangers(dangers); } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 2656e318..be40c711 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -102,4 +102,8 @@ public boolean isDangerCrew() { public String getDangerState() { return Danger.from(getAbsenceCount() + getLateCount() / 3).getName(); } + + public int getAbsenceLateCount() { + return getAbsenceCount() + getLateCount() / 3; + } } diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 69daaf1e..398874d6 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -75,4 +75,14 @@ public Crew getCrew(String findName) { .findFirst() .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_NAME_ERROR.getErrorMessage())); } + + public List getDangers() { + List dangers = new ArrayList<>(); + for (Crew crew : crews) { + if (crew.isDangerCrew()) { + dangers.add(crew); + } + } + return dangers; + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index b9506518..728c2122 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -65,4 +65,8 @@ public RecordQueryResult getAttendanceRecords(String name) { Crew crew = crews.getCrew(name); return RecordQueryResult.from(crew); } + + public List getDangers() { + return crews.getDangers(); + } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 993002ba..049dcfb0 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -10,6 +10,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.Comparator; import java.util.List; import java.util.Locale; @@ -72,4 +73,17 @@ public static void printRecordQuery(RecordQueryResult queryResult) { System.out.printf("\n%s 대상자입니다.\n", crew.getDangerState()); } } + + public static void printDangers(List dangers) { + System.out.println("\n제적 위험자 조회 결과"); + dangers.sort(Comparator.comparingInt(Crew::getAbsenceLateCount).reversed() + .thenComparing(Crew::getAbsenceCount).reversed() + .thenComparing(Crew::getLateCount).reversed() + .thenComparing(Crew::getName)); + for (Crew danger : dangers) { + System.out.printf("- %s: 결석 %d회, 지각 %d회 (%s)\n", + danger.getName(), danger.getAbsenceCount(), danger.getLateCount(), danger.getDangerState()); + } + System.out.println(); + } } From 87ed601b54d8d3ccff543fbadeefeb05656588f1 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 14:03:19 +0900 Subject: [PATCH 09/30] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/command/impl/CheckCommand.java | 5 +- .../command/impl/ModificationCommand.java | 5 +- .../controller/AttendanceController.java | 5 +- src/main/java/attendance/domain/Crews.java | 5 +- .../java/attendance/util/InputParser.java | 4 +- src/test/java/attendance/ApplicationTest.java | 57 +++++++++++++++++-- 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index 4a7ea257..8d5dafcb 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -10,6 +10,7 @@ import attendance.util.InputParser; import attendance.view.InputView; import attendance.view.OutputView; +import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -27,8 +28,8 @@ public CheckCommand(AttendanceService service) { @Override public void execute() { -// LocalDate now = DateTimes.now().toLocalDate(); - LocalDate now = LocalDate.of(2024, 12, 13); + LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = LocalDate.of(2024, 12, 13); if (isHoliday(now)) { throw new IllegalArgumentException( ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index 66d47031..bbb75460 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -6,6 +6,7 @@ import attendance.util.InputParser; import attendance.view.InputView; import attendance.view.OutputView; +import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalTime; @@ -23,8 +24,8 @@ public void execute() { service.validateNameExists(name); LocalDate date = InputParser.parseDay(InputView.readModificationDay()); -// LocalDate now = DateTimes.now().toLocalDate(); - LocalDate now = LocalDate.of(2024, 12, 13); + LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = LocalDate.of(2024, 12, 13); service.validateModificationPossibleDate(now, date); LocalTime newTime = InputParser.parseTime(InputView.readModificationTime()); diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java index 8781db58..d3e7fe8d 100644 --- a/src/main/java/attendance/controller/AttendanceController.java +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -6,6 +6,7 @@ import attendance.util.InputParser; import attendance.util.file.FileReader; import attendance.view.InputView; +import camp.nextstep.edu.missionutils.DateTimes; import java.io.IOException; import java.time.LocalDate; import java.util.List; @@ -23,8 +24,8 @@ public AttendanceController(MenuCommandRegistry registry, AttendanceService serv public void run() throws IOException { registerFileInfo(); -// LocalDate now = DateTimes.now().toLocalDate(); - LocalDate now = LocalDate.of(2024, 12, 13); + LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = LocalDate.of(2024, 12, 13); while (true) { MenuOption option = readOption(now); diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 398874d6..622038b0 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -4,6 +4,7 @@ import attendance.constant.ErrorMessage; import attendance.constant.Holiday; +import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -47,8 +48,8 @@ private void setRecords(List attendanceRecords, Map dateTimes) { Crew crew = Crew.from(crewName); -// LocalDate now = DateTimes.now().toLocalDate(); - LocalDate now = LocalDate.of(2024, 12, 13); + LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = LocalDate.of(2024, 12, 13); for (LocalDate date = LocalDate.of(2024, 12, 1); date.isBefore(now); date = date.plusDays(1)) { if (isHoliDay(date)) { continue; diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java index 0a0ec07f..0068ec88 100644 --- a/src/main/java/attendance/util/InputParser.java +++ b/src/main/java/attendance/util/InputParser.java @@ -38,8 +38,8 @@ public static LocalDate parseDay(String rawInput) { Validator.validateDayFormat(rawInput); LocalDate now = DateTimes.now().toLocalDate(); - return LocalDate.of(2024, 12, NumberConvertor.convertToNumber(rawInput)); -// return LocalDate.of(now.getYear(), now.getMonthValue(), NumberConvertor.convertToNumber(rawInput)); +// return LocalDate.of(2024, 12, NumberConvertor.convertToNumber(rawInput)); + return LocalDate.of(now.getYear(), now.getMonthValue(), NumberConvertor.convertToNumber(rawInput)); } public static Map> getAttendanceRecords(List readLines) { diff --git a/src/test/java/attendance/ApplicationTest.java b/src/test/java/attendance/ApplicationTest.java index c1b43494..9027808f 100644 --- a/src/test/java/attendance/ApplicationTest.java +++ b/src/test/java/attendance/ApplicationTest.java @@ -1,14 +1,13 @@ package attendance; -import camp.nextstep.edu.missionutils.test.NsTest; -import org.junit.jupiter.api.Test; - -import java.time.LocalDate; - import static camp.nextstep.edu.missionutils.test.Assertions.assertNowTest; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import camp.nextstep.edu.missionutils.test.NsTest; +import java.time.LocalDate; +import org.junit.jupiter.api.Test; + class ApplicationTest extends NsTest { @Test void 잘못된_형식_예외_테스트() { @@ -77,6 +76,54 @@ class ApplicationTest extends NsTest { ); } + @Test + void 제적_위험자_조회_기능_테스트_12월_12일() { + assertNowTest( + () -> { + run("4", "Q"); + assertThat(output()).contains( + "제적 위험자 조회 결과\n" + + "- 빙티: 결석 2회, 지각 3회 (면담)\n" + + "- 이든: 결석 2회, 지각 3회 (면담)" + ); + }, + LocalDate.of(2024, 12, 12).atStartOfDay() + ); + } + + @Test + void 제적_위험자_조회_기능_테스트_12월_13일() { + assertNowTest( + () -> { + run("4", "Q"); + assertThat(output()).contains( + "- 빙티: 결석 3회, 지각 3회 (면담)\n" + + "- 이든: 결석 2회, 지각 4회 (면담)\n" + + "- 빙봉: 결석 1회, 지각 5회 (경고)\n" + + "- 쿠키: 결석 2회, 지각 2회 (경고)" + ); + }, + LocalDate.of(2024, 12, 13).atStartOfDay() + ); + } + + @Test + void 제적_위험자_조회_기능_테스트_12월_14일() { + assertNowTest( + () -> { + run("4", "Q"); + assertThat(output()).contains( + "- 빙티: 결석 3회, 지각 4회 (면담)\n" + + "- 빙봉: 결석 1회, 지각 6회 (면담)\n" + + "- 이든: 결석 2회, 지각 5회 (면담)\n" + + "- 쿠키: 결석 2회, 지각 3회 (면담)\n" + + "- 짱수: 결석 2회, 지각 0회 (경고)" + ); + }, + LocalDate.of(2024, 12, 14).atStartOfDay() + ); + } + @Override protected void runMain() { Application.main(new String[]{}); From 03d688a0f0d08211021ad7742e2b8584782cc1b3 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Mon, 5 Jan 2026 14:04:00 +0900 Subject: [PATCH 10/30] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ed9c50d..146fa3ce 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 1. 파일 입력 및 크루별 출석 기록 저장 1. 1일부터 어제까지 순회하면서 기록 저장 - 주말 및 공휴일은 패스 - - 기록이 없으면 결석으로 처리(시간은 00:00) + - 기록이 없으면 결석으로 처리(시간은 23:59) 2. 오늘 날짜 출력 및 기능 선택 입력 및 검증 1. 오늘 날짜 출력 2. 기능 선택 입력 및 검증 @@ -60,6 +60,7 @@ 2. 출석 기록 출력 - 등교일만 출력 - 날짜, 요일, 시간, 상태 + - 시간이 23:59 이면 결석으로 처리 - 출석, 지각, 결석 횟수 - 제적 위험자인 경우! -> 어떤 대상자인지 6. 제적 위험자 확인 @@ -70,4 +71,4 @@ 2. 결석 내림차순 3. 지각 내림차순 4. 이름 오름차순 -7. 종료 +7. 종료 \ No newline at end of file From 0a493fde221d4a8bbd3b5e391c5d15d4f210310f Mon Sep 17 00:00:00 2001 From: khcho96 Date: Tue, 6 Jan 2026 10:35:43 +0900 Subject: [PATCH 11/30] =?UTF-8?q?feat:=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/impl/ModificationCommand.java | 21 ++++++++++++-- src/main/java/attendance/view/OutputView.java | 28 +++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index bbb75460..42d568bd 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -1,6 +1,10 @@ package attendance.command.impl; +import static java.util.Locale.KOREA; + import attendance.command.Command; +import attendance.constant.ErrorMessage; +import attendance.constant.Holiday; import attendance.dto.ModificationResult; import attendance.service.AttendanceService; import attendance.util.InputParser; @@ -9,9 +13,13 @@ import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalTime; +import java.time.format.DateTimeFormatter; public class ModificationCommand implements Command { + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); + private final AttendanceService service; public ModificationCommand(AttendanceService service) { @@ -20,12 +28,17 @@ public ModificationCommand(AttendanceService service) { @Override public void execute() { + LocalDate now = DateTimes.now().toLocalDate(); +// LocalDate now = LocalDate.of(2024, 12, 13); + if (isHoliday(now)) { + throw new IllegalArgumentException( + ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); + } + String name = InputParser.parseName(InputView.readModificationName()); service.validateNameExists(name); LocalDate date = InputParser.parseDay(InputView.readModificationDay()); - LocalDate now = DateTimes.now().toLocalDate(); -// LocalDate now = LocalDate.of(2024, 12, 13); service.validateModificationPossibleDate(now, date); LocalTime newTime = InputParser.parseTime(InputView.readModificationTime()); @@ -35,4 +48,8 @@ public void execute() { OutputView.printModificationResult(modificationResult); } + + private boolean isHoliday(LocalDate now) { + return !Holiday.from(now).equals(Holiday.NONE); + } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 049dcfb0..2bfe4719 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -1,22 +1,23 @@ package attendance.view; +import static java.util.Locale.KOREA; + import attendance.constant.Check; import attendance.domain.Attendance; import attendance.domain.Crew; import attendance.dto.CheckResult; import attendance.dto.ModificationResult; import attendance.dto.RecordQueryResult; +import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.Comparator; import java.util.List; -import java.util.Locale; public class OutputView { - private static final Locale KOREA = Locale.KOREA; private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("M월 dd일 E요일 HH:mm", KOREA); private static final DateTimeFormatter DATE_FMT = @@ -51,17 +52,22 @@ public static void printRecordQuery(RecordQueryResult queryResult) { List attendances = crew.getAttendances(); attendances.sort(null); - System.out.println("이번 달 빙티의 출석 기록입니다.\n"); + System.out.printf("이번 달 %s의 출석 기록입니다.\n", crew.getName()); for (Attendance attendance : attendances) { LocalDateTime dateTime = attendance.getDateTime(); LocalDate date = dateTime.toLocalDate(); + if (date.isEqual(DateTimes.now().toLocalDate())) { + continue; + } + if (!dateTime.toLocalTime().isBefore(LocalTime.of(23, 59))) { - System.out.printf("%s --:-- (%s)", date.format(DATE_FMT), + System.out.printf("%s --:-- (%s)\n", date.format(DATE_FMT), Check.from(attendance.getDateTime()).getName()); continue; } - System.out.printf("%s (%s)", dateTime.format(DATETIME_FMT), Check.from(attendance.getDateTime()).getName()); + System.out.printf("%s (%s)\n", dateTime.format(DATETIME_FMT), + Check.from(attendance.getDateTime()).getName()); } System.out.println(); @@ -76,10 +82,14 @@ public static void printRecordQuery(RecordQueryResult queryResult) { public static void printDangers(List dangers) { System.out.println("\n제적 위험자 조회 결과"); - dangers.sort(Comparator.comparingInt(Crew::getAbsenceLateCount).reversed() - .thenComparing(Crew::getAbsenceCount).reversed() - .thenComparing(Crew::getLateCount).reversed() - .thenComparing(Crew::getName)); + dangers.sort( + Comparator.comparingInt(Crew::getAbsenceLateCount).reversed() + .thenComparing(Comparator.comparingInt(Crew::getAbsenceCount).reversed()) + .thenComparing(Comparator.comparingInt(Crew::getLateCount).reversed()) + .thenComparing(Crew::getName) + ); + + for (Crew danger : dangers) { System.out.printf("- %s: 결석 %d회, 지각 %d회 (%s)\n", danger.getName(), danger.getAbsenceCount(), danger.getLateCount(), danger.getDangerState()); From 3f71603ccc6327fc3e34a13bef69e40c640c32ba Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 15:30:59 +0900 Subject: [PATCH 12/30] first commit --- src/main/java/attendance/Application.java | 14 +-- src/main/java/attendance/command/Command.java | 5 - .../command/MenuCommandRegistry.java | 30 ----- .../java/attendance/command/MenuOption.java | 25 ---- .../attendance/command/impl/CheckCommand.java | 52 --------- .../command/impl/DangerQueryCommand.java | 23 ---- .../command/impl/ModificationCommand.java | 55 --------- .../command/impl/RecordQueryCommand.java | 27 ----- src/main/java/attendance/constant/Check.java | 35 ------ src/main/java/attendance/constant/Danger.java | 31 ----- .../attendance/constant/ErrorMessage.java | 24 ---- .../java/attendance/constant/Holiday.java | 37 ------ .../attendance/constant/OperationTime.java | 20 ---- .../java/attendance/constant/Standard.java | 38 ------ .../controller/AttendanceController.java | 52 --------- .../java/attendance/domain/Attendance.java | 62 ---------- src/main/java/attendance/domain/Crew.java | 109 ------------------ src/main/java/attendance/domain/Crews.java | 89 -------------- src/main/java/attendance/dto/CheckResult.java | 11 -- .../attendance/dto/ModificationResult.java | 11 -- .../attendance/dto/RecordQueryResult.java | 9 -- .../attendance/service/AttendanceService.java | 72 ------------ .../java/attendance/util/InputParser.java | 58 ---------- .../java/attendance/util/NumberConvertor.java | 14 --- src/main/java/attendance/util/Validator.java | 23 ---- .../java/attendance/util/file/FileReader.java | 32 ----- src/main/java/attendance/view/InputView.java | 48 -------- src/main/java/attendance/view/OutputView.java | 99 ---------------- 28 files changed, 1 insertion(+), 1104 deletions(-) delete mode 100644 src/main/java/attendance/command/Command.java delete mode 100644 src/main/java/attendance/command/MenuCommandRegistry.java delete mode 100644 src/main/java/attendance/command/MenuOption.java delete mode 100644 src/main/java/attendance/command/impl/CheckCommand.java delete mode 100644 src/main/java/attendance/command/impl/DangerQueryCommand.java delete mode 100644 src/main/java/attendance/command/impl/ModificationCommand.java delete mode 100644 src/main/java/attendance/command/impl/RecordQueryCommand.java delete mode 100644 src/main/java/attendance/constant/Check.java delete mode 100644 src/main/java/attendance/constant/Danger.java delete mode 100644 src/main/java/attendance/constant/ErrorMessage.java delete mode 100644 src/main/java/attendance/constant/Holiday.java delete mode 100644 src/main/java/attendance/constant/OperationTime.java delete mode 100644 src/main/java/attendance/constant/Standard.java delete mode 100644 src/main/java/attendance/controller/AttendanceController.java delete mode 100644 src/main/java/attendance/domain/Attendance.java delete mode 100644 src/main/java/attendance/domain/Crew.java delete mode 100644 src/main/java/attendance/domain/Crews.java delete mode 100644 src/main/java/attendance/dto/CheckResult.java delete mode 100644 src/main/java/attendance/dto/ModificationResult.java delete mode 100644 src/main/java/attendance/dto/RecordQueryResult.java delete mode 100644 src/main/java/attendance/service/AttendanceService.java delete mode 100644 src/main/java/attendance/util/InputParser.java delete mode 100644 src/main/java/attendance/util/NumberConvertor.java delete mode 100644 src/main/java/attendance/util/Validator.java delete mode 100644 src/main/java/attendance/util/file/FileReader.java delete mode 100644 src/main/java/attendance/view/InputView.java delete mode 100644 src/main/java/attendance/view/OutputView.java diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index 1097af77..759aa3ae 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -1,19 +1,7 @@ package attendance; -import attendance.command.MenuCommandRegistry; -import attendance.controller.AttendanceController; -import attendance.service.AttendanceService; -import java.io.IOException; - public class Application { public static void main(String[] args) { - AttendanceService service = new AttendanceService(); - MenuCommandRegistry registry = MenuCommandRegistry.from(service); - AttendanceController controller = new AttendanceController(registry, service); - try { - controller.run(); - } catch (IOException e) { - throw new RuntimeException(e); - } + } } diff --git a/src/main/java/attendance/command/Command.java b/src/main/java/attendance/command/Command.java deleted file mode 100644 index cee363fe..00000000 --- a/src/main/java/attendance/command/Command.java +++ /dev/null @@ -1,5 +0,0 @@ -package attendance.command; - -public interface Command { - void execute(); -} diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java deleted file mode 100644 index f13183d3..00000000 --- a/src/main/java/attendance/command/MenuCommandRegistry.java +++ /dev/null @@ -1,30 +0,0 @@ -package attendance.command; - -import attendance.command.impl.CheckCommand; -import attendance.command.impl.DangerQueryCommand; -import attendance.command.impl.ModificationCommand; -import attendance.command.impl.RecordQueryCommand; -import attendance.service.AttendanceService; -import java.util.EnumMap; - -public class MenuCommandRegistry { - - private final EnumMap commands; - - private MenuCommandRegistry(EnumMap commands) { - this.commands = commands; - } - - public static MenuCommandRegistry from(AttendanceService service) { - EnumMap map = new EnumMap<>(MenuOption.class); - map.put(MenuOption.A, new CheckCommand(service)); - map.put(MenuOption.B, new ModificationCommand(service)); - map.put(MenuOption.C, new RecordQueryCommand(service)); - map.put(MenuOption.D, new DangerQueryCommand(service)); - return new MenuCommandRegistry(map); - } - - public void execute(MenuOption option) { - commands.get(option).execute(); - } -} diff --git a/src/main/java/attendance/command/MenuOption.java b/src/main/java/attendance/command/MenuOption.java deleted file mode 100644 index 6e6a9bd5..00000000 --- a/src/main/java/attendance/command/MenuOption.java +++ /dev/null @@ -1,25 +0,0 @@ -package attendance.command; - -import attendance.constant.ErrorMessage; -import java.util.Arrays; - -public enum MenuOption { - A("1"), - B("2"), - C("3"), - D("4"), - QUIT("Q"); - - private final String command; - - MenuOption(String command) { - this.command = command; - } - - public static MenuOption from(String command) { - return Arrays.stream(values()) - .filter(opt -> opt.command.equals(command)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); - } -} diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java deleted file mode 100644 index 8d5dafcb..00000000 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ /dev/null @@ -1,52 +0,0 @@ -package attendance.command.impl; - -import static java.util.Locale.KOREA; - -import attendance.command.Command; -import attendance.constant.ErrorMessage; -import attendance.constant.Holiday; -import attendance.dto.CheckResult; -import attendance.service.AttendanceService; -import attendance.util.InputParser; -import attendance.view.InputView; -import attendance.view.OutputView; -import camp.nextstep.edu.missionutils.DateTimes; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -public class CheckCommand implements Command { - - private static final DateTimeFormatter DATETIME_FMT = - DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); - - private final AttendanceService service; - - public CheckCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - LocalDate now = DateTimes.now().toLocalDate(); -// LocalDate now = LocalDate.of(2024, 12, 13); - if (isHoliday(now)) { - throw new IllegalArgumentException( - ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); - } - - String name = InputParser.parseName(InputView.readName()); - service.validateCheckPossible(name, now); - - LocalTime time = InputParser.parseTime(InputView.readTime()); - service.validatePossibleTime(time); - - CheckResult checkResult = service.checkAttendance(name, now, time); - - OutputView.printCheckResult(checkResult); - } - - private boolean isHoliday(LocalDate now) { - return !Holiday.from(now).equals(Holiday.NONE); - } -} diff --git a/src/main/java/attendance/command/impl/DangerQueryCommand.java b/src/main/java/attendance/command/impl/DangerQueryCommand.java deleted file mode 100644 index e33d5acd..00000000 --- a/src/main/java/attendance/command/impl/DangerQueryCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package attendance.command.impl; - -import attendance.command.Command; -import attendance.domain.Crew; -import attendance.service.AttendanceService; -import attendance.view.OutputView; -import java.util.List; - -public class DangerQueryCommand implements Command { - - private final AttendanceService service; - - public DangerQueryCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - List dangers = service.getDangers(); - - OutputView.printDangers(dangers); - } -} diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java deleted file mode 100644 index 42d568bd..00000000 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package attendance.command.impl; - -import static java.util.Locale.KOREA; - -import attendance.command.Command; -import attendance.constant.ErrorMessage; -import attendance.constant.Holiday; -import attendance.dto.ModificationResult; -import attendance.service.AttendanceService; -import attendance.util.InputParser; -import attendance.view.InputView; -import attendance.view.OutputView; -import camp.nextstep.edu.missionutils.DateTimes; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -public class ModificationCommand implements Command { - - private static final DateTimeFormatter DATETIME_FMT = - DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); - - private final AttendanceService service; - - public ModificationCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - LocalDate now = DateTimes.now().toLocalDate(); -// LocalDate now = LocalDate.of(2024, 12, 13); - if (isHoliday(now)) { - throw new IllegalArgumentException( - ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(now.format(DATETIME_FMT))); - } - - String name = InputParser.parseName(InputView.readModificationName()); - service.validateNameExists(name); - - LocalDate date = InputParser.parseDay(InputView.readModificationDay()); - service.validateModificationPossibleDate(now, date); - - LocalTime newTime = InputParser.parseTime(InputView.readModificationTime()); - service.validatePossibleTime(newTime); - - ModificationResult modificationResult = service.modifyRecord(name, date, newTime); - - OutputView.printModificationResult(modificationResult); - } - - private boolean isHoliday(LocalDate now) { - return !Holiday.from(now).equals(Holiday.NONE); - } -} diff --git a/src/main/java/attendance/command/impl/RecordQueryCommand.java b/src/main/java/attendance/command/impl/RecordQueryCommand.java deleted file mode 100644 index bb048d8d..00000000 --- a/src/main/java/attendance/command/impl/RecordQueryCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -package attendance.command.impl; - -import attendance.command.Command; -import attendance.dto.RecordQueryResult; -import attendance.service.AttendanceService; -import attendance.util.InputParser; -import attendance.view.InputView; -import attendance.view.OutputView; - -public class RecordQueryCommand implements Command { - - private final AttendanceService service; - - public RecordQueryCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - String name = InputParser.parseName(InputView.readName()); - service.validateNameExists(name); - - RecordQueryResult result = service.getAttendanceRecords(name); - - OutputView.printRecordQuery(result); - } -} diff --git a/src/main/java/attendance/constant/Check.java b/src/main/java/attendance/constant/Check.java deleted file mode 100644 index ecb232fb..00000000 --- a/src/main/java/attendance/constant/Check.java +++ /dev/null @@ -1,35 +0,0 @@ -package attendance.constant; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Arrays; - -public enum Check { - - ABSENCE(30, "결석"), - LATE(5, "지각"), - ATTENDANCE(0, "출석"), - ; - - private final int lateMinutes; - private final String name; - - Check(int lateMinutes, String name) { - this.lateMinutes = lateMinutes; - this.name = name; - } - - public static Check from(LocalDateTime dateTime) { - LocalTime time = dateTime.toLocalTime(); - LocalTime standardTime = Standard.from(dateTime.toLocalDate()).getStartTime(); - - return Arrays.stream(values()) - .filter(check -> time.isAfter(standardTime.plusMinutes(check.lateMinutes))) - .findFirst() - .orElse(ATTENDANCE); - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/attendance/constant/Danger.java b/src/main/java/attendance/constant/Danger.java deleted file mode 100644 index 72807ebf..00000000 --- a/src/main/java/attendance/constant/Danger.java +++ /dev/null @@ -1,31 +0,0 @@ -package attendance.constant; - -import java.util.Arrays; - -public enum Danger { - - EXPULSION(6, "제적"), - TALK(3, "면담"), - WARNING(2, "경고"), - NONE(0, ""), - ; - - private final int absenceCount; - private final String name; - - Danger(int absenceCount, String name) { - this.absenceCount = absenceCount; - this.name = name; - } - - public static Danger from(int count) { - return Arrays.stream(values()) - .filter(danger -> danger.absenceCount <= count) - .findFirst() - .orElse(NONE); - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/attendance/constant/ErrorMessage.java b/src/main/java/attendance/constant/ErrorMessage.java deleted file mode 100644 index 50d91258..00000000 --- a/src/main/java/attendance/constant/ErrorMessage.java +++ /dev/null @@ -1,24 +0,0 @@ -package attendance.constant; - -public enum ErrorMessage { - - FORMAT_ERROR("잘못된 형식을 입력하였습니다."), - NO_ATTENDANCE_DAY_ERROR("%s은 등교일이 아닙니다."), - NO_EXIST_NAME_ERROR("등록되지 않은 닉네임입니다."), - ALREADY_ATTENDANCE_ERROR("이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."), - NO_OPERATION_TIME_ERROR("캠퍼스 운영 시간에만 출석이 가능합니다."), - FUTURE_DAY_ERROR("아직 수정할 수 없습니다."), - NO_EXIST_ATTENDANCE_CHECK_RECORD("출석 기록이 없습니다."), - ; - - private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; - private final String errorMessage; - - ErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - public String getErrorMessage(Object... args) { - return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); - } -} diff --git a/src/main/java/attendance/constant/Holiday.java b/src/main/java/attendance/constant/Holiday.java deleted file mode 100644 index caf782d4..00000000 --- a/src/main/java/attendance/constant/Holiday.java +++ /dev/null @@ -1,37 +0,0 @@ -package attendance.constant; - -import java.time.DayOfWeek; -import java.time.LocalDate; - -public enum Holiday { - - SATURDAY(DayOfWeek.SATURDAY, LocalDate.of(1, 1, 1)), - SUNDAY(DayOfWeek.SATURDAY, LocalDate.of(1, 1, 1)), - CHRISTMAS(DayOfWeek.WEDNESDAY, LocalDate.of(2024, 12, 25)), - NONE(null, null) - ; - - private final DayOfWeek dayOfWeek; - private final LocalDate date; - - Holiday(DayOfWeek dayOfWeek, LocalDate date) { - this.dayOfWeek = dayOfWeek; - this.date = date; - } - - public static Holiday from(LocalDate date) { - if (date.getDayOfWeek() == DayOfWeek.SATURDAY) { - return SATURDAY; - } - - if (date.getDayOfWeek() == DayOfWeek.SUNDAY) { - return SUNDAY; - } - - if (date.isEqual(CHRISTMAS.date)) { - return CHRISTMAS; - } - - return NONE; - } -} diff --git a/src/main/java/attendance/constant/OperationTime.java b/src/main/java/attendance/constant/OperationTime.java deleted file mode 100644 index 28389521..00000000 --- a/src/main/java/attendance/constant/OperationTime.java +++ /dev/null @@ -1,20 +0,0 @@ -package attendance.constant; - -import java.time.LocalTime; - -public enum OperationTime { - - START(LocalTime.of(8,0)), - END(LocalTime.of(23,0)) - ; - - private final LocalTime time; - - OperationTime(LocalTime time) { - this.time = time; - } - - public LocalTime getTime() { - return time; - } -} diff --git a/src/main/java/attendance/constant/Standard.java b/src/main/java/attendance/constant/Standard.java deleted file mode 100644 index 63204248..00000000 --- a/src/main/java/attendance/constant/Standard.java +++ /dev/null @@ -1,38 +0,0 @@ -package attendance.constant; - -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalTime; - -public enum Standard { - - MONDAY(DayOfWeek.MONDAY, LocalTime.of(13,0), LocalTime.of(23,0)), - OTHER(null, LocalTime.of(10,0), LocalTime.of(23,0)), - ; - - private final DayOfWeek dayOfWeek; - private final LocalTime startTime; - private final LocalTime endTime; - - Standard(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) { - this.dayOfWeek = dayOfWeek; - this.startTime = startTime; - this.endTime = endTime; - } - - public static Standard from(LocalDate date) { - if (date.getDayOfWeek() == DayOfWeek.MONDAY) { - return MONDAY; - } - - return OTHER; - } - - public LocalTime getStartTime() { - return startTime; - } - - public LocalTime getEndTime() { - return endTime; - } -} diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java deleted file mode 100644 index d3e7fe8d..00000000 --- a/src/main/java/attendance/controller/AttendanceController.java +++ /dev/null @@ -1,52 +0,0 @@ -package attendance.controller; - -import attendance.command.MenuCommandRegistry; -import attendance.command.MenuOption; -import attendance.service.AttendanceService; -import attendance.util.InputParser; -import attendance.util.file.FileReader; -import attendance.view.InputView; -import camp.nextstep.edu.missionutils.DateTimes; -import java.io.IOException; -import java.time.LocalDate; -import java.util.List; -import java.util.Map; - -public class AttendanceController { - - private final MenuCommandRegistry registry; - private final AttendanceService service; - - public AttendanceController(MenuCommandRegistry registry, AttendanceService service) { - this.registry = registry; - this.service = service; - } - - public void run() throws IOException { - registerFileInfo(); - LocalDate now = DateTimes.now().toLocalDate(); -// LocalDate now = LocalDate.of(2024, 12, 13); - while (true) { - MenuOption option = readOption(now); - - if (option.equals(MenuOption.QUIT)) { - return; - } - - registry.execute(option); - } - } - - private void registerFileInfo() throws IOException { - FileReader fileReader = new FileReader("src/main/resources/attendances.csv"); - List readLines = fileReader.readLines(); - - Map> attendanceRecords = InputParser.getAttendanceRecords(readLines); - - service.registerFileInfo(attendanceRecords); - } - - private MenuOption readOption(LocalDate now) { - return InputParser.parseMenu(InputView.readMenuSelection(now)); - } -} diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java deleted file mode 100644 index 9fcdff6c..00000000 --- a/src/main/java/attendance/domain/Attendance.java +++ /dev/null @@ -1,62 +0,0 @@ -package attendance.domain; - -import attendance.constant.Check; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; - -public class Attendance implements Comparable { - - private LocalDateTime dateTime; - - private Attendance(LocalDateTime dateTime) { - this.dateTime = dateTime; - } - - public static Attendance from(LocalDateTime dateTime) { - return new Attendance(dateTime); - } - - public LocalDate getDate() { - return dateTime.toLocalDate(); - } - - public LocalTime getTime() { - return dateTime.toLocalTime(); - } - - public LocalTime modifyTime(LocalTime newTime) { - LocalTime oldTime = dateTime.toLocalTime(); - dateTime = LocalDateTime.of(dateTime.toLocalDate(), newTime); - return oldTime; - } - - @Override - public int compareTo(Attendance o) { - if (dateTime.toLocalDate().isBefore(o.dateTime.toLocalDate())){ - return -1; - } - - if (dateTime.toLocalDate().isEqual(o.dateTime.toLocalDate())){ - return 0; - } - - return 1; - } - - public LocalDateTime getDateTime() { - return dateTime; - } - - public boolean isAttendance() { - return Check.from(this.dateTime).equals(Check.ATTENDANCE); - } - - public boolean isLate() { - return Check.from(this.dateTime).equals(Check.LATE); - } - - public boolean isAbsence() { - return Check.from(this.dateTime).equals(Check.ABSENCE); - } -} diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java deleted file mode 100644 index be40c711..00000000 --- a/src/main/java/attendance/domain/Crew.java +++ /dev/null @@ -1,109 +0,0 @@ -package attendance.domain; - -import attendance.constant.Check; -import attendance.constant.Danger; -import attendance.constant.ErrorMessage; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class Crew { - - private final String name; - private final List attendances; - - private Crew(String name) { - this.name = name; - this.attendances = new ArrayList<>(); - } - - public static Crew from(String crewName) { - return new Crew(crewName); - } - - public Check addAttendanceRecord(LocalDateTime dateTime) { - attendances.add(Attendance.from(dateTime)); - return Check.from(dateTime); - } - - public String getName() { - return name; - } - - @Override - public boolean equals(Object object) { - if (object == null || getClass() != object.getClass()) { - return false; - } - Crew crew = (Crew) object; - return Objects.equals(name, crew.name); - } - - @Override - public int hashCode() { - return Objects.hashCode(name); - } - - public boolean containsDate(LocalDate now) { - return attendances.stream() - .anyMatch(attendance -> attendance.getDate().isEqual(now)); - } - - public LocalTime modifyRecord(LocalDate date, LocalTime time) { - Attendance record = attendances.stream() - .filter(attendance -> attendance.getDate().isEqual(date)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException( - ErrorMessage.NO_EXIST_ATTENDANCE_CHECK_RECORD.getErrorMessage())); - return record.modifyTime(time); - } - - public List getAttendances() { - return attendances; - } - - public int getAttendanceCount() { - int count = 0; - for (Attendance attendance : attendances) { - if (attendance.isAttendance()) { - count++; - } - } - return count; - } - - public int getLateCount() { - int count = 0; - for (Attendance attendance : attendances) { - if (attendance.isLate()) { - count++; - } - } - return count; - } - - public int getAbsenceCount() { - int count = 0; - for (Attendance attendance : attendances) { - if (attendance.isAbsence()) { - count++; - } - } - return count; - } - - public boolean isDangerCrew() { - return getAbsenceCount() + getLateCount() / 3 >= 2; - } - - public String getDangerState() { - return Danger.from(getAbsenceCount() + getLateCount() / 3).getName(); - } - - public int getAbsenceLateCount() { - return getAbsenceCount() + getLateCount() / 3; - } -} diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java deleted file mode 100644 index 622038b0..00000000 --- a/src/main/java/attendance/domain/Crews.java +++ /dev/null @@ -1,89 +0,0 @@ -package attendance.domain; - -import static java.util.Locale.KOREA; - -import attendance.constant.ErrorMessage; -import attendance.constant.Holiday; -import camp.nextstep.edu.missionutils.DateTimes; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class Crews { - - private static final DateTimeFormatter DATETIME_FMT = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); - - private final List crews; - - private Crews() { - crews = new ArrayList<>(); - } - - public static Crews newInstance() { - return new Crews(); - } - - public void registerRecord(String crewName, List attendanceRecords) { - Map dateTimes = new HashMap<>(); - setRecords(attendanceRecords, dateTimes); - - Crew crew = setCrew(crewName, dateTimes); - crews.add(crew); - } - - private void setRecords(List attendanceRecords, Map dateTimes) { - for (String attendanceRecord : attendanceRecords) { - LocalDateTime localDateTime = LocalDateTime.parse(attendanceRecord, DATETIME_FMT); - LocalDate localDate = localDateTime.toLocalDate(); - - dateTimes.put(localDate, localDateTime); - } - } - - private Crew setCrew(String crewName, Map dateTimes) { - Crew crew = Crew.from(crewName); - LocalDate now = DateTimes.now().toLocalDate(); -// LocalDate now = LocalDate.of(2024, 12, 13); - for (LocalDate date = LocalDate.of(2024, 12, 1); date.isBefore(now); date = date.plusDays(1)) { - if (isHoliDay(date)) { - continue; - } - - if (dateTimes.containsKey(date)) { // 해당 날짜 출석 기록이 있으면 - crew.addAttendanceRecord(dateTimes.get(date)); - continue; - } - - crew.addAttendanceRecord(LocalDateTime.of(date, LocalTime.of(23, 59))); - } - return crew; - } - - private static boolean isHoliDay(LocalDate date) { - return !Holiday.from(date).equals(Holiday.NONE); - } - - public Crew getCrew(String findName) { - Crew findCrew = Crew.from(findName); - return crews.stream() - .filter(crew -> crew.equals(findCrew)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_NAME_ERROR.getErrorMessage())); - } - - public List getDangers() { - List dangers = new ArrayList<>(); - for (Crew crew : crews) { - if (crew.isDangerCrew()) { - dangers.add(crew); - } - } - return dangers; - } -} diff --git a/src/main/java/attendance/dto/CheckResult.java b/src/main/java/attendance/dto/CheckResult.java deleted file mode 100644 index b8e0b8a5..00000000 --- a/src/main/java/attendance/dto/CheckResult.java +++ /dev/null @@ -1,11 +0,0 @@ -package attendance.dto; - -import attendance.constant.Check; -import java.time.LocalDateTime; - -public record CheckResult(LocalDateTime dateTime, Check check) { - - public static CheckResult of(LocalDateTime dateTime, Check check) { - return new CheckResult(dateTime, check); - } -} diff --git a/src/main/java/attendance/dto/ModificationResult.java b/src/main/java/attendance/dto/ModificationResult.java deleted file mode 100644 index cfcd1b48..00000000 --- a/src/main/java/attendance/dto/ModificationResult.java +++ /dev/null @@ -1,11 +0,0 @@ -package attendance.dto; - -import java.time.LocalDate; -import java.time.LocalTime; - -public record ModificationResult(LocalDate date, LocalTime oldTime, LocalTime newTime) { - - public static ModificationResult of(LocalDate date, LocalTime oldTime, LocalTime newTime) { - return new ModificationResult(date, oldTime, newTime); - } -} diff --git a/src/main/java/attendance/dto/RecordQueryResult.java b/src/main/java/attendance/dto/RecordQueryResult.java deleted file mode 100644 index 9c38b43d..00000000 --- a/src/main/java/attendance/dto/RecordQueryResult.java +++ /dev/null @@ -1,9 +0,0 @@ -package attendance.dto; - -import attendance.domain.Crew; - -public record RecordQueryResult(Crew crew) { - public static RecordQueryResult from(Crew crew) { - return new RecordQueryResult(crew); - } -} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java deleted file mode 100644 index 728c2122..00000000 --- a/src/main/java/attendance/service/AttendanceService.java +++ /dev/null @@ -1,72 +0,0 @@ -package attendance.service; - -import attendance.constant.Check; -import attendance.constant.ErrorMessage; -import attendance.constant.OperationTime; -import attendance.domain.Crew; -import attendance.domain.Crews; -import attendance.dto.CheckResult; -import attendance.dto.ModificationResult; -import attendance.dto.RecordQueryResult; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.List; -import java.util.Map; - -public class AttendanceService { - - private Crews crews; - - public void registerFileInfo(Map> attendanceRecords) { - crews = Crews.newInstance(); - for (String crewName : attendanceRecords.keySet()) { - crews.registerRecord(crewName, attendanceRecords.get(crewName)); - } - } - - public void validateCheckPossible(String name, LocalDate now) { - Crew crew = crews.getCrew(name); - if (crew.containsDate(now)) { - throw new IllegalArgumentException(ErrorMessage.ALREADY_ATTENDANCE_ERROR.getErrorMessage()); - } - } - - public CheckResult checkAttendance(String name, LocalDate now, LocalTime time) { - Crew crew = crews.getCrew(name); - Check check = crew.addAttendanceRecord(LocalDateTime.of(now, time)); - return CheckResult.of(LocalDateTime.of(now, time), check); - } - - public void validatePossibleTime(LocalTime time) { - if (time.isBefore(OperationTime.START.getTime()) || time.isAfter(OperationTime.END.getTime())) { - throw new IllegalArgumentException(ErrorMessage.NO_OPERATION_TIME_ERROR.getErrorMessage()); - } - } - - public void validateNameExists(String name) { - crews.getCrew(name); - } - - public void validateModificationPossibleDate(LocalDate now, LocalDate date) { - if (date.isAfter(now)) { - throw new IllegalArgumentException(ErrorMessage.FUTURE_DAY_ERROR.getErrorMessage()); - } - } - - public ModificationResult modifyRecord(String name, LocalDate date, LocalTime newTime) { - Crew crew = crews.getCrew(name); - LocalTime oldTime = crew.modifyRecord(date, newTime); - - return ModificationResult.of(date, oldTime, newTime); - } - - public RecordQueryResult getAttendanceRecords(String name) { - Crew crew = crews.getCrew(name); - return RecordQueryResult.from(crew); - } - - public List getDangers() { - return crews.getDangers(); - } -} diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java deleted file mode 100644 index 0068ec88..00000000 --- a/src/main/java/attendance/util/InputParser.java +++ /dev/null @@ -1,58 +0,0 @@ -package attendance.util; - -import attendance.command.MenuOption; -import camp.nextstep.edu.missionutils.DateTimes; -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public final class InputParser { - - private static final String DELIMITER = ","; - - private InputParser() { - } - - public static MenuOption parseMenu(String rawInput) { - return MenuOption.from(rawInput.strip()); - } - - public static String parseName(String rawInput) { - return rawInput.strip(); - } - - public static LocalTime parseTime(String rawInput) { - rawInput = rawInput.strip(); - - Validator.validateTimeFormat(rawInput); - - return LocalTime.parse(rawInput); - } - - public static LocalDate parseDay(String rawInput) { - rawInput = rawInput.strip(); - - Validator.validateDayFormat(rawInput); - - LocalDate now = DateTimes.now().toLocalDate(); -// return LocalDate.of(2024, 12, NumberConvertor.convertToNumber(rawInput)); - return LocalDate.of(now.getYear(), now.getMonthValue(), NumberConvertor.convertToNumber(rawInput)); - } - - public static Map> getAttendanceRecords(List readLines) { - readLines.removeFirst(); - Map> attendanceRecords = new HashMap<>(); - for (String readLine : readLines) { - String[] split = readLine.split(DELIMITER); - if (attendanceRecords.containsKey(split[0])) { - attendanceRecords.get(split[0]).add(split[1]); - continue; - } - attendanceRecords.put(split[0], new ArrayList<>(List.of(split[1]))); - } - return attendanceRecords; - } -} diff --git a/src/main/java/attendance/util/NumberConvertor.java b/src/main/java/attendance/util/NumberConvertor.java deleted file mode 100644 index b68a85f7..00000000 --- a/src/main/java/attendance/util/NumberConvertor.java +++ /dev/null @@ -1,14 +0,0 @@ -package attendance.util; - -import attendance.constant.ErrorMessage; - -public final class NumberConvertor { - - public static int convertToNumber(String input) { - try { - return Integer.parseInt(input); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); - } - } -} diff --git a/src/main/java/attendance/util/Validator.java b/src/main/java/attendance/util/Validator.java deleted file mode 100644 index 2592fa9b..00000000 --- a/src/main/java/attendance/util/Validator.java +++ /dev/null @@ -1,23 +0,0 @@ -package attendance.util; - -import attendance.constant.ErrorMessage; - -public final class Validator { - - private static final String DAY_FORMAT = "^[1-9]|[1-2]\\d|3[0-1]$"; - private static final String TIME_FORMAT = "([01]\\d|2[0-3]):[0-5]\\d"; - - private Validator() {} - - public static void validateTimeFormat(String input) { - if (!input.matches(TIME_FORMAT)) { - throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); - } - } - - public static void validateDayFormat(String input) { - if (!input.matches(DAY_FORMAT)) { - throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); - } - } -} diff --git a/src/main/java/attendance/util/file/FileReader.java b/src/main/java/attendance/util/file/FileReader.java deleted file mode 100644 index 621174fb..00000000 --- a/src/main/java/attendance/util/file/FileReader.java +++ /dev/null @@ -1,32 +0,0 @@ -package attendance.util.file; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.BufferedReader; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class FileReader { - - private static final int BUFFER_SIZE = 8192; - - private final java.io.FileReader fr; - - public FileReader(String fileName) throws IOException { - fr = new java.io.FileReader(fileName, UTF_8); - } - - public List readLines() throws IOException { - List contents = new ArrayList<>(); - BufferedReader br = new BufferedReader(fr, BUFFER_SIZE); - - String line; - while ((line = br.readLine()) != null) { - contents.add(line); - } - br.close(); - - return new ArrayList<>(contents); - } -} diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java deleted file mode 100644 index 46fcc9f0..00000000 --- a/src/main/java/attendance/view/InputView.java +++ /dev/null @@ -1,48 +0,0 @@ -package attendance.view; - -import static java.util.Locale.KOREA; - -import camp.nextstep.edu.missionutils.Console; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; - -public class InputView { - - private static final DateTimeFormatter DATETIME_FMT = - DateTimeFormatter.ofPattern("MM월 dd일 E요일", KOREA); - - public static String readMenuSelection(LocalDate now) { - System.out.printf("\n오늘은 %s입니다. 기능을 선택해 주세요.\n" - + "1. 출석 확인\n" - + "2. 출석 수정\n" - + "3. 크루별 출석 기록 확인\n" - + "4. 제적 위험자 확인\n" - + "Q. 종료\n", now.format(DATETIME_FMT)); - return Console.readLine(); - } - - public static String readName() { - System.out.println("\n닉네임을 입력해 주세요."); - return Console.readLine(); - } - - public static String readTime() { - System.out.println("등교 시간을 입력해 주세요."); - return Console.readLine(); - } - - public static String readModificationName() { - System.out.println("\n닉네임을 입력해 주세요."); - return Console.readLine(); - } - - public static String readModificationDay() { - System.out.println("수정하려는 날짜(일)를 입력해 주세요."); - return Console.readLine(); - } - - public static String readModificationTime() { - System.out.println("언제로 변경하겠습니까?"); - return Console.readLine(); - } -} diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java deleted file mode 100644 index 2bfe4719..00000000 --- a/src/main/java/attendance/view/OutputView.java +++ /dev/null @@ -1,99 +0,0 @@ -package attendance.view; - -import static java.util.Locale.KOREA; - -import attendance.constant.Check; -import attendance.domain.Attendance; -import attendance.domain.Crew; -import attendance.dto.CheckResult; -import attendance.dto.ModificationResult; -import attendance.dto.RecordQueryResult; -import camp.nextstep.edu.missionutils.DateTimes; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.util.Comparator; -import java.util.List; - -public class OutputView { - - private static final DateTimeFormatter DATETIME_FMT = - DateTimeFormatter.ofPattern("M월 dd일 E요일 HH:mm", KOREA); - private static final DateTimeFormatter DATE_FMT = - DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); - private static final DateTimeFormatter TIME_FMT = - DateTimeFormatter.ofPattern("HH:mm", KOREA); - - private OutputView() { - } - - public static void printCheckResult(CheckResult checkResult) { - Check check = checkResult.check(); - LocalDateTime dateTime = checkResult.dateTime(); - - System.out.printf("\n%s (%s)\n", dateTime.format(DATETIME_FMT), check.getName()); - } - - public static void printModificationResult(ModificationResult modificationResult) { - LocalDate date = modificationResult.date(); - LocalTime oldTime = modificationResult.oldTime(); - LocalTime newTime = modificationResult.newTime(); - - System.out.printf("\n%s %s (%s) -> %s (%s) 수정 완료!\n", - date.format(DATE_FMT), - oldTime.format(TIME_FMT), Check.from(LocalDateTime.of(date, oldTime)).getName(), - newTime.format(TIME_FMT), Check.from(LocalDateTime.of(date, newTime)).getName() - ); - } - - public static void printRecordQuery(RecordQueryResult queryResult) { - Crew crew = queryResult.crew(); - List attendances = crew.getAttendances(); - attendances.sort(null); - - System.out.printf("이번 달 %s의 출석 기록입니다.\n", crew.getName()); - for (Attendance attendance : attendances) { - LocalDateTime dateTime = attendance.getDateTime(); - LocalDate date = dateTime.toLocalDate(); - - if (date.isEqual(DateTimes.now().toLocalDate())) { - continue; - } - - if (!dateTime.toLocalTime().isBefore(LocalTime.of(23, 59))) { - System.out.printf("%s --:-- (%s)\n", date.format(DATE_FMT), - Check.from(attendance.getDateTime()).getName()); - continue; - } - System.out.printf("%s (%s)\n", dateTime.format(DATETIME_FMT), - Check.from(attendance.getDateTime()).getName()); - } - System.out.println(); - - System.out.printf("출석: %d회\n", crew.getAttendanceCount()); - System.out.printf("지각: %d회\n", crew.getLateCount()); - System.out.printf("결석: %d회\n", crew.getAbsenceCount()); - - if (crew.isDangerCrew()) { - System.out.printf("\n%s 대상자입니다.\n", crew.getDangerState()); - } - } - - public static void printDangers(List dangers) { - System.out.println("\n제적 위험자 조회 결과"); - dangers.sort( - Comparator.comparingInt(Crew::getAbsenceLateCount).reversed() - .thenComparing(Comparator.comparingInt(Crew::getAbsenceCount).reversed()) - .thenComparing(Comparator.comparingInt(Crew::getLateCount).reversed()) - .thenComparing(Crew::getName) - ); - - - for (Crew danger : dangers) { - System.out.printf("- %s: 결석 %d회, 지각 %d회 (%s)\n", - danger.getName(), danger.getAbsenceCount(), danger.getLateCount(), danger.getDangerState()); - } - System.out.println(); - } -} From d608838f109804d5a17fd4ebc1b44038f0c541a1 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 16:27:48 +0900 Subject: [PATCH 13/30] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 76 +++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 146fa3ce..76c7d0be 100644 --- a/README.md +++ b/README.md @@ -3,72 +3,72 @@ ## 기능 구현 목록 > 기능 작동 순서대로 작성 -1. 파일 입력 및 크루별 출석 기록 저장 - 1. 1일부터 어제까지 순회하면서 기록 저장 - - 주말 및 공휴일은 패스 - - 기록이 없으면 결석으로 처리(시간은 23:59) -2. 오늘 날짜 출력 및 기능 선택 입력 및 검증 - 1. 오늘 날짜 출력 - 2. 기능 선택 입력 및 검증 - - 예외 사항 - - 1,2,3,4,Q가 아닌 경우 - - 잘못된 형식을 입력하였습니다. -3. 출석 확인 +1. 파일 입력 및 출석 정보 저장 +2. 기능 선택 및 검증 + - 출력문 + - 날짜 요일 - 예외 사항 - - 등교일이 아닌 경우 - - 12월 %d일 %s요일은 등교일이 아닙니다. - 1. 닉네임 입력 및 검증 + - 1,2,3,4,Q가 아닌 경우 + - 잘못된 형식을 입력하였습니다. +3. 출석확인 + 1. 오늘이 등교일이 아니면 예외 발생 + - %s은 등교일이 아닙니다. + 2. 닉네임 입력 및 검증 - 예외 사항 - 등록되지 않은 닉네임인 경우 - 등록되지 않은 닉네임입니다. - 이미 출석을 하였는데 다시 출석 확인을 하는 경우 - 이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요. - 2. 등교 시간 입력 + 3. 등교 시간 입력 및 검증 - 예외 사항 - - 잘못된 형식인 경우 + - 시간을 잘못된 형식으로 입력한 경우 - 잘못된 형식을 입력하였습니다. - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 - 캠퍼스 운영 시간에만 출석이 가능합니다. - 3. 출석 체크 - 4. 결과 출력 - - 날짜, 요일, 시간, 출석체크결과 + 4. 출석 정보 저장 + 5. 확인 결과 출력 + - 월, 일, 요일, 시간, 출석 상태 4. 출석 수정 1. 닉네임 입력 및 검증 - 예외 사항 - 등록되지 않은 닉네임인 경우 - 등록되지 않은 닉네임입니다. - 2. 수정하려는 날짜 입력 + 2. 날짜 입력 및 검증 - 예외 사항 - - 잘못된 형식인 경우 + - 잘못된 형식을 입력한 경우 - 잘못된 형식을 입력하였습니다. + - 등교일이 아니면 예외 발생 + - %s은 등교일이 아닙니다. - 미래 날짜로 출석을 수정하는 경우 - 아직 수정할 수 없습니다. - 3. 수정하려는 시간 입력 + 3. 시각 입력 및 검증 - 예외 사항 - - 잘못된 형식인 경우 + - 시간을 잘못된 형식으로 입력한 경우 - 잘못된 형식을 입력하였습니다. - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 - 캠퍼스 운영 시간에만 출석이 가능합니다. - 4. 출석 내용 수정 - 5. 결과 출력 - - 날짜, 요일, 변경전 시간, 변경전 상태, 변경후 시간, 변경후 상태 + 4. 출석 정보 수정 + 5. 수정 결과 출력 + - 월, 일, 요일 + - 이전 시간, 이전 출석 상태 + - 변경 시간, 변경 출석 상태 5. 크루별 출석 기록 확인 - 1. 닉네임 입력 + 1. 닉네임 입력 및 검증 - 예외 사항 - 등록되지 않은 닉네임인 경우 - 등록되지 않은 닉네임입니다. 2. 출석 기록 출력 - - 등교일만 출력 - - 날짜, 요일, 시간, 상태 - - 시간이 23:59 이면 결석으로 처리 + - 전날까지의 출석 기록 출력 + - 월, 일, 요일, 시간, 출석 상태 + - 아예 안온날은 시간을 --:-- 으로 출력 - 출석, 지각, 결석 횟수 - - 제적 위험자인 경우! -> 어떤 대상자인지 + - 위험 대상자라면 해당 상태 출력 6. 제적 위험자 확인 - 1. 제적 위험자 출력 - - 이름, 결석 횟수, 지각 횟수, 어떤 대상자인지 - - 정렬 기준 - 1. 결석+지각3회 내림차순 - 2. 결석 내림차순 - 3. 지각 내림차순 - 4. 이름 오름차순 + - 전날까지의 기록으로 제적 위험자 파악 + - 이름, 결석 횟수, 지각 횟수, 위험 상태 + - 정렬 기준 + 1. 결석 + 지각/3 내림 + 2. 결석 내림 + 3. 지각 내림 + 4. 이름 오름 7. 종료 \ No newline at end of file From 9df6dbc5a740ce10e521749a785afcff06ee8bda Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 16:38:00 +0900 Subject: [PATCH 14/30] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 76c7d0be..824c6312 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,22 @@ - 출력문 - 날짜 요일 - 예외 사항 - - 1,2,3,4,Q가 아닌 경우 + - 1,2,3,4,Q가 아닌 경우 -> Validator - 잘못된 형식을 입력하였습니다. 3. 출석확인 - 1. 오늘이 등교일이 아니면 예외 발생 + 1. 오늘이 등교일이 아니면 예외 발생 -> Service - %s은 등교일이 아닙니다. 2. 닉네임 입력 및 검증 - 예외 사항 - - 등록되지 않은 닉네임인 경우 + - 등록되지 않은 닉네임인 경우 -> Crews - 등록되지 않은 닉네임입니다. - - 이미 출석을 하였는데 다시 출석 확인을 하는 경우 + - 이미 출석을 하였는데 다시 출석 확인을 하는 경우 -> Attendance - 이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요. 3. 등교 시간 입력 및 검증 - 예외 사항 - - 시간을 잘못된 형식으로 입력한 경우 + - 시간을 잘못된 형식으로 입력한 경우 -> Validator - 잘못된 형식을 입력하였습니다. - - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 + - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 -> Service - 캠퍼스 운영 시간에만 출석이 가능합니다. 4. 출석 정보 저장 5. 확인 결과 출력 @@ -31,21 +31,21 @@ 4. 출석 수정 1. 닉네임 입력 및 검증 - 예외 사항 - - 등록되지 않은 닉네임인 경우 + - 등록되지 않은 닉네임인 경우 -> Crews - 등록되지 않은 닉네임입니다. 2. 날짜 입력 및 검증 - 예외 사항 - - 잘못된 형식을 입력한 경우 + - 잘못된 형식을 입력한 경우 -> Validator - 잘못된 형식을 입력하였습니다. - - 등교일이 아니면 예외 발생 + - 등교일이 아니면 예외 발생 -> Service - %s은 등교일이 아닙니다. - - 미래 날짜로 출석을 수정하는 경우 + - 미래 날짜로 출석을 수정하는 경우 -> Service - 아직 수정할 수 없습니다. 3. 시각 입력 및 검증 - 예외 사항 - - 시간을 잘못된 형식으로 입력한 경우 + - 시간을 잘못된 형식으로 입력한 경우 -> Validator - 잘못된 형식을 입력하였습니다. - - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 + - 등교 시간이 캠퍼스 운영 시간이 아닌 경우 -> Service - 캠퍼스 운영 시간에만 출석이 가능합니다. 4. 출석 정보 수정 5. 수정 결과 출력 @@ -55,7 +55,7 @@ 5. 크루별 출석 기록 확인 1. 닉네임 입력 및 검증 - 예외 사항 - - 등록되지 않은 닉네임인 경우 + - 등록되지 않은 닉네임인 경우 -> Crews - 등록되지 않은 닉네임입니다. 2. 출석 기록 출력 - 전날까지의 출석 기록 출력 From 9a3971ce2ac4121e1db86312ee958482a6456e9b Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:30:59 +0900 Subject: [PATCH 15/30] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=EC=B6=9C=EC=84=9D=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/Application.java | 14 +++++- src/main/java/attendance/command/Command.java | 5 +++ .../command/MenuCommandRegistry.java | 30 +++++++++++++ .../java/attendance/command/MenuOption.java | 25 +++++++++++ .../command/impl/FeatureACommand.java | 18 ++++++++ .../command/impl/FeatureBCommand.java | 18 ++++++++ .../command/impl/FeatureCCommand.java | 18 ++++++++ .../command/impl/FeatureDCommand.java | 18 ++++++++ .../attendance/constant/AttendanceState.java | 34 ++++++++++++++ src/main/java/attendance/constant/Danger.java | 31 +++++++++++++ .../attendance/constant/ErrorMessage.java | 23 ++++++++++ .../java/attendance/constant/Holiday.java | 18 ++++++++ .../java/attendance/constant/Standard.java | 31 +++++++++++++ .../controller/AttendanceController.java | 45 +++++++++++++++++++ .../java/attendance/domain/Attendance.java | 42 +++++++++++++++++ src/main/java/attendance/domain/Crew.java | 20 +++++++++ src/main/java/attendance/domain/Crews.java | 25 +++++++++++ .../attendance/service/AttendanceService.java | 42 +++++++++++++++++ .../java/attendance/util/InputParser.java | 45 +++++++++++++++++++ .../java/attendance/util/NumberConvertor.java | 14 ++++++ src/main/java/attendance/util/Validator.java | 23 ++++++++++ .../java/attendance/util/file/FileReader.java | 32 +++++++++++++ src/main/java/attendance/view/InputView.java | 11 +++++ src/main/java/attendance/view/OutputView.java | 25 +++++++++++ 24 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 src/main/java/attendance/command/Command.java create mode 100644 src/main/java/attendance/command/MenuCommandRegistry.java create mode 100644 src/main/java/attendance/command/MenuOption.java create mode 100644 src/main/java/attendance/command/impl/FeatureACommand.java create mode 100644 src/main/java/attendance/command/impl/FeatureBCommand.java create mode 100644 src/main/java/attendance/command/impl/FeatureCCommand.java create mode 100644 src/main/java/attendance/command/impl/FeatureDCommand.java create mode 100644 src/main/java/attendance/constant/AttendanceState.java create mode 100644 src/main/java/attendance/constant/Danger.java create mode 100644 src/main/java/attendance/constant/ErrorMessage.java create mode 100644 src/main/java/attendance/constant/Holiday.java create mode 100644 src/main/java/attendance/constant/Standard.java create mode 100644 src/main/java/attendance/controller/AttendanceController.java create mode 100644 src/main/java/attendance/domain/Attendance.java create mode 100644 src/main/java/attendance/domain/Crew.java create mode 100644 src/main/java/attendance/domain/Crews.java create mode 100644 src/main/java/attendance/service/AttendanceService.java create mode 100644 src/main/java/attendance/util/InputParser.java create mode 100644 src/main/java/attendance/util/NumberConvertor.java create mode 100644 src/main/java/attendance/util/Validator.java create mode 100644 src/main/java/attendance/util/file/FileReader.java create mode 100644 src/main/java/attendance/view/InputView.java create mode 100644 src/main/java/attendance/view/OutputView.java diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index 759aa3ae..1097af77 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -1,7 +1,19 @@ package attendance; +import attendance.command.MenuCommandRegistry; +import attendance.controller.AttendanceController; +import attendance.service.AttendanceService; +import java.io.IOException; + public class Application { public static void main(String[] args) { - + AttendanceService service = new AttendanceService(); + MenuCommandRegistry registry = MenuCommandRegistry.from(service); + AttendanceController controller = new AttendanceController(registry, service); + try { + controller.run(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/attendance/command/Command.java b/src/main/java/attendance/command/Command.java new file mode 100644 index 00000000..cee363fe --- /dev/null +++ b/src/main/java/attendance/command/Command.java @@ -0,0 +1,5 @@ +package attendance.command; + +public interface Command { + void execute(); +} diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java new file mode 100644 index 00000000..833cfb74 --- /dev/null +++ b/src/main/java/attendance/command/MenuCommandRegistry.java @@ -0,0 +1,30 @@ +package attendance.command; + +import attendance.command.impl.FeatureACommand; +import attendance.command.impl.FeatureBCommand; +import attendance.command.impl.FeatureCCommand; +import attendance.command.impl.FeatureDCommand; +import attendance.service.AttendanceService; +import java.util.EnumMap; + +public class MenuCommandRegistry { + + private final EnumMap commands; + + private MenuCommandRegistry(EnumMap commands) { + this.commands = commands; + } + + public static MenuCommandRegistry from(AttendanceService service) { + EnumMap map = new EnumMap<>(MenuOption.class); + map.put(MenuOption.A, new FeatureACommand(service)); + map.put(MenuOption.B, new FeatureBCommand(service)); + map.put(MenuOption.C, new FeatureCCommand(service)); + map.put(MenuOption.D, new FeatureDCommand(service)); + return new MenuCommandRegistry(map); + } + + public void execute(MenuOption option) { + commands.get(option).execute(); + } +} diff --git a/src/main/java/attendance/command/MenuOption.java b/src/main/java/attendance/command/MenuOption.java new file mode 100644 index 00000000..6e6a9bd5 --- /dev/null +++ b/src/main/java/attendance/command/MenuOption.java @@ -0,0 +1,25 @@ +package attendance.command; + +import attendance.constant.ErrorMessage; +import java.util.Arrays; + +public enum MenuOption { + A("1"), + B("2"), + C("3"), + D("4"), + QUIT("Q"); + + private final String command; + + MenuOption(String command) { + this.command = command; + } + + public static MenuOption from(String command) { + return Arrays.stream(values()) + .filter(opt -> opt.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } +} diff --git a/src/main/java/attendance/command/impl/FeatureACommand.java b/src/main/java/attendance/command/impl/FeatureACommand.java new file mode 100644 index 00000000..1fd041ad --- /dev/null +++ b/src/main/java/attendance/command/impl/FeatureACommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class FeatureACommand implements Command { + + private final AttendanceService service; + + public FeatureACommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/command/impl/FeatureBCommand.java b/src/main/java/attendance/command/impl/FeatureBCommand.java new file mode 100644 index 00000000..6a02a9f3 --- /dev/null +++ b/src/main/java/attendance/command/impl/FeatureBCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class FeatureBCommand implements Command { + + private final AttendanceService service; + + public FeatureBCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/command/impl/FeatureCCommand.java b/src/main/java/attendance/command/impl/FeatureCCommand.java new file mode 100644 index 00000000..19669f66 --- /dev/null +++ b/src/main/java/attendance/command/impl/FeatureCCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class FeatureCCommand implements Command { + + private final AttendanceService service; + + public FeatureCCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/command/impl/FeatureDCommand.java b/src/main/java/attendance/command/impl/FeatureDCommand.java new file mode 100644 index 00000000..5ca410d5 --- /dev/null +++ b/src/main/java/attendance/command/impl/FeatureDCommand.java @@ -0,0 +1,18 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; + +public class FeatureDCommand implements Command { + + private final AttendanceService service; + + public FeatureDCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/attendance/constant/AttendanceState.java b/src/main/java/attendance/constant/AttendanceState.java new file mode 100644 index 00000000..a908e019 --- /dev/null +++ b/src/main/java/attendance/constant/AttendanceState.java @@ -0,0 +1,34 @@ +package attendance.constant; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Arrays; + +public enum AttendanceState { + + ABSENCE(30, "제적"), + LATE(5, "면담"), + ATTENDANCE(0, "출석"), + ; + + private final int lateMinutes; + private final String name; + + AttendanceState(int lateMinutes, String name) { + this.lateMinutes = lateMinutes; + this.name = name; + } + + public static AttendanceState from(LocalDate date, LocalTime time) { + Standard standard = Standard.from(date); + + return Arrays.stream(values()) + .filter(danger -> standard.getStartTime().plusMinutes(danger.lateMinutes).isBefore(time)) + .findFirst() + .orElse(ATTENDANCE); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/attendance/constant/Danger.java b/src/main/java/attendance/constant/Danger.java new file mode 100644 index 00000000..cd1ca99f --- /dev/null +++ b/src/main/java/attendance/constant/Danger.java @@ -0,0 +1,31 @@ +package attendance.constant; + +import java.util.Arrays; + +public enum Danger { + + OUT(6, "제적"), + INTERVIEW(3, "면담"), + WARNING(2, "경고"), + NONE(0, ""), + ; + + private final int absenceCount; + private final String name; + + Danger(int absenceCount, String name) { + this.absenceCount = absenceCount; + this.name = name; + } + + public static Danger from(int num) { + return Arrays.stream(values()) + .filter(danger -> danger.absenceCount <= num) + .findFirst() + .orElse(NONE); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/attendance/constant/ErrorMessage.java b/src/main/java/attendance/constant/ErrorMessage.java new file mode 100644 index 00000000..d47383ff --- /dev/null +++ b/src/main/java/attendance/constant/ErrorMessage.java @@ -0,0 +1,23 @@ +package attendance.constant; + +public enum ErrorMessage { + + FORMAT_ERROR("잘못된 형식을 입력하였습니다."), + NO_ATTENDANCE_DAY_ERROR("%s은 등교일이 아닙니다."), + NO_EXIST_NAME_ERROR("등록되지 않은 닉네임입니다."), + ALREADY_ATTENDANCE_ERROR("이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."), + NO_OPERATION_TIME_ERROR("캠퍼스 운영 시간에만 출석이 가능합니다."), + FUTURE_DATE_ERROR("아직 수정할 수 없습니다."), + ; + + private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage(Object... args) { + return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); + } +} diff --git a/src/main/java/attendance/constant/Holiday.java b/src/main/java/attendance/constant/Holiday.java new file mode 100644 index 00000000..d1842cbb --- /dev/null +++ b/src/main/java/attendance/constant/Holiday.java @@ -0,0 +1,18 @@ +package attendance.constant; + +import java.time.DayOfWeek; +import java.time.LocalDate; + +public enum Holiday { + + HOLI_DAY, + NONE; + + public static Holiday from(LocalDate date) { + if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY) || date.getDayOfWeek().equals(DayOfWeek.SUNDAY) || date.isEqual(LocalDate.of(2024,12,25))) { + return HOLI_DAY; + } + + return NONE; + } +} diff --git a/src/main/java/attendance/constant/Standard.java b/src/main/java/attendance/constant/Standard.java new file mode 100644 index 00000000..0954bad9 --- /dev/null +++ b/src/main/java/attendance/constant/Standard.java @@ -0,0 +1,31 @@ +package attendance.constant; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; + +public enum Standard { + + MONDAY(LocalTime.of(13,0)), + OTHERS(LocalTime.of(10,0)), + ; + + private final LocalTime startTime; + + Standard(LocalTime startTime) { + this.startTime = startTime; + } + + public static Standard from(LocalDate date) { + + if (date.getDayOfWeek().equals(DayOfWeek.MONDAY) && Holiday.from(date).equals(Holiday.NONE)) { + return MONDAY; + } + + return OTHERS; + } + + public LocalTime getStartTime() { + return startTime; + } +} diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java new file mode 100644 index 00000000..e9df55be --- /dev/null +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -0,0 +1,45 @@ +package attendance.controller; + +import attendance.command.MenuCommandRegistry; +import attendance.command.MenuOption; +import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.util.file.FileReader; +import attendance.view.InputView; +import java.io.IOException; +import java.util.List; + +public class AttendanceController { + + private final MenuCommandRegistry registry; + private final AttendanceService service; + + public AttendanceController(MenuCommandRegistry registry, AttendanceService service) { + this.registry = registry; + this.service = service; + } + + public void run() throws IOException { + registerFileInfo(); + + while (true) { + MenuOption option = readOption(); + + if (option.equals(MenuOption.QUIT)) { + return; + } + + registry.execute(option); + } + } + + private void registerFileInfo() throws IOException { + FileReader fileReader = new FileReader("src/main/resources/attendances.csv"); + List readLines = fileReader.readLines(); + service.registerFileInfo(readLines); + } + + private MenuOption readOption() { + InputParser.parseMenu(InputView.readMenuSelection()); + } +} diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java new file mode 100644 index 00000000..4e103d9a --- /dev/null +++ b/src/main/java/attendance/domain/Attendance.java @@ -0,0 +1,42 @@ +package attendance.domain; + +import attendance.constant.Holiday; +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashMap; +import java.util.Map; + +public class Attendance { + + private final Map attendances; + + public Attendance(Map attendances) { + this.attendances = new HashMap<>(); + } + + public static Attendance from(Map dateTimes) { + Map attendances = new HashMap<>(); + + LocalDate now = DateTimes.now().toLocalDate(); + for (LocalDate date = LocalDate.of(2024,12,1); date.isBefore(now); date = date.plusDays(1)) { + + if (isHoliDay(date)) { + continue; + } + + if (dateTimes.containsKey(date)) { + attendances.put(date, dateTimes.get(date)); + continue; + } + + attendances.put(date, LocalTime.of(23,59)); + } + + return new Attendance(attendances); + } + + private static boolean isHoliDay(LocalDate date) { + return !Holiday.from(date).equals(Holiday.NONE); + } +} diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java new file mode 100644 index 00000000..a0c491c3 --- /dev/null +++ b/src/main/java/attendance/domain/Crew.java @@ -0,0 +1,20 @@ +package attendance.domain; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Map; + +public class Crew { + + private final String name; + private final Attendance attendance; + + private Crew(String name, Attendance attendance) { + this.name = name; + this.attendance = attendance; + } + + public static Crew of(String name, Map localDateTimes) { + return new Crew(name, Attendance.from(localDateTimes)); + } +} diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java new file mode 100644 index 00000000..6f430d0a --- /dev/null +++ b/src/main/java/attendance/domain/Crews.java @@ -0,0 +1,25 @@ +package attendance.domain; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Crews { + + private final List crews; + + private Crews(List crews) { + this.crews = new ArrayList<>(); + } + + public static Crews from(Map> attendances) { + List crews = new ArrayList<>(); + for (String name : attendances.keySet()) { + Crew crew = Crew.of(name, attendances.get(name)); + crews.add(crew); + } + return new Crews(crews); + } +} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java new file mode 100644 index 00000000..277293b5 --- /dev/null +++ b/src/main/java/attendance/service/AttendanceService.java @@ -0,0 +1,42 @@ +package attendance.service; + +import static java.util.Locale.KOREA; + +import attendance.domain.Crews; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AttendanceService { + + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); + + private Crews crews; + + public void registerFileInfo(List readLines) { + Map> attendances = new HashMap<>(); + + readLines.removeFirst(); + for (String readLine : readLines) { + String[] split = readLine.split(","); + String name = split[0]; + LocalDateTime dateTime = LocalDateTime.parse(split[1], DATETIME_FMT); + LocalDate date = dateTime.toLocalDate(); + LocalTime time = dateTime.toLocalTime(); + + if (attendances.containsKey(name)) { + attendances.get(name).put(date, time); + continue; + } + + attendances.put(name, new HashMap<>()); + } + + crews = Crews.from(attendances); + } +} diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java new file mode 100644 index 00000000..10b953bc --- /dev/null +++ b/src/main/java/attendance/util/InputParser.java @@ -0,0 +1,45 @@ +package attendance.util; + +import static java.util.Locale.KOREA; + +import attendance.command.MenuOption; +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public final class InputParser { + + private static final DateTimeFormatter TIME_FMT = + DateTimeFormatter.ofPattern("HH:mm", KOREA); + + private InputParser() { + } + + public static String parseName(String rawInput) { + return rawInput.strip(); + } + + public static LocalDate parseDate(String rawInput) { + rawInput = rawInput.strip(); + + Validator.validateDayFormat(rawInput); + + int day = NumberConvertor.convertToNumber(rawInput); + + LocalDate now = DateTimes.now().toLocalDate(); + return LocalDate.of(now.getYear(), now.getMonthValue(), day); + } + + public static LocalTime parseTime(String rawInput) { + rawInput = rawInput.strip(); + + Validator.validateTimeFormat(rawInput); + + return LocalTime.parse(rawInput, TIME_FMT); + } + + public static MenuOption parseMenu(String rawInput) { + return MenuOption.from(rawInput.strip()); + } +} diff --git a/src/main/java/attendance/util/NumberConvertor.java b/src/main/java/attendance/util/NumberConvertor.java new file mode 100644 index 00000000..b68a85f7 --- /dev/null +++ b/src/main/java/attendance/util/NumberConvertor.java @@ -0,0 +1,14 @@ +package attendance.util; + +import attendance.constant.ErrorMessage; + +public final class NumberConvertor { + + public static int convertToNumber(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + } +} diff --git a/src/main/java/attendance/util/Validator.java b/src/main/java/attendance/util/Validator.java new file mode 100644 index 00000000..2da3ff69 --- /dev/null +++ b/src/main/java/attendance/util/Validator.java @@ -0,0 +1,23 @@ +package attendance.util; + +import attendance.constant.ErrorMessage; + +public final class Validator { + + private static final String DAY_FORMAT = "^[1-9]|[1-2]\\d|3[0-1]$"; + private static final String TIME_FORMAT = "([01]\\d|2[0-3]):[0-5]\\d"; + + private Validator() {} + + public static void validateDayFormat(String rawInput) { + if (!rawInput.matches(DAY_FORMAT)) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + } + + public static void validateTimeFormat(String rawInput) { + if (!rawInput.matches(TIME_FORMAT)) { + throw new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + } +} diff --git a/src/main/java/attendance/util/file/FileReader.java b/src/main/java/attendance/util/file/FileReader.java new file mode 100644 index 00000000..621174fb --- /dev/null +++ b/src/main/java/attendance/util/file/FileReader.java @@ -0,0 +1,32 @@ +package attendance.util.file; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class FileReader { + + private static final int BUFFER_SIZE = 8192; + + private final java.io.FileReader fr; + + public FileReader(String fileName) throws IOException { + fr = new java.io.FileReader(fileName, UTF_8); + } + + public List readLines() throws IOException { + List contents = new ArrayList<>(); + BufferedReader br = new BufferedReader(fr, BUFFER_SIZE); + + String line; + while ((line = br.readLine()) != null) { + contents.add(line); + } + br.close(); + + return new ArrayList<>(contents); + } +} diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java new file mode 100644 index 00000000..5e4f328a --- /dev/null +++ b/src/main/java/attendance/view/InputView.java @@ -0,0 +1,11 @@ +package attendance.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + public static String read() { + System.out.println(""); + return Console.readLine(); + } +} diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java new file mode 100644 index 00000000..36629df3 --- /dev/null +++ b/src/main/java/attendance/view/OutputView.java @@ -0,0 +1,25 @@ +package attendance.view; + +import static java.util.Locale.KOREA; + +import java.time.format.DateTimeFormatter; + +public class OutputView { + + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일 HH:mm", KOREA); + private static final DateTimeFormatter DATE_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); + private static final DateTimeFormatter TIME_FMT = + DateTimeFormatter.ofPattern("HH:mm", KOREA); + + private OutputView() { + } + + public static void printErrorMessage(IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + + public static void print() { + } +} From d661da7c902609531a8c632f242fe388498d250c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:34:02 +0900 Subject: [PATCH 16/30] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AttendanceController.java | 2 +- src/main/java/attendance/view/InputView.java | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java index e9df55be..e941f79a 100644 --- a/src/main/java/attendance/controller/AttendanceController.java +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -40,6 +40,6 @@ private void registerFileInfo() throws IOException { } private MenuOption readOption() { - InputParser.parseMenu(InputView.readMenuSelection()); + return InputParser.parseMenu(InputView.readMenuSelection()); } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 5e4f328a..fcddc908 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -1,11 +1,24 @@ package attendance.view; +import static java.util.Locale.KOREA; + import camp.nextstep.edu.missionutils.Console; +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.format.DateTimeFormatter; public class InputView { - public static String read() { - System.out.println(""); + private static final DateTimeFormatter DATE_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); + + public static String readMenuSelection() { + System.out.printf("오늘은 %s입니다. 기능을 선택해 주세요.\n" + + "1. 출석 확인\n" + + "2. 출석 수정\n" + + "3. 크루별 출석 기록 확인\n" + + "4. 제적 위험\n" + + "자 확인\n" + + "Q. 종료\n", DateTimes.now().toLocalDate().format(DATE_FMT)); return Console.readLine(); } } From 0d929685c3707605ec8d99bcb201a17f1efc0118 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:38:20 +0900 Subject: [PATCH 17/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20-=20=EC=98=A4=EB=8A=98=EC=9D=B4=20=EB=93=B1?= =?UTF-8?q?=EA=B5=90=EC=9D=BC=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=A9=B4=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/attendance/command/MenuCommandRegistry.java | 4 ++-- .../impl/{FeatureACommand.java => CheckCommand.java} | 7 +++++-- .../java/attendance/service/AttendanceService.java | 10 ++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) rename src/main/java/attendance/command/impl/{FeatureACommand.java => CheckCommand.java} (54%) diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java index 833cfb74..812f5255 100644 --- a/src/main/java/attendance/command/MenuCommandRegistry.java +++ b/src/main/java/attendance/command/MenuCommandRegistry.java @@ -1,6 +1,6 @@ package attendance.command; -import attendance.command.impl.FeatureACommand; +import attendance.command.impl.CheckCommand; import attendance.command.impl.FeatureBCommand; import attendance.command.impl.FeatureCCommand; import attendance.command.impl.FeatureDCommand; @@ -17,7 +17,7 @@ private MenuCommandRegistry(EnumMap commands) { public static MenuCommandRegistry from(AttendanceService service) { EnumMap map = new EnumMap<>(MenuOption.class); - map.put(MenuOption.A, new FeatureACommand(service)); + map.put(MenuOption.A, new CheckCommand(service)); map.put(MenuOption.B, new FeatureBCommand(service)); map.put(MenuOption.C, new FeatureCCommand(service)); map.put(MenuOption.D, new FeatureDCommand(service)); diff --git a/src/main/java/attendance/command/impl/FeatureACommand.java b/src/main/java/attendance/command/impl/CheckCommand.java similarity index 54% rename from src/main/java/attendance/command/impl/FeatureACommand.java rename to src/main/java/attendance/command/impl/CheckCommand.java index 1fd041ad..adb798a2 100644 --- a/src/main/java/attendance/command/impl/FeatureACommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -2,17 +2,20 @@ import attendance.command.Command; import attendance.service.AttendanceService; +import camp.nextstep.edu.missionutils.DateTimes; -public class FeatureACommand implements Command { +public class CheckCommand implements Command { private final AttendanceService service; - public FeatureACommand(AttendanceService service) { + public CheckCommand(AttendanceService service) { this.service = service; } @Override public void execute() { + service.validateHoliday(DateTimes.now().toLocalDate()); + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 277293b5..957c0ac3 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -2,6 +2,8 @@ import static java.util.Locale.KOREA; +import attendance.constant.ErrorMessage; +import attendance.constant.Holiday; import attendance.domain.Crews; import java.time.LocalDate; import java.time.LocalDateTime; @@ -15,6 +17,8 @@ public class AttendanceService { private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); + private static final DateTimeFormatter DATE_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); private Crews crews; @@ -39,4 +43,10 @@ public void registerFileInfo(List readLines) { crews = Crews.from(attendances); } + + public void validateHoliday(LocalDate date) { + if (!Holiday.from(date).equals(Holiday.NONE)) { + throw new IllegalArgumentException(ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); + } + } } From cd528b47f3c184fd32d534014258c92027685c31 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:44:11 +0900 Subject: [PATCH 18/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/command/impl/CheckCommand.java | 9 +++++++-- src/main/java/attendance/domain/Attendance.java | 9 +++++++++ src/main/java/attendance/domain/Crew.java | 8 ++++++++ src/main/java/attendance/domain/Crews.java | 8 ++++++++ src/main/java/attendance/service/AttendanceService.java | 6 ++++++ src/main/java/attendance/view/InputView.java | 5 +++++ 6 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index adb798a2..de797d7e 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -2,7 +2,10 @@ import attendance.command.Command; import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.view.InputView; import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDate; public class CheckCommand implements Command { @@ -14,8 +17,10 @@ public CheckCommand(AttendanceService service) { @Override public void execute() { - service.validateHoliday(DateTimes.now().toLocalDate()); - + LocalDate now = DateTimes.now().toLocalDate(); + service.validateHoliday(now); + String name = InputParser.parseName(InputView.readName()); + service.check(name, now); } } diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index 4e103d9a..be01b97a 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.ErrorMessage; import attendance.constant.Holiday; import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; @@ -39,4 +40,12 @@ public static Attendance from(Map dateTimes) { private static boolean isHoliDay(LocalDate date) { return !Holiday.from(date).equals(Holiday.NONE); } + + public void check(LocalDate date) { + if (attendances.containsKey(date)) { + throw new IllegalArgumentException(ErrorMessage.ALREADY_ATTENDANCE_ERROR.getErrorMessage()); + } + + + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index a0c491c3..603301c0 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -17,4 +17,12 @@ private Crew(String name, Attendance attendance) { public static Crew of(String name, Map localDateTimes) { return new Crew(name, Attendance.from(localDateTimes)); } + + public String getName() { + return name; + } + + public void check(LocalDate date) { + attendance.check(date); + } } diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 6f430d0a..9770876d 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.ErrorMessage; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; @@ -22,4 +23,11 @@ public static Crews from(Map> attendances) { } return new Crews(crews); } + + public Crew getCrew(String name) { + return crews.stream() + .filter(crew -> crew.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_NAME_ERROR.getErrorMessage())); + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 957c0ac3..6f476946 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -4,6 +4,7 @@ import attendance.constant.ErrorMessage; import attendance.constant.Holiday; +import attendance.domain.Crew; import attendance.domain.Crews; import java.time.LocalDate; import java.time.LocalDateTime; @@ -49,4 +50,9 @@ public void validateHoliday(LocalDate date) { throw new IllegalArgumentException(ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); } } + + public void check(String name, LocalDate date) { + Crew crew = crews.getCrew(name); + crew.check(date); + } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index fcddc908..cabafa21 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -21,4 +21,9 @@ public static String readMenuSelection() { + "Q. 종료\n", DateTimes.now().toLocalDate().format(DATE_FMT)); return Console.readLine(); } + + public static String readName() { + System.out.println("닉네임을 입력해 주세요."); + return Console.readLine(); + } } From 696b5a7e5ca53428635e7a715bafc1be136826d5 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:50:45 +0900 Subject: [PATCH 19/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/command/impl/CheckCommand.java | 4 +++- src/main/java/attendance/domain/Attendance.java | 9 ++------- src/main/java/attendance/domain/Crew.java | 7 +++++-- src/main/java/attendance/service/AttendanceService.java | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index de797d7e..0b39c7bb 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -21,6 +21,8 @@ public void execute() { service.validateHoliday(now); String name = InputParser.parseName(InputView.readName()); - service.check(name, now); + service.validateCheckPossible(name, now); + + } } diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index be01b97a..04049d62 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -1,6 +1,5 @@ package attendance.domain; -import attendance.constant.ErrorMessage; import attendance.constant.Holiday; import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; @@ -41,11 +40,7 @@ private static boolean isHoliDay(LocalDate date) { return !Holiday.from(date).equals(Holiday.NONE); } - public void check(LocalDate date) { - if (attendances.containsKey(date)) { - throw new IllegalArgumentException(ErrorMessage.ALREADY_ATTENDANCE_ERROR.getErrorMessage()); - } - - + public boolean contains(LocalDate date) { + return attendances.containsKey(date); } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 603301c0..51fe49a4 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.ErrorMessage; import java.time.LocalDate; import java.time.LocalTime; import java.util.Map; @@ -22,7 +23,9 @@ public String getName() { return name; } - public void check(LocalDate date) { - attendance.check(date); + public void validateCheckPossible(LocalDate date) { + if (attendance.contains(date)) { + throw new IllegalArgumentException(ErrorMessage.ALREADY_ATTENDANCE_ERROR.getErrorMessage()); + } } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 6f476946..8da85d48 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -51,8 +51,8 @@ public void validateHoliday(LocalDate date) { } } - public void check(String name, LocalDate date) { + public void validateCheckPossible(String name, LocalDate date) { Crew crew = crews.getCrew(name); - crew.check(date); + crew.validateCheckPossible(date); } } From e22986c2b9a7f8a0a92059e7d56ad12adb3b2ef8 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:55:00 +0900 Subject: [PATCH 20/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=93=B1=EA=B5=90=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/attendance/command/impl/CheckCommand.java | 4 ++++ .../java/attendance/service/AttendanceService.java | 12 ++++++++++++ src/main/java/attendance/view/InputView.java | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index 0b39c7bb..2538a054 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -6,6 +6,7 @@ import attendance.view.InputView; import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; +import java.time.LocalTime; public class CheckCommand implements Command { @@ -23,6 +24,9 @@ public void execute() { String name = InputParser.parseName(InputView.readName()); service.validateCheckPossible(name, now); + LocalTime time = InputParser.parseTime(InputView.readTime()); + service.validateOperationTime(time); + service.check(name, now, time); } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 8da85d48..86ea98c8 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -16,6 +16,8 @@ public class AttendanceService { + private static final LocalTime START_TIME = LocalTime.of(8,0); + private static final LocalTime END_TIME = LocalTime.of(23,0); private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); private static final DateTimeFormatter DATE_FMT = @@ -55,4 +57,14 @@ public void validateCheckPossible(String name, LocalDate date) { Crew crew = crews.getCrew(name); crew.validateCheckPossible(date); } + + public void check(String name, LocalDate now, LocalTime time) { + + } + + public void validateOperationTime(LocalTime time) { + if (time.isBefore(START_TIME) || time.isAfter(END_TIME)) { + throw new IllegalArgumentException(ErrorMessage.NO_OPERATION_TIME_ERROR.getErrorMessage()); + } + } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index cabafa21..8ea480ad 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -26,4 +26,9 @@ public static String readName() { System.out.println("닉네임을 입력해 주세요."); return Console.readLine(); } + + public static String readTime() { + System.out.println("등교 시간을 입력해 주세요."); + return Console.readLine(); + } } From c5082a05b5d3d1497ac90ba12edfa1006e01dbac Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 17:56:16 +0900 Subject: [PATCH 21/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20-=20=EC=B6=9C=EC=84=9D=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/domain/Attendance.java | 4 ++++ src/main/java/attendance/domain/Crew.java | 4 ++++ .../java/attendance/service/AttendanceService.java | 12 +++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index 04049d62..32265632 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -43,4 +43,8 @@ private static boolean isHoliDay(LocalDate date) { public boolean contains(LocalDate date) { return attendances.containsKey(date); } + + public void check(LocalDate date, LocalTime time) { + attendances.put(date, time); + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 51fe49a4..100db605 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -28,4 +28,8 @@ public void validateCheckPossible(LocalDate date) { throw new IllegalArgumentException(ErrorMessage.ALREADY_ATTENDANCE_ERROR.getErrorMessage()); } } + + public void check(LocalDate date, LocalTime time) { + attendance.check(date, time); + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 86ea98c8..d8c3d4c0 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -16,8 +16,8 @@ public class AttendanceService { - private static final LocalTime START_TIME = LocalTime.of(8,0); - private static final LocalTime END_TIME = LocalTime.of(23,0); + private static final LocalTime START_TIME = LocalTime.of(8, 0); + private static final LocalTime END_TIME = LocalTime.of(23, 0); private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", KOREA); private static final DateTimeFormatter DATE_FMT = @@ -49,7 +49,8 @@ public void registerFileInfo(List readLines) { public void validateHoliday(LocalDate date) { if (!Holiday.from(date).equals(Holiday.NONE)) { - throw new IllegalArgumentException(ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); + throw new IllegalArgumentException( + ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); } } @@ -58,8 +59,9 @@ public void validateCheckPossible(String name, LocalDate date) { crew.validateCheckPossible(date); } - public void check(String name, LocalDate now, LocalTime time) { - + public void check(String name, LocalDate date, LocalTime time) { + Crew crew = crews.getCrew(name); + crew.check(date, time); } public void validateOperationTime(LocalTime time) { From 02f6f6682982d9c0dd5d83dc726e58333cad7e43 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 18:13:45 +0900 Subject: [PATCH 22/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20-=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/attendance/command/impl/CheckCommand.java | 3 +++ .../java/attendance/constant/AttendanceState.java | 2 +- src/main/java/attendance/domain/Attendance.java | 2 +- src/main/java/attendance/domain/Crews.java | 2 +- src/main/java/attendance/view/InputView.java | 3 +-- src/main/java/attendance/view/OutputView.java | 11 ++++++----- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/attendance/command/impl/CheckCommand.java b/src/main/java/attendance/command/impl/CheckCommand.java index 2538a054..0de19ea1 100644 --- a/src/main/java/attendance/command/impl/CheckCommand.java +++ b/src/main/java/attendance/command/impl/CheckCommand.java @@ -4,6 +4,7 @@ import attendance.service.AttendanceService; import attendance.util.InputParser; import attendance.view.InputView; +import attendance.view.OutputView; import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalTime; @@ -28,5 +29,7 @@ public void execute() { service.validateOperationTime(time); service.check(name, now, time); + + OutputView.printCheckResult(now, time); } } diff --git a/src/main/java/attendance/constant/AttendanceState.java b/src/main/java/attendance/constant/AttendanceState.java index a908e019..cfd43ec5 100644 --- a/src/main/java/attendance/constant/AttendanceState.java +++ b/src/main/java/attendance/constant/AttendanceState.java @@ -19,7 +19,7 @@ public enum AttendanceState { this.name = name; } - public static AttendanceState from(LocalDate date, LocalTime time) { + public static AttendanceState of(LocalDate date, LocalTime time) { Standard standard = Standard.from(date); return Arrays.stream(values()) diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index 32265632..39b428d2 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -12,7 +12,7 @@ public class Attendance { private final Map attendances; public Attendance(Map attendances) { - this.attendances = new HashMap<>(); + this.attendances = attendances; } public static Attendance from(Map dateTimes) { diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 9770876d..9ee506d0 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -12,7 +12,7 @@ public class Crews { private final List crews; private Crews(List crews) { - this.crews = new ArrayList<>(); + this.crews = crews; } public static Crews from(Map> attendances) { diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 8ea480ad..5a1b2c88 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -16,8 +16,7 @@ public static String readMenuSelection() { + "1. 출석 확인\n" + "2. 출석 수정\n" + "3. 크루별 출석 기록 확인\n" - + "4. 제적 위험\n" - + "자 확인\n" + + "4. 제적 위험자 확인\n" + "Q. 종료\n", DateTimes.now().toLocalDate().format(DATE_FMT)); return Console.readLine(); } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 36629df3..973fc180 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -2,6 +2,10 @@ import static java.util.Locale.KOREA; +import attendance.constant.AttendanceState; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class OutputView { @@ -16,10 +20,7 @@ public class OutputView { private OutputView() { } - public static void printErrorMessage(IllegalArgumentException e) { - System.out.println(e.getMessage()); - } - - public static void print() { + public static void printCheckResult(LocalDate date, LocalTime time) { + System.out.printf("%s (%s)\n", LocalDateTime.of(date,time).format(DATETIME_FMT), AttendanceState.of(date, time).getName()); } } From 0ab0641209eae6fbe47dbce20393ed64ddcf0ff2 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 18:17:10 +0900 Subject: [PATCH 23/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/MenuCommandRegistry.java | 4 +-- .../command/impl/FeatureBCommand.java | 18 ---------- .../command/impl/ModificationCommand.java | 34 +++++++++++++++++++ .../attendance/service/AttendanceService.java | 4 +++ src/main/java/attendance/view/InputView.java | 5 +++ 5 files changed, 45 insertions(+), 20 deletions(-) delete mode 100644 src/main/java/attendance/command/impl/FeatureBCommand.java create mode 100644 src/main/java/attendance/command/impl/ModificationCommand.java diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java index 812f5255..cc700323 100644 --- a/src/main/java/attendance/command/MenuCommandRegistry.java +++ b/src/main/java/attendance/command/MenuCommandRegistry.java @@ -1,7 +1,7 @@ package attendance.command; import attendance.command.impl.CheckCommand; -import attendance.command.impl.FeatureBCommand; +import attendance.command.impl.ModificationCommand; import attendance.command.impl.FeatureCCommand; import attendance.command.impl.FeatureDCommand; import attendance.service.AttendanceService; @@ -18,7 +18,7 @@ private MenuCommandRegistry(EnumMap commands) { public static MenuCommandRegistry from(AttendanceService service) { EnumMap map = new EnumMap<>(MenuOption.class); map.put(MenuOption.A, new CheckCommand(service)); - map.put(MenuOption.B, new FeatureBCommand(service)); + map.put(MenuOption.B, new ModificationCommand(service)); map.put(MenuOption.C, new FeatureCCommand(service)); map.put(MenuOption.D, new FeatureDCommand(service)); return new MenuCommandRegistry(map); diff --git a/src/main/java/attendance/command/impl/FeatureBCommand.java b/src/main/java/attendance/command/impl/FeatureBCommand.java deleted file mode 100644 index 6a02a9f3..00000000 --- a/src/main/java/attendance/command/impl/FeatureBCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -package attendance.command.impl; - -import attendance.command.Command; -import attendance.service.AttendanceService; - -public class FeatureBCommand implements Command { - - private final AttendanceService service; - - public FeatureBCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - - } -} diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java new file mode 100644 index 00000000..f354f3be --- /dev/null +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -0,0 +1,34 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.view.InputView; +import attendance.view.OutputView; +import java.time.LocalDate; +import java.time.LocalTime; + +public class ModificationCommand implements Command { + + private final AttendanceService service; + + public ModificationCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + String name = InputParser.parseName(InputView.readModifiedName()); + service.validateModificationPossible(name); + + LocalDate time = InputParser.parseTime(InputView.readTime()); + service.validateModificationPossible(time); + + LocalTime time = InputParser.parseTime(InputView.readTime()); + service.validateModificationPossible(time); + + service.modify(name, now, time); + + OutputView.printCheckResult(now, time); + } +} diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index d8c3d4c0..a72fde0d 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -69,4 +69,8 @@ public void validateOperationTime(LocalTime time) { throw new IllegalArgumentException(ErrorMessage.NO_OPERATION_TIME_ERROR.getErrorMessage()); } } + + public void validateModificationPossible(String name) { + crews.getCrew(name); + } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 5a1b2c88..bc01701d 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -30,4 +30,9 @@ public static String readTime() { System.out.println("등교 시간을 입력해 주세요."); return Console.readLine(); } + + public static String readModifiedName() { + System.out.println("출석을 수정하려는 크루의 닉네임을 입력해 주세요."); + return Console.readLine(); + } } From 81b4572e5122d5c888d031c12ab3fce6dad6c884 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 18:21:30 +0900 Subject: [PATCH 24/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EB=82=A0=EC=A7=9C=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/impl/ModificationCommand.java | 4 ++-- .../attendance/service/AttendanceService.java | 18 +++++++++++++++++- src/main/java/attendance/view/InputView.java | 5 +++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index f354f3be..2a1878c0 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -21,8 +21,8 @@ public void execute() { String name = InputParser.parseName(InputView.readModifiedName()); service.validateModificationPossible(name); - LocalDate time = InputParser.parseTime(InputView.readTime()); - service.validateModificationPossible(time); + LocalDate date = InputParser.parseDate(InputView.readModifiedDate()); + service.validateModificationPossible(date); LocalTime time = InputParser.parseTime(InputView.readTime()); service.validateModificationPossible(time); diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index a72fde0d..89a65a00 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -6,6 +6,7 @@ import attendance.constant.Holiday; import attendance.domain.Crew; import attendance.domain.Crews; +import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -48,12 +49,16 @@ public void registerFileInfo(List readLines) { } public void validateHoliday(LocalDate date) { - if (!Holiday.from(date).equals(Holiday.NONE)) { + if (isHoliday(date)) { throw new IllegalArgumentException( ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); } } + private static boolean isHoliday(LocalDate date) { + return !Holiday.from(date).equals(Holiday.NONE); + } + public void validateCheckPossible(String name, LocalDate date) { Crew crew = crews.getCrew(name); crew.validateCheckPossible(date); @@ -73,4 +78,15 @@ public void validateOperationTime(LocalTime time) { public void validateModificationPossible(String name) { crews.getCrew(name); } + + public void validateModificationPossible(LocalDate date) { + if (isHoliday(date)) { + throw new IllegalArgumentException( + ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); + } + + if (date.isAfter(DateTimes.now().toLocalDate())) { + throw new IllegalArgumentException(ErrorMessage.FUTURE_DATE_ERROR.getErrorMessage()); + } + } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index bc01701d..93ca829b 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -35,4 +35,9 @@ public static String readModifiedName() { System.out.println("출석을 수정하려는 크루의 닉네임을 입력해 주세요."); return Console.readLine(); } + + public static String readModifiedDate() { + System.out.println("수정하려는 날짜(일)를 입력해 주세요."); + return Console.readLine(); + } } From 55f9649ea83ef8c2752beb1deeb9c7e0a13a545c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 18:23:53 +0900 Subject: [PATCH 25/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=8B=9C=EA=B0=81=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/command/impl/ModificationCommand.java | 2 +- .../java/attendance/service/AttendanceService.java | 12 ++++++++---- src/main/java/attendance/view/InputView.java | 5 +++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index 2a1878c0..83004638 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -24,7 +24,7 @@ public void execute() { LocalDate date = InputParser.parseDate(InputView.readModifiedDate()); service.validateModificationPossible(date); - LocalTime time = InputParser.parseTime(InputView.readTime()); + LocalTime time = InputParser.parseTime(InputView.readModifiedTime()); service.validateModificationPossible(time); service.modify(name, now, time); diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 89a65a00..1eb63678 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -80,13 +80,17 @@ public void validateModificationPossible(String name) { } public void validateModificationPossible(LocalDate date) { - if (isHoliday(date)) { - throw new IllegalArgumentException( - ErrorMessage.NO_ATTENDANCE_DAY_ERROR.getErrorMessage(date.format(DATE_FMT))); - } + validateHoliday(date); + validateFutureDate(date); + } + private static void validateFutureDate(LocalDate date) { if (date.isAfter(DateTimes.now().toLocalDate())) { throw new IllegalArgumentException(ErrorMessage.FUTURE_DATE_ERROR.getErrorMessage()); } } + + public void validateModificationPossible(LocalTime time) { + validateOperationTime(time); + } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 93ca829b..9793b513 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -40,4 +40,9 @@ public static String readModifiedDate() { System.out.println("수정하려는 날짜(일)를 입력해 주세요."); return Console.readLine(); } + + public static String readModifiedTime() { + System.out.println("언제로 변경하겠습니까?"); + return Console.readLine(); + } } From 65ff73be50d0a443af6bfd08382e4efbb3320c7f Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 18:27:33 +0900 Subject: [PATCH 26/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=B6=9C=EC=84=9D=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/attendance/command/impl/ModificationCommand.java | 4 ++-- src/main/java/attendance/domain/Attendance.java | 6 ++++++ src/main/java/attendance/domain/Crew.java | 4 ++++ src/main/java/attendance/service/AttendanceService.java | 5 +++++ src/main/java/attendance/view/OutputView.java | 4 ++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/attendance/command/impl/ModificationCommand.java b/src/main/java/attendance/command/impl/ModificationCommand.java index 83004638..c9aa4b8f 100644 --- a/src/main/java/attendance/command/impl/ModificationCommand.java +++ b/src/main/java/attendance/command/impl/ModificationCommand.java @@ -27,8 +27,8 @@ public void execute() { LocalTime time = InputParser.parseTime(InputView.readModifiedTime()); service.validateModificationPossible(time); - service.modify(name, now, time); + LocalTime oldTime = service.modify(name, date, time); - OutputView.printCheckResult(now, time); + OutputView.printModificationResult(date, time, oldTime); } } diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index 39b428d2..eb76d10a 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -47,4 +47,10 @@ public boolean contains(LocalDate date) { public void check(LocalDate date, LocalTime time) { attendances.put(date, time); } + + public LocalTime modify(LocalDate date, LocalTime newTime) { + LocalTime oldTime = attendances.get(date); + attendances.put(date, newTime); + return oldTime; + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 100db605..9dad5486 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -32,4 +32,8 @@ public void validateCheckPossible(LocalDate date) { public void check(LocalDate date, LocalTime time) { attendance.check(date, time); } + + public LocalTime modify(LocalDate date, LocalTime newTime) { + return attendance.modify(date, newTime); + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 1eb63678..fc742c90 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -93,4 +93,9 @@ private static void validateFutureDate(LocalDate date) { public void validateModificationPossible(LocalTime time) { validateOperationTime(time); } + + public LocalTime modify(String name, LocalDate date, LocalTime newTime) { + Crew crew = crews.getCrew(name); + return crew.modify(date, newTime); + } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 973fc180..cd3c345c 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -23,4 +23,8 @@ private OutputView() { public static void printCheckResult(LocalDate date, LocalTime time) { System.out.printf("%s (%s)\n", LocalDateTime.of(date,time).format(DATETIME_FMT), AttendanceState.of(date, time).getName()); } + + public static void printModificationResult(LocalDate date, LocalTime time, LocalTime oldTime) { + + } } From bfc3cea3bde79e6ebe144581b8718f0d36c3bc6f Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 18:39:07 +0900 Subject: [PATCH 27/30] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=88=98=EC=A0=95=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/constant/AttendanceState.java | 2 +- src/main/java/attendance/service/AttendanceService.java | 2 +- src/main/java/attendance/view/OutputView.java | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/attendance/constant/AttendanceState.java b/src/main/java/attendance/constant/AttendanceState.java index cfd43ec5..2f4935a4 100644 --- a/src/main/java/attendance/constant/AttendanceState.java +++ b/src/main/java/attendance/constant/AttendanceState.java @@ -6,7 +6,7 @@ public enum AttendanceState { - ABSENCE(30, "제적"), + ABSENCE(30, "결석"), LATE(5, "면담"), ATTENDANCE(0, "출석"), ; diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index fc742c90..62270143 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -42,7 +42,7 @@ public void registerFileInfo(List readLines) { continue; } - attendances.put(name, new HashMap<>()); + attendances.put(name, new HashMap<>(Map.of(date, time))); } crews = Crews.from(attendances); diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index cd3c345c..2b19153e 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -25,6 +25,13 @@ public static void printCheckResult(LocalDate date, LocalTime time) { } public static void printModificationResult(LocalDate date, LocalTime time, LocalTime oldTime) { - + if (oldTime.isAfter(LocalTime.of(23,58))) { + System.out.printf("%s --:-- (결석) -> %s (%s) 수정 완료!\n", date.format(DATE_FMT), + time.format(TIME_FMT), AttendanceState.of(date, time).getName()); + return; + } + System.out.printf("%s %s (%s) -> %s (%s) 수정 완료!\n", date.format(DATE_FMT), + oldTime.format(TIME_FMT), AttendanceState.of(date, oldTime).getName(), + time.format(TIME_FMT), AttendanceState.of(date, time).getName()); } } From 68b86eab7a6fc502f93d7c6af820d0e3fe785c90 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 19:02:12 +0900 Subject: [PATCH 28/30] =?UTF-8?q?feat:=20=ED=81=AC=EB=A3=A8=EB=B3=84=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=20=EA=B8=B0=EB=A1=9D=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/MenuCommandRegistry.java | 4 +- .../command/impl/FeatureCCommand.java | 18 --------- .../command/impl/RecordQueryCommand.java | 25 +++++++++++++ src/main/java/attendance/constant/Danger.java | 4 +- .../java/attendance/domain/Attendance.java | 32 ++++++++++++++++ src/main/java/attendance/domain/Crew.java | 16 ++++++++ .../attendance/service/AttendanceService.java | 4 ++ src/main/java/attendance/view/OutputView.java | 37 ++++++++++++++++++- 8 files changed, 116 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/attendance/command/impl/FeatureCCommand.java create mode 100644 src/main/java/attendance/command/impl/RecordQueryCommand.java diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java index cc700323..bdd9a354 100644 --- a/src/main/java/attendance/command/MenuCommandRegistry.java +++ b/src/main/java/attendance/command/MenuCommandRegistry.java @@ -2,7 +2,7 @@ import attendance.command.impl.CheckCommand; import attendance.command.impl.ModificationCommand; -import attendance.command.impl.FeatureCCommand; +import attendance.command.impl.RecordQueryCommand; import attendance.command.impl.FeatureDCommand; import attendance.service.AttendanceService; import java.util.EnumMap; @@ -19,7 +19,7 @@ public static MenuCommandRegistry from(AttendanceService service) { EnumMap map = new EnumMap<>(MenuOption.class); map.put(MenuOption.A, new CheckCommand(service)); map.put(MenuOption.B, new ModificationCommand(service)); - map.put(MenuOption.C, new FeatureCCommand(service)); + map.put(MenuOption.C, new RecordQueryCommand(service)); map.put(MenuOption.D, new FeatureDCommand(service)); return new MenuCommandRegistry(map); } diff --git a/src/main/java/attendance/command/impl/FeatureCCommand.java b/src/main/java/attendance/command/impl/FeatureCCommand.java deleted file mode 100644 index 19669f66..00000000 --- a/src/main/java/attendance/command/impl/FeatureCCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -package attendance.command.impl; - -import attendance.command.Command; -import attendance.service.AttendanceService; - -public class FeatureCCommand implements Command { - - private final AttendanceService service; - - public FeatureCCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - - } -} diff --git a/src/main/java/attendance/command/impl/RecordQueryCommand.java b/src/main/java/attendance/command/impl/RecordQueryCommand.java new file mode 100644 index 00000000..e3b32eab --- /dev/null +++ b/src/main/java/attendance/command/impl/RecordQueryCommand.java @@ -0,0 +1,25 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.domain.Crew; +import attendance.service.AttendanceService; +import attendance.util.InputParser; +import attendance.view.InputView; +import attendance.view.OutputView; + +public class RecordQueryCommand implements Command { + + private final AttendanceService service; + + public RecordQueryCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + String name = InputParser.parseName(InputView.readName()); + Crew crew = service.getAttendanceRecords(name); + + OutputView.printRecords(crew); + } +} diff --git a/src/main/java/attendance/constant/Danger.java b/src/main/java/attendance/constant/Danger.java index cd1ca99f..2eb41649 100644 --- a/src/main/java/attendance/constant/Danger.java +++ b/src/main/java/attendance/constant/Danger.java @@ -18,9 +18,9 @@ public enum Danger { this.name = name; } - public static Danger from(int num) { + public static Danger from(int absenceCount) { return Arrays.stream(values()) - .filter(danger -> danger.absenceCount <= num) + .filter(danger -> danger.absenceCount <= absenceCount) .findFirst() .orElse(NONE); } diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index eb76d10a..63194758 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.AttendanceState; import attendance.constant.Holiday; import camp.nextstep.edu.missionutils.DateTimes; import java.time.LocalDate; @@ -53,4 +54,35 @@ public LocalTime modify(LocalDate date, LocalTime newTime) { attendances.put(date, newTime); return oldTime; } + + public Map getRecords() { + Map records = new HashMap<>(attendances); + records.remove(DateTimes.now().toLocalDate()); + + return records; + } + + public int getAttendanceCount() { + Map records = getRecords(); + + return (int) records.keySet().stream() + .filter(date -> AttendanceState.of(date, records.get(date)).equals(AttendanceState.ATTENDANCE)) + .count(); + } + + public int getLateCount() { + Map records = getRecords(); + + return (int) records.keySet().stream() + .filter(date -> AttendanceState.of(date, records.get(date)).equals(AttendanceState.LATE)) + .count(); + } + + public int getAbsenceCount() { + Map records = getRecords(); + + return (int) records.keySet().stream() + .filter(date -> AttendanceState.of(date, records.get(date)).equals(AttendanceState.ABSENCE)) + .count(); + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 9dad5486..0d4687b4 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -36,4 +36,20 @@ public void check(LocalDate date, LocalTime time) { public LocalTime modify(LocalDate date, LocalTime newTime) { return attendance.modify(date, newTime); } + + public Map getAttendanceRecords() { + return attendance.getRecords(); + } + + public int getAttendanceCount() { + return attendance.getAttendanceCount(); + } + + public int getLateCount() { + return attendance.getLateCount(); + } + + public int getAbsenceCount() { + return attendance.getAbsenceCount(); + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 62270143..4bb8318d 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -98,4 +98,8 @@ public LocalTime modify(String name, LocalDate date, LocalTime newTime) { Crew crew = crews.getCrew(name); return crew.modify(date, newTime); } + + public Crew getAttendanceRecords(String name) { + return crews.getCrew(name); + } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 2b19153e..15040d2f 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -3,10 +3,15 @@ import static java.util.Locale.KOREA; import attendance.constant.AttendanceState; +import attendance.constant.Danger; +import attendance.domain.Crew; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class OutputView { @@ -21,11 +26,12 @@ private OutputView() { } public static void printCheckResult(LocalDate date, LocalTime time) { - System.out.printf("%s (%s)\n", LocalDateTime.of(date,time).format(DATETIME_FMT), AttendanceState.of(date, time).getName()); + System.out.printf("%s (%s)\n", LocalDateTime.of(date, time).format(DATETIME_FMT), + AttendanceState.of(date, time).getName()); } public static void printModificationResult(LocalDate date, LocalTime time, LocalTime oldTime) { - if (oldTime.isAfter(LocalTime.of(23,58))) { + if (oldTime.isAfter(LocalTime.of(23, 58))) { System.out.printf("%s --:-- (결석) -> %s (%s) 수정 완료!\n", date.format(DATE_FMT), time.format(TIME_FMT), AttendanceState.of(date, time).getName()); return; @@ -34,4 +40,31 @@ public static void printModificationResult(LocalDate date, LocalTime time, Local oldTime.format(TIME_FMT), AttendanceState.of(date, oldTime).getName(), time.format(TIME_FMT), AttendanceState.of(date, time).getName()); } + + public static void printRecords(Crew crew) { + System.out.printf("이번 달 %s의 출석 기록입니다.\n\n", crew.getName()); + + Map attendanceRecords = crew.getAttendanceRecords(); + List dates = new ArrayList<>(attendanceRecords.keySet()); + dates.sort(null); + for (LocalDate date : dates) { + LocalTime time = attendanceRecords.get(date); + if (time.isAfter(LocalTime.of(23, 58))) { + System.out.printf("%s --:-- (%s)\n", date.format(DATE_FMT), AttendanceState.of(date, time).getName()); + continue; + } + System.out.printf("%s %s (%s)\n", date.format(DATE_FMT), time.format(TIME_FMT), + AttendanceState.of(date, time).getName()); + } + System.out.println(); + + System.out.printf("출석: %d회\n", crew.getAttendanceCount()); + System.out.printf("지각: %d회\n", crew.getLateCount()); + System.out.printf("결석: %d회\n\n", crew.getAbsenceCount()); + + Danger danger = Danger.from(crew.getAbsenceCount() + crew.getLateCount() / 3); + if (!danger.equals(Danger.NONE)) { + System.out.printf("%s 대상자입니다.", danger.getName()); + } + } } From 294f83352b46807e135cffa77f7de543ec2e36c3 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 19:12:02 +0900 Subject: [PATCH 29/30] =?UTF-8?q?feat:=20=EC=A0=9C=EC=A0=81=20=EC=9C=84?= =?UTF-8?q?=ED=97=98=EC=9E=90=20=ED=99=95=EC=9D=B8=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/MenuCommandRegistry.java | 4 +-- .../command/impl/DangerQueryCommand.java | 23 +++++++++++++++ .../command/impl/FeatureDCommand.java | 18 ------------ src/main/java/attendance/domain/Crew.java | 9 ++++++ src/main/java/attendance/domain/Crews.java | 7 +++++ .../attendance/service/AttendanceService.java | 4 +++ src/main/java/attendance/view/OutputView.java | 28 +++++++++++++++---- 7 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 src/main/java/attendance/command/impl/DangerQueryCommand.java delete mode 100644 src/main/java/attendance/command/impl/FeatureDCommand.java diff --git a/src/main/java/attendance/command/MenuCommandRegistry.java b/src/main/java/attendance/command/MenuCommandRegistry.java index bdd9a354..d7b07b30 100644 --- a/src/main/java/attendance/command/MenuCommandRegistry.java +++ b/src/main/java/attendance/command/MenuCommandRegistry.java @@ -3,7 +3,7 @@ import attendance.command.impl.CheckCommand; import attendance.command.impl.ModificationCommand; import attendance.command.impl.RecordQueryCommand; -import attendance.command.impl.FeatureDCommand; +import attendance.command.impl.DangerQueryCommand; import attendance.service.AttendanceService; import java.util.EnumMap; @@ -20,7 +20,7 @@ public static MenuCommandRegistry from(AttendanceService service) { map.put(MenuOption.A, new CheckCommand(service)); map.put(MenuOption.B, new ModificationCommand(service)); map.put(MenuOption.C, new RecordQueryCommand(service)); - map.put(MenuOption.D, new FeatureDCommand(service)); + map.put(MenuOption.D, new DangerQueryCommand(service)); return new MenuCommandRegistry(map); } diff --git a/src/main/java/attendance/command/impl/DangerQueryCommand.java b/src/main/java/attendance/command/impl/DangerQueryCommand.java new file mode 100644 index 00000000..e33d5acd --- /dev/null +++ b/src/main/java/attendance/command/impl/DangerQueryCommand.java @@ -0,0 +1,23 @@ +package attendance.command.impl; + +import attendance.command.Command; +import attendance.domain.Crew; +import attendance.service.AttendanceService; +import attendance.view.OutputView; +import java.util.List; + +public class DangerQueryCommand implements Command { + + private final AttendanceService service; + + public DangerQueryCommand(AttendanceService service) { + this.service = service; + } + + @Override + public void execute() { + List dangers = service.getDangers(); + + OutputView.printDangers(dangers); + } +} diff --git a/src/main/java/attendance/command/impl/FeatureDCommand.java b/src/main/java/attendance/command/impl/FeatureDCommand.java deleted file mode 100644 index 5ca410d5..00000000 --- a/src/main/java/attendance/command/impl/FeatureDCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -package attendance.command.impl; - -import attendance.command.Command; -import attendance.service.AttendanceService; - -public class FeatureDCommand implements Command { - - private final AttendanceService service; - - public FeatureDCommand(AttendanceService service) { - this.service = service; - } - - @Override - public void execute() { - - } -} diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 0d4687b4..98f30dff 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.Danger; import attendance.constant.ErrorMessage; import java.time.LocalDate; import java.time.LocalTime; @@ -52,4 +53,12 @@ public int getLateCount() { public int getAbsenceCount() { return attendance.getAbsenceCount(); } + + public Danger getDangerState() { + return Danger.from(getAbsenceLateCount()); + } + + public int getAbsenceLateCount() { + return getAbsenceCount() + getLateCount() / 3; + } } diff --git a/src/main/java/attendance/domain/Crews.java b/src/main/java/attendance/domain/Crews.java index 9ee506d0..ad1bb3af 100644 --- a/src/main/java/attendance/domain/Crews.java +++ b/src/main/java/attendance/domain/Crews.java @@ -1,5 +1,6 @@ package attendance.domain; +import attendance.constant.Danger; import attendance.constant.ErrorMessage; import java.time.LocalDate; import java.time.LocalTime; @@ -30,4 +31,10 @@ public Crew getCrew(String name) { .findFirst() .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_NAME_ERROR.getErrorMessage())); } + + public List getDangers() { + return new ArrayList<>(crews.stream() + .filter(crew -> !crew.getDangerState().equals(Danger.NONE)) + .toList()); + } } diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java index 4bb8318d..3d32aeac 100644 --- a/src/main/java/attendance/service/AttendanceService.java +++ b/src/main/java/attendance/service/AttendanceService.java @@ -102,4 +102,8 @@ public LocalTime modify(String name, LocalDate date, LocalTime newTime) { public Crew getAttendanceRecords(String name) { return crews.getCrew(name); } + + public List getDangers() { + return crews.getDangers(); + } } diff --git a/src/main/java/attendance/view/OutputView.java b/src/main/java/attendance/view/OutputView.java index 15040d2f..2a43a21d 100644 --- a/src/main/java/attendance/view/OutputView.java +++ b/src/main/java/attendance/view/OutputView.java @@ -10,6 +10,7 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -26,17 +27,17 @@ private OutputView() { } public static void printCheckResult(LocalDate date, LocalTime time) { - System.out.printf("%s (%s)\n", LocalDateTime.of(date, time).format(DATETIME_FMT), + System.out.printf("%s (%s)\n\n", LocalDateTime.of(date, time).format(DATETIME_FMT), AttendanceState.of(date, time).getName()); } public static void printModificationResult(LocalDate date, LocalTime time, LocalTime oldTime) { if (oldTime.isAfter(LocalTime.of(23, 58))) { - System.out.printf("%s --:-- (결석) -> %s (%s) 수정 완료!\n", date.format(DATE_FMT), + System.out.printf("%s --:-- (결석) -> %s (%s) 수정 완료!\n\n", date.format(DATE_FMT), time.format(TIME_FMT), AttendanceState.of(date, time).getName()); return; } - System.out.printf("%s %s (%s) -> %s (%s) 수정 완료!\n", date.format(DATE_FMT), + System.out.printf("%s %s (%s) -> %s (%s) 수정 완료!\n\n", date.format(DATE_FMT), oldTime.format(TIME_FMT), AttendanceState.of(date, oldTime).getName(), time.format(TIME_FMT), AttendanceState.of(date, time).getName()); } @@ -62,9 +63,24 @@ public static void printRecords(Crew crew) { System.out.printf("지각: %d회\n", crew.getLateCount()); System.out.printf("결석: %d회\n\n", crew.getAbsenceCount()); - Danger danger = Danger.from(crew.getAbsenceCount() + crew.getLateCount() / 3); - if (!danger.equals(Danger.NONE)) { - System.out.printf("%s 대상자입니다.", danger.getName()); + Danger dangerState = crew.getDangerState(); + if (!dangerState.equals(Danger.NONE)) { + System.out.printf("%s 대상자입니다.\n", dangerState.getName()); } + System.out.println(); + } + + public static void printDangers(List dangers) { + dangers.sort(Comparator.comparingInt(Crew::getAbsenceLateCount).reversed() + .thenComparingInt(Crew::getAbsenceCount).reversed() + .thenComparingInt(Crew::getLateCount).reversed() + .thenComparing(Crew::getName)); + + System.out.println("제적 위험자 조회 결과"); + for (Crew danger : dangers) { + System.out.printf("- %s: 결석 %d회, 지각 %d회 (%s)\n", + danger.getName(), danger.getAbsenceCount(), danger.getLateCount(), danger.getDangerState().getName()); + } + System.out.println(); } } From 47657a385d6f193d0ce30f65d3d7f26db13064f2 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Thu, 8 Jan 2026 19:27:06 +0900 Subject: [PATCH 30/30] =?UTF-8?q?refactor:=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/constant/AttendanceState.java | 2 +- src/main/java/attendance/domain/Attendance.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/attendance/constant/AttendanceState.java b/src/main/java/attendance/constant/AttendanceState.java index 2f4935a4..f1e7a663 100644 --- a/src/main/java/attendance/constant/AttendanceState.java +++ b/src/main/java/attendance/constant/AttendanceState.java @@ -7,7 +7,7 @@ public enum AttendanceState { ABSENCE(30, "결석"), - LATE(5, "면담"), + LATE(5, "지각"), ATTENDANCE(0, "출석"), ; diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index 63194758..5df77ef9 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -21,7 +21,6 @@ public static Attendance from(Map dateTimes) { LocalDate now = DateTimes.now().toLocalDate(); for (LocalDate date = LocalDate.of(2024,12,1); date.isBefore(now); date = date.plusDays(1)) { - if (isHoliDay(date)) { continue; }