From 856687f896e5641a3c359be9556df8d7b5089f6c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 15:04:59 +0900 Subject: [PATCH 01/12] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=EA=B5=AC?= =?UTF-8?q?=ED=98=84=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 | 68 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 572a180..1ed8801 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,58 @@ # 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. 파일 입력 +2. 오늘 날짜와 요일 출력 +3. 기능 선택 입력 + - 예외 사항 + 1. “1”,”2”,”3”,”4”,”Q”가 아닌 다른 값을 입력한 경우 - “잘못된 형식을 입력하였습니다.” +4. 출석 확인 + - 예외 사항 + 1. 주말 또는 공휴일인 경우 - “%d월 %d일 %s요일은 등교일이 아닙니다.” + 1. 닉네임 입력 + - 예외 사항 + 1. 등록되지 않은 닉네임인 경우 - “등록되지 않은 닉네임입니다.” + 2. 이미 출석한 경우 - “이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요.” + 2. 등교 시간 입력 + - 예외 사항 + 1. 시간 형식이 잘못된 경우(00:00) - “잘못된 형식을 입력하였습니다.” + 2. 등교 시간이 캠퍼스 운영 시간이 아닌 경우 - “캠퍼스 운영 시간에만 출석이 가능합니다.” + 3. 출석 등록된 날짜, 요일, 시간 정보 출력 + - “%d월 %d일 %s요일 00:00 (출석)” + - “%d월 %d일 %s요일 00:00 (지각)” + - “%d월 %d일 %s요일 —:— (결석)” +5. 출석 수정 + - 예외 사항 + 1. 주말 또는 공휴일인 경우 - “%d월 %d일 %s요일은 등교일이 아닙니다.” + 1. 닉네임 입력 + - 예외 사항 + 1. 등록되지 않은 닉네임인 경우 - “등록되지 않은 닉네임입니다.” + 2. 수정 날짜 입력 + - 예외 사항 + 1. 날짜 형식(1이상 31이하의 정수)이 아닌 경우 - “잘못된 형식을 입력하였습니다.” + 2. 미래 날짜인 경우 - “아직 수정할 수 없습니다.” + 3. 변경 시각 입력 + - 예외 사항 + 1. 시간 형식이 잘못된 경우(00:00) - “잘못된 형식을 입력하였습니다.” + 2. 등교 시간이 캠퍼스 운영 시간이 아닌 경우 - “캠퍼스 운영 시간에만 출석이 가능합니다.” + 4. 변경 정보 출력 + - “%d월 %d일 %s요일 00:00 (지각) -> %d월 %d일 %s요일 00:00 (출석) 수정 완료!” +6. 크루별 출석 기록 확인 + 1. 닉네임 입력 + - 예외 사항 + 1. 등록되지 않은 닉네임인 경우 - “등록되지 않은 닉네임입니다.” + 2. 출석 기록 출력 + - “이번 달 %s의 출석 기록입니다.” + - 평일 날짜별 출석 기록 + - 출석, 지각, 결석 횟수 + - 대상자 정보 + - “경고 대상자입니다.” + - “면담 대상자입니다.” + - “제적 대상자입니다.” +7. 제적 위험자 확인 + - 제적 위험자 출력 + - “제적 위험자 조회 결과” + - “- %s: 결석 %d회, 지각 %d회 (면담)” + - 제적, 면담, 경고 순으로 출력(지각 3회는 결석 1회로 계산) + - 출석 상태 같으면 닉네임 오름차순 +8. 종료 From 67040f88f9c7e8445888e228e5bfa451cb1de0d2 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 15:05:49 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EC=9E=85?= =?UTF-8?q?=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/Application.java | 29 ++++++- .../java/attendance/domain/Attendance.java | 45 +++++++++++ .../java/attendance/domain/Attendances.java | 38 ++++++++++ src/main/java/attendance/domain/Crew.java | 75 +++++++++++++++++++ .../java/attendance/util/file/FileReader.java | 43 +++++++++++ 5 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 src/main/java/attendance/domain/Attendance.java create mode 100644 src/main/java/attendance/domain/Attendances.java create mode 100644 src/main/java/attendance/domain/Crew.java create mode 100644 src/main/java/attendance/util/file/FileReader.java diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index e9b866e..075b012 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -1,7 +1,32 @@ package attendance; +import attendance.domain.Attendances; +import attendance.util.file.FileReader; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + public class Application { - public static void main(String[] args) { - // TODO: 프로그램 구현 + + private static Attendances attendances; + + public static void main(String[] args) throws IOException { + FileReader reader = new FileReader("src/main/resources/attendances.csv"); + List readLines = reader.readLines(); + readLines.removeFirst(); + attendances = Attendances.newInstance(); + for (String readPromotion : readLines) { + String[] split = readPromotion.split(","); + String name = split[0]; + String[] split1 = split[1].split(" "); + String dateFormat = split1[0]; + String timeFormat = split1[1]; + LocalDate date = LocalDate.parse(dateFormat); + LocalTime time = LocalTime.parse(timeFormat); + attendances.addAttendance(name, date, time); + } + + System.out.println(attendances.getCrews()); } } diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java new file mode 100644 index 0000000..cf8ca42 --- /dev/null +++ b/src/main/java/attendance/domain/Attendance.java @@ -0,0 +1,45 @@ +package attendance.domain; + +import java.time.LocalDate; +import java.time.LocalTime; + +public class Attendance { + + private LocalDate date; + private String dayOfWeek; + private LocalTime time; + private String state; + + public Attendance(LocalDate date, String dayOfWeek, LocalTime time, String state) { + this.date = date; + this.dayOfWeek = dayOfWeek; + this.time = time; + this.state = state; + } + + public LocalDate getDate() { + return date; + } + + public String getDayOfWeek() { + return dayOfWeek; + } + + public LocalTime getTime() { + return time; + } + + public String getState() { + return state; + } + + @Override + public String toString() { + return "Attendance{" + + "date=" + date + + ", dayOfWeek='" + dayOfWeek + '\'' + + ", time=" + time + + ", state='" + state + '\'' + + '}'; + } +} diff --git a/src/main/java/attendance/domain/Attendances.java b/src/main/java/attendance/domain/Attendances.java new file mode 100644 index 0000000..a08e6ce --- /dev/null +++ b/src/main/java/attendance/domain/Attendances.java @@ -0,0 +1,38 @@ +package attendance.domain; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +public class Attendances { + + private List crews; + + public Attendances() { + crews = new ArrayList<>(); + } + + public static Attendances newInstance() { + return new Attendances(); + } + + public void addAttendance(String name, LocalDate date, LocalTime time) { + Crew crew = new Crew(name); + for (Crew crew1 : crews) { + if (crew1.getName().equals(name)) { + crew = crew1; + } + } + + crew.registerDateAndTime(date, time); + + if (!crews.contains(crew)) { + crews.add(crew); + } + } + + public List getCrews() { + return crews; + } +} diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java new file mode 100644 index 0000000..1bce37d --- /dev/null +++ b/src/main/java/attendance/domain/Crew.java @@ -0,0 +1,75 @@ +package attendance.domain; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.TextStyle; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +public class Crew { + + private String name; + private DangerState state; + private List attendances; + private int lateCount; + private int absenceCount; + + public Crew(String name) { + this.name = name; + attendances = new ArrayList<>(); + } + + @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 void registerDateAndTime(LocalDate date, LocalTime time) { + String dayOfWeek = date.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + + LocalTime lateTime = LocalTime.parse("10:05"); + LocalTime absenceTime = LocalTime.parse("10:30"); + if (dayOfWeek.equals("월")) { + lateTime = LocalTime.parse("13:05"); + absenceTime = LocalTime.parse("13:30"); + } + + String attendanceState = "출석"; + if (time.isAfter(lateTime)) { + attendanceState = "지각"; + lateCount++; + } + if (time.isAfter(absenceTime)) { + attendanceState = "결석"; + absenceCount++; + } + + attendances.add(new Attendance(date, dayOfWeek, time, attendanceState)); + } + + @Override + public String toString() { + return "Crew{" + + "name='" + name + '\'' + + ", state=" + state + + ", attendances=" + attendances + + ", lateCount=" + lateCount + + ", absenceCount=" + absenceCount + + '}'; + } + + public String getName() { + return name; + } +} 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 0000000..20c8ca9 --- /dev/null +++ b/src/main/java/attendance/util/file/FileReader.java @@ -0,0 +1,43 @@ +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 final java.io.FileReader fr; + + public FileReader(String fileName) throws IOException { + fr = new java.io.FileReader(fileName, UTF_8); + } + + // 파일 전체를 한 번에 읽기 + public String readAll() throws IOException { + StringBuilder contents = new StringBuilder(); + int ch; + while ((ch = fr.read()) != -1) { + contents.append((char) ch); + } + fr.close(); + + return contents.toString(); + } + + // 파일 전체를 한 줄 씩 나눠서 읽기 + public List readLines() throws IOException { + List contents = new ArrayList<>(); + BufferedReader br = new BufferedReader(fr); + + String line; + while ((line = br.readLine()) != null) { + contents.add(line); + } + br.close(); + + return new ArrayList<>(contents); + } +} From 5a7d7d9debdb430a5deecacf3593923571c650ed Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 15:28:24 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EC=84=A0?= =?UTF-8?q?=ED=83=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 | 35 ++++++++++++++- .../java/attendance/util/InputParser.java | 43 +++++++++++++++++++ src/main/java/attendance/util/Validator.java | 23 ++++++++++ src/main/java/attendance/view/InputView.java | 27 ++++++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 src/main/java/attendance/util/InputParser.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 075b012..559843a 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -1,11 +1,16 @@ package attendance; import attendance.domain.Attendances; +import attendance.time.DateTime; +import attendance.util.InputParser; import attendance.util.file.FileReader; +import attendance.view.InputView; import java.io.IOException; import java.time.LocalDate; import java.time.LocalTime; +import java.time.format.TextStyle; import java.util.List; +import java.util.Locale; public class Application { @@ -27,6 +32,34 @@ public static void main(String[] args) throws IOException { attendances.addAttendance(name, date, time); } - System.out.println(attendances.getCrews()); +// System.out.println(attendances.getCrews()); + + while (true) { + LocalDate nowDate = DateTime.now(); + String rawChoice = InputView.readChoice(nowDate); + String choice = InputParser.parseChoice(rawChoice); + + if (choice.equals("Q")) { + break; + } + + if (choice.equals("1")) { + + continue; + } + + if (choice.equals("2")) { + continue; + } + + if (choice.equals("3")) { + continue; + } + + if (choice.equals("4")) { + + } + + } } } diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java new file mode 100644 index 0000000..b323051 --- /dev/null +++ b/src/main/java/attendance/util/InputParser.java @@ -0,0 +1,43 @@ +package attendance.util; + +import java.util.ArrayList; +import java.util.List; + +public final class InputParser { + + private static final String FIRST_DELIMITER = ","; + private static final String SECOND_DELIMITER = "-"; + private static final String YES = "Y"; + + private InputParser() { + } + +// public static List parsePurchaseProducts(String rawInput) { +// Validator.validateNullOrBlank(rawInput); +// rawInput = rawInput.strip(); +// +// Validator.validateCsvFormat(rawInput); +// +// List purchaseProducts = new ArrayList<>(); +// String[] split = rawInput.split(FIRST_DELIMITER); +// for (String s : split) { +// String[] order = s.strip().substring(1, s.length() - 1).split(SECOND_DELIMITER); +// String name = order[0]; +// int quantity = NumberConvertor.convertToNumber(order[1]); +// +// Validator.validateQuantity(quantity); +// +// for (int i = 0; i < quantity; i++) { +// purchaseProducts.add(name); +// } +// } +// +// return purchaseProducts; +// } + + public static String parseChoice(String rawChoice) { + rawChoice = rawChoice.strip(); + Validator.validateChoiceFormat(rawChoice); + return rawChoice; + } +} diff --git a/src/main/java/attendance/util/Validator.java b/src/main/java/attendance/util/Validator.java new file mode 100644 index 0000000..61a9f05 --- /dev/null +++ b/src/main/java/attendance/util/Validator.java @@ -0,0 +1,23 @@ +package attendance.util; + +import static attendance.constant.ErrorMessage.FORMAT_ERROR; + +public final class Validator { + + private static final String CSV_FORMAT = "^ *(\\[[가-힣a-zA-Z]+-\\d+])+ *(, *(\\[[가-힣a-zA-Z]+-\\d+])+ *)*$"; + private static final String CHOICE = " *[1234Q] *"; + + private Validator() { + } + + public static void validateCsvFormat(String input) { + if (!input.matches(CSV_FORMAT)) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } + } + public static void validateChoiceFormat(String rawChoice) { + if (!rawChoice.matches(CHOICE)) { + throw new IllegalArgumentException(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 0000000..82ada31 --- /dev/null +++ b/src/main/java/attendance/view/InputView.java @@ -0,0 +1,27 @@ +package attendance.view; + +import static camp.nextstep.edu.missionutils.Console.readLine; + +import camp.nextstep.edu.missionutils.Console; +import java.time.LocalDate; +import java.time.format.TextStyle; +import java.util.Locale; + +public class InputView { + + private static final String CHOICE_REQUEST = ""; + + public static String readChoice(LocalDate nowDate) { + System.out.println(CHOICE_REQUEST); + int month = nowDate.getMonth().getValue(); + int day = nowDate.getDayOfMonth(); + String dayOfWeek = nowDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + System.out.printf("오늘은 %d월 %d일 %s요일입니다. 기능을 선택해주세요.\n", month, day, dayOfWeek); + System.out.println("1. 출석 확인"); + System.out.println("2. 출석 수정"); + System.out.println("3. 크루별 출석 기록 확인"); + System.out.println("4. 제적 위험자 확인"); + System.out.println("Q. 종료"); + return Console.readLine(); + } +} From 16040191e4714ed72c41a5f3b0495207ea7006f8 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 15:29:26 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=9D=B8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=82=AC=ED=95=AD=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 --- src/test/java/attendance/ApplicationTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/attendance/ApplicationTest.java b/src/test/java/attendance/ApplicationTest.java index c1b4349..e089cb0 100644 --- a/src/test/java/attendance/ApplicationTest.java +++ b/src/test/java/attendance/ApplicationTest.java @@ -1,6 +1,7 @@ package attendance; import camp.nextstep.edu.missionutils.test.NsTest; +import java.io.IOException; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -79,6 +80,10 @@ class ApplicationTest extends NsTest { @Override protected void runMain() { - Application.main(new String[]{}); + try { + Application.main(new String[]{}); + } catch (IOException e) { + throw new RuntimeException(e); + } } } From eaf98ad3f466d304cb12037c32e4fb6301643daa Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 15:29:29 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=EC=B6=9C=EC=84=9D=20=EC=9D=B8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=82=AC=ED=95=AD=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 --- src/main/java/attendance/Application.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index 559843a..f144ca8 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -44,6 +44,14 @@ public static void main(String[] args) throws IOException { } if (choice.equals("1")) { + int month = nowDate.getMonthValue(); + int day = nowDate.getDayOfMonth(); + String dayOfWeek = nowDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + if (dayOfWeek.matches("[토|일]") || nowDate.isEqual(LocalDate.of(24,12,25))) { + throw new IllegalArgumentException(String.format("%d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); + } + + continue; } From eac98d98e800751bd8250f680b07470d31e32a96 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 15:43:53 +0900 Subject: [PATCH 06/12] =?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=EB=82=B4=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EC=9E=85=EB=A0=A5=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=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/domain/Attendances.java | 15 +++++++++++++-- src/main/java/attendance/domain/Crew.java | 9 +++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index f144ca8..77a6491 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -35,7 +35,8 @@ public static void main(String[] args) throws IOException { // System.out.println(attendances.getCrews()); while (true) { - LocalDate nowDate = DateTime.now(); +// LocalDate nowDate = DateTime.now(); + LocalDate nowDate = LocalDate.of(2024,12,13); String rawChoice = InputView.readChoice(nowDate); String choice = InputParser.parseChoice(rawChoice); @@ -47,11 +48,18 @@ public static void main(String[] args) throws IOException { int month = nowDate.getMonthValue(); int day = nowDate.getDayOfMonth(); String dayOfWeek = nowDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); - if (dayOfWeek.matches("[토|일]") || nowDate.isEqual(LocalDate.of(24,12,25))) { - throw new IllegalArgumentException(String.format("%d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); + if (dayOfWeek.matches("[토|일]") || nowDate.isEqual(LocalDate.of(2024,12,25))) { + throw new IllegalArgumentException(String.format("[ERROR] %d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); } + String name = InputView.readName(); + if (!attendances.contains(name)) { + throw new IllegalArgumentException("[ERROR] 등록되지 않은 닉네임입니다."); + } + if (attendances.isAlreadyAttend(name, nowDate)) { + throw new IllegalArgumentException("[ERROR] 이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."); + } continue; } diff --git a/src/main/java/attendance/domain/Attendances.java b/src/main/java/attendance/domain/Attendances.java index a08e6ce..42634ef 100644 --- a/src/main/java/attendance/domain/Attendances.java +++ b/src/main/java/attendance/domain/Attendances.java @@ -32,7 +32,18 @@ public void addAttendance(String name, LocalDate date, LocalTime time) { } } - public List getCrews() { - return crews; + public boolean contains(String name) { + Crew crew = new Crew(name); + return crews.contains(crew); + } + + public boolean isAlreadyAttend(String name, LocalDate nowDate) { + Crew checkCrew = new Crew(name); + for (Crew crew : crews) { + if (crew.equals(checkCrew) && crew.containsDate(nowDate)) { + return true; + } + } + return false; } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 1bce37d..595ca22 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -72,4 +72,13 @@ public String toString() { public String getName() { return name; } + + public boolean containsDate(LocalDate date) { + for (Attendance attendance : attendances) { + if (attendance.getDate().isEqual(date)) { + return true; + } + } + return false; + } } From 61c0506d973fdfdc061f3fa91146d5e4f5ddef47 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 16:19:28 +0900 Subject: [PATCH 07/12] =?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 --- src/main/java/attendance/Application.java | 20 +++++-- .../java/attendance/domain/Attendances.java | 14 +++++ src/main/java/attendance/domain/Crew.java | 60 ++++++++++++++----- src/main/java/attendance/time/DateTime.java | 11 ++++ .../java/attendance/util/InputParser.java | 7 +++ src/main/java/attendance/util/Validator.java | 7 +++ src/main/java/attendance/view/InputView.java | 10 ++++ 7 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 src/main/java/attendance/time/DateTime.java diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index 77a6491..5d6560c 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -1,7 +1,7 @@ package attendance; import attendance.domain.Attendances; -import attendance.time.DateTime; +import attendance.domain.Crew; import attendance.util.InputParser; import attendance.util.file.FileReader; import attendance.view.InputView; @@ -36,7 +36,7 @@ public static void main(String[] args) throws IOException { while (true) { // LocalDate nowDate = DateTime.now(); - LocalDate nowDate = LocalDate.of(2024,12,13); + LocalDate nowDate = LocalDate.of(2024, 12, 13); String rawChoice = InputView.readChoice(nowDate); String choice = InputParser.parseChoice(rawChoice); @@ -48,8 +48,9 @@ public static void main(String[] args) throws IOException { int month = nowDate.getMonthValue(); int day = nowDate.getDayOfMonth(); String dayOfWeek = nowDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); - if (dayOfWeek.matches("[토|일]") || nowDate.isEqual(LocalDate.of(2024,12,25))) { - throw new IllegalArgumentException(String.format("[ERROR] %d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); + if (dayOfWeek.matches("[토|일]") || nowDate.isEqual(LocalDate.of(2024, 12, 25))) { + throw new IllegalArgumentException( + String.format("[ERROR] %d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); } String name = InputView.readName(); @@ -61,6 +62,17 @@ public static void main(String[] args) throws IOException { throw new IllegalArgumentException("[ERROR] 이미 출석을 확인하였습니다. 필요한 경우 수정 기능을 이용해주세요."); } + String rawTime = InputView.readTime(); + LocalTime newTime = InputParser.parseTime(rawTime); + LocalTime startTime = LocalTime.of(8, 0, 0); + LocalTime endTime = LocalTime.of(23, 0, 0); + if (newTime.isBefore(startTime) || newTime.isAfter(endTime)) { + throw new IllegalArgumentException("[ERROR] 캠퍼스 운영 시간에만 출석이 가능합니다."); + } + + Crew registerdCrew = attendances.registerAttendance(name, newTime, nowDate); + System.out.println(registerdCrew.getInfoAt(nowDate)); + continue; } diff --git a/src/main/java/attendance/domain/Attendances.java b/src/main/java/attendance/domain/Attendances.java index 42634ef..9b2316f 100644 --- a/src/main/java/attendance/domain/Attendances.java +++ b/src/main/java/attendance/domain/Attendances.java @@ -46,4 +46,18 @@ public boolean isAlreadyAttend(String name, LocalDate nowDate) { } return false; } + + public Crew registerAttendance(String name, LocalTime newTime, LocalDate nowDate) { + Crew checkCrew = new Crew(name); + for (Crew crew : crews) { + if (crew.equals(checkCrew)) { + return crew.registerAttendance(newTime, nowDate); + } + } + return null; + } + + public List getCrews() { + return crews; + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 595ca22..1b387f2 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -38,22 +38,7 @@ public int hashCode() { public void registerDateAndTime(LocalDate date, LocalTime time) { String dayOfWeek = date.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); - LocalTime lateTime = LocalTime.parse("10:05"); - LocalTime absenceTime = LocalTime.parse("10:30"); - if (dayOfWeek.equals("월")) { - lateTime = LocalTime.parse("13:05"); - absenceTime = LocalTime.parse("13:30"); - } - - String attendanceState = "출석"; - if (time.isAfter(lateTime)) { - attendanceState = "지각"; - lateCount++; - } - if (time.isAfter(absenceTime)) { - attendanceState = "결석"; - absenceCount++; - } + String attendanceState = getAttendanceState(time, dayOfWeek); attendances.add(new Attendance(date, dayOfWeek, time, attendanceState)); } @@ -81,4 +66,47 @@ public boolean containsDate(LocalDate date) { } return false; } + + public Crew registerAttendance(LocalTime newTime, LocalDate nowDate) { + String dayOfWeek = nowDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + attendances.add(new Attendance(nowDate, dayOfWeek, newTime, getAttendanceState(newTime, dayOfWeek))); + return this; + } + + private String getAttendanceState(LocalTime time, String dayOfWeek) { + LocalTime lateTime = LocalTime.parse("10:05"); + LocalTime absenceTime = LocalTime.parse("10:30"); + if (dayOfWeek.equals("월")) { + lateTime = LocalTime.parse("13:05"); + absenceTime = LocalTime.parse("13:30"); + } + + String attendanceState = "출석"; + if (time.isAfter(lateTime)) { + attendanceState = "지각"; + lateCount++; + } + if (time.isAfter(absenceTime)) { + attendanceState = "결석"; + absenceCount++; + } + return attendanceState; + } + + public String getInfoAt(LocalDate nowDate) { + for (Attendance attendance : attendances) { + if (attendance.getDate().isEqual(nowDate)) { + int month = attendance.getDate().getMonthValue(); + int day = attendance.getDate().getDayOfMonth(); + String dayOfWeek = attendance.getDayOfWeek(); + int hour = attendance.getTime().getHour(); + int minute = attendance.getTime().getMinute(); + String state = attendance.getState(); + + return "\n" + month + "월 " + day + "일 " + dayOfWeek + "요일 " + + String.format("%02d:%02d", hour, minute) + " (" + state + ")"; + } + } + return null; + } } diff --git a/src/main/java/attendance/time/DateTime.java b/src/main/java/attendance/time/DateTime.java new file mode 100644 index 0000000..ab20062 --- /dev/null +++ b/src/main/java/attendance/time/DateTime.java @@ -0,0 +1,11 @@ +package attendance.time; + +import camp.nextstep.edu.missionutils.DateTimes; +import java.time.LocalDate; + +public class DateTime { + + public static LocalDate now() { + return DateTimes.now().toLocalDate(); + } +} diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java index b323051..6d4c543 100644 --- a/src/main/java/attendance/util/InputParser.java +++ b/src/main/java/attendance/util/InputParser.java @@ -1,5 +1,6 @@ package attendance.util; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -40,4 +41,10 @@ public static String parseChoice(String rawChoice) { Validator.validateChoiceFormat(rawChoice); return rawChoice; } + + public static LocalTime parseTime(String rawTime) { + rawTime = rawTime.strip(); + Validator.validateTimeFormat(rawTime); + return LocalTime.parse(rawTime); + } } diff --git a/src/main/java/attendance/util/Validator.java b/src/main/java/attendance/util/Validator.java index 61a9f05..e08a8cb 100644 --- a/src/main/java/attendance/util/Validator.java +++ b/src/main/java/attendance/util/Validator.java @@ -6,6 +6,7 @@ public final class Validator { private static final String CSV_FORMAT = "^ *(\\[[가-힣a-zA-Z]+-\\d+])+ *(, *(\\[[가-힣a-zA-Z]+-\\d+])+ *)*$"; private static final String CHOICE = " *[1234Q] *"; + private static final String TIME_FORMAT = "\\d{2}:\\d{2}"; private Validator() { } @@ -20,4 +21,10 @@ public static void validateChoiceFormat(String rawChoice) { throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); } } + + public static void validateTimeFormat(String rawTime) { + if (!rawTime.matches(TIME_FORMAT)) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } + } } diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 82ada31..2ee012a 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -24,4 +24,14 @@ public static String readChoice(LocalDate nowDate) { System.out.println("Q. 종료"); return Console.readLine(); } + + public static String readName() { + System.out.println("닉네임을 입력해주세요."); + return Console.readLine(); + } + + public static String readTime() { + System.out.println("등교 시간을 입력해 주세요."); + return Console.readLine(); + } } From 13a5324fc154ed2b09b96e33cfdd6c3e139a78d8 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 16:27:29 +0900 Subject: [PATCH 08/12] =?UTF-8?q?fix:=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=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/attendance/Application.java | 5 +++-- src/main/java/attendance/domain/Crew.java | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index 5d6560c..6ff51ae 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -32,8 +32,9 @@ public static void main(String[] args) throws IOException { attendances.addAttendance(name, date, time); } -// System.out.println(attendances.getCrews()); - + for (Crew crew : attendances.getCrews()) { + System.out.println(crew); + } while (true) { // LocalDate nowDate = DateTime.now(); LocalDate nowDate = LocalDate.of(2024, 12, 13); diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index 1b387f2..d0e0db2 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -103,6 +103,11 @@ public String getInfoAt(LocalDate nowDate) { int minute = attendance.getTime().getMinute(); String state = attendance.getState(); + if (state.equals("결석")) { + return "\n" + month + "월 " + day + "일 " + dayOfWeek + "요일 " + + "--:--" + " (" + state + ")"; + } + return "\n" + month + "월 " + day + "일 " + dayOfWeek + "요일 " + String.format("%02d:%02d", hour, minute) + " (" + state + ")"; } From 9723a8a27851cbb21d936091a79c95e92506fe9d Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 14 Dec 2025 18:50:02 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=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 --- src/main/java/attendance/Application.java | 122 +++++++++++++++- .../java/attendance/constant/Constant.java | 7 + .../attendance/constant/ErrorMessage.java | 19 +++ .../java/attendance/domain/Attendance.java | 26 +++- .../java/attendance/domain/Attendances.java | 33 +++++ src/main/java/attendance/domain/Crew.java | 138 ++++++++++++++++-- .../java/attendance/domain/DangerState.java | 4 + .../java/attendance/util/InputParser.java | 15 ++ .../java/attendance/util/NumberConvertor.java | 12 ++ src/main/java/attendance/util/Validator.java | 8 + .../java/attendance/util/file/FileWriter.java | 43 ++++++ src/main/java/attendance/view/InputView.java | 12 +- src/main/java/attendance/view/OutputView.java | 58 ++++++++ 13 files changed, 479 insertions(+), 18 deletions(-) create mode 100644 src/main/java/attendance/constant/Constant.java create mode 100644 src/main/java/attendance/constant/ErrorMessage.java create mode 100644 src/main/java/attendance/domain/DangerState.java create mode 100644 src/main/java/attendance/util/NumberConvertor.java create mode 100644 src/main/java/attendance/util/file/FileWriter.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 6ff51ae..f1bdc9d 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.java @@ -2,13 +2,16 @@ import attendance.domain.Attendances; import attendance.domain.Crew; +import attendance.time.DateTime; 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.time.LocalTime; import java.time.format.TextStyle; +import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -32,15 +35,20 @@ public static void main(String[] args) throws IOException { attendances.addAttendance(name, date, time); } - for (Crew crew : attendances.getCrews()) { - System.out.println(crew); - } while (true) { -// LocalDate nowDate = DateTime.now(); - LocalDate nowDate = LocalDate.of(2024, 12, 13); + LocalDate nowDate = DateTime.now(); +// LocalDate nowDate = LocalDate.of(2024, 12, 13); String rawChoice = InputView.readChoice(nowDate); String choice = InputParser.parseChoice(rawChoice); + List allDates = getAllDates(nowDate); + List allCrews = attendances.getCrews(); + for (Crew crew : allCrews) { + crew.addAbsenceDate(allDates); + } + + attendances.setDangerStatus(); + if (choice.equals("Q")) { break; } @@ -54,7 +62,7 @@ public static void main(String[] args) throws IOException { String.format("[ERROR] %d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); } - String name = InputView.readName(); + String name = InputView.readNameForRegisterAttendance(); if (!attendances.contains(name)) { throw new IllegalArgumentException("[ERROR] 등록되지 않은 닉네임입니다."); } @@ -74,21 +82,121 @@ public static void main(String[] args) throws IOException { Crew registerdCrew = attendances.registerAttendance(name, newTime, nowDate); System.out.println(registerdCrew.getInfoAt(nowDate)); + attendances.setDangerStatus(); + continue; } if (choice.equals("2")) { + String name = InputView.readNameForModifyAttendance(); + if (!attendances.contains(name)) { + throw new IllegalArgumentException("[ERROR] 등록되지 않은 닉네임입니다."); + } + + String rawModifiedDate = InputView.readDate(); + LocalDate modifiedDate = InputParser.parseDate(rawModifiedDate); + if (modifiedDate.isAfter(nowDate)) { + throw new IllegalArgumentException("[ERROR] 아직 수정할 수 없습니다."); + } + + int month = modifiedDate.getMonthValue(); + int day = modifiedDate.getDayOfMonth(); + String dayOfWeek = modifiedDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + if (dayOfWeek.matches("[토|일]") || modifiedDate.isEqual(LocalDate.of(2024, 12, 25))) { + throw new IllegalArgumentException( + String.format("[ERROR] %d월 %d일 %s요일은 등교일이 아닙니다.", month, day, dayOfWeek)); + } + + String rawTime = InputView.readTime(); + LocalTime newTime = InputParser.parseTime(rawTime); + LocalTime startTime = LocalTime.of(8, 0, 0); + LocalTime endTime = LocalTime.of(23, 0, 0); + if (newTime.isBefore(startTime) || newTime.isAfter(endTime)) { + throw new IllegalArgumentException("[ERROR] 캠퍼스 운영 시간에만 출석이 가능합니다."); + } + + List crews = attendances.modifyAttendance(name, newTime, modifiedDate); + System.out.println(crews.get(0).getInfoAt(modifiedDate) + " -> " + + crews.get(1).getInfoAt(modifiedDate) + " 수정 완료!"); + + attendances.setDangerStatus(); + continue; } if (choice.equals("3")) { + String name = InputView.readNameForModifyAttendance(); + if (!attendances.contains(name)) { + throw new IllegalArgumentException("[ERROR] 등록되지 않은 닉네임입니다."); + } + + System.out.printf("이번 달 %s의 출석 기록입니다.\n", name); + Crew crew = attendances.getCrew(name); + List dates = crew.getDates(); + for (LocalDate date : dates) { + System.out.print("\n" + crew.getInfoAt(date)); + } + System.out.println(); + + int attendanceCount = crew.getAttendanceCount(); + int lateCount = crew.getLateCount(); + int absenceCount = crew.getAbsenceCount(); + System.out.println("\n출석: " + attendanceCount + "회"); + System.out.println("지각: " + lateCount + "회"); + System.out.println("결석: " + absenceCount + "회"); + + System.out.println("\n" + crew.getDangerState() + " 대상자입니다."); + continue; } if (choice.equals("4")) { - + List crews = attendances.getCrews(); + System.out.println("제적 위험자 조회 결과"); + for (Crew crew : crews) { + System.out.printf("- %s: 결석 %d회, 지각 %d회 (%s)\n", crew.getName(), crew.getAbsenceCount(), + crew.getLateCount(), crew.getDangerState()); + } } } } + + private static List getAllDates(LocalDate nowDate) { + List dates = List.of( + LocalDate.of(2024, 12, 2), + LocalDate.of(2024, 12, 3), + LocalDate.of(2024, 12, 4), + LocalDate.of(2024, 12, 5), + LocalDate.of(2024, 12, 6), + + LocalDate.of(2024, 12, 9), + LocalDate.of(2024, 12, 10), + LocalDate.of(2024, 12, 11), + LocalDate.of(2024, 12, 12), + LocalDate.of(2024, 12, 13), + + LocalDate.of(2024, 12, 16), + LocalDate.of(2024, 12, 17), + LocalDate.of(2024, 12, 18), + LocalDate.of(2024, 12, 19), + LocalDate.of(2024, 12, 20), + + LocalDate.of(2024, 12, 23), + LocalDate.of(2024, 12, 24), + LocalDate.of(2024, 12, 26), + LocalDate.of(2024, 12, 27), + + LocalDate.of(2024, 12, 30), + LocalDate.of(2024, 12, 31) + ); + + List resultDate = new ArrayList<>(); + for (LocalDate date : dates) { + if (date.isBefore(nowDate)) { + resultDate.add(date); + } + } + return resultDate; + } } diff --git a/src/main/java/attendance/constant/Constant.java b/src/main/java/attendance/constant/Constant.java new file mode 100644 index 0000000..41c9da6 --- /dev/null +++ b/src/main/java/attendance/constant/Constant.java @@ -0,0 +1,7 @@ +package attendance.constant; + +public final class Constant { + +// public static final int = ; +// public static final String = ; +} diff --git a/src/main/java/attendance/constant/ErrorMessage.java b/src/main/java/attendance/constant/ErrorMessage.java new file mode 100644 index 0000000..d571980 --- /dev/null +++ b/src/main/java/attendance/constant/ErrorMessage.java @@ -0,0 +1,19 @@ +package attendance.constant; + +public enum ErrorMessage { + + FORMAT_ERROR("잘못된 형식을 입력하였습니다."), + EXCEED_ERROR("재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요."), + INVALID_ERROR("잘못된 입력입니다. 다시 입력해 주세요."); + + private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage() { + return ERROR_MESSAGE_PREFIX + errorMessage; + } +} diff --git a/src/main/java/attendance/domain/Attendance.java b/src/main/java/attendance/domain/Attendance.java index cf8ca42..3730b22 100644 --- a/src/main/java/attendance/domain/Attendance.java +++ b/src/main/java/attendance/domain/Attendance.java @@ -3,7 +3,7 @@ import java.time.LocalDate; import java.time.LocalTime; -public class Attendance { +public class Attendance implements Comparable { private LocalDate date; private String dayOfWeek; @@ -42,4 +42,28 @@ public String toString() { ", state='" + state + '\'' + '}'; } + + public void modify(LocalDate modifiedDate, String dayOfWeek, LocalTime newTime, String attendanceState) { + this.date = modifiedDate; + this.dayOfWeek = dayOfWeek; + this.time = newTime; + this.state = attendanceState; + } + + public Attendance clone() { + return new Attendance(this.date, this.dayOfWeek, this.time, this.state); + } + + @Override + public int compareTo(Attendance o) { + if (this.date.isBefore(o.date)) { + return -1; + } + + if (this.date.isAfter(o.date)) { + return 1; + } + + return 0; + } } diff --git a/src/main/java/attendance/domain/Attendances.java b/src/main/java/attendance/domain/Attendances.java index 9b2316f..efa4e3e 100644 --- a/src/main/java/attendance/domain/Attendances.java +++ b/src/main/java/attendance/domain/Attendances.java @@ -1,9 +1,12 @@ package attendance.domain; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; public class Attendances { @@ -58,6 +61,36 @@ public Crew registerAttendance(String name, LocalTime newTime, LocalDate nowDate } public List getCrews() { + Collections.sort(crews); return crews; } + + public Crew getCrew(String name) { + for (Crew crew : crews) { + if (crew.equals(new Crew(name))) { + return crew; + } + } + return null; + } + + public List modifyAttendance(String name, LocalTime newTime, LocalDate modifiedDate) { + List crews = new ArrayList<>(); + Crew checkCrew = new Crew(name); + for (Crew crew : this.crews) { + if (crew.equals(checkCrew)) { + crews.add(crew.clone()); + crew.modifyAttendance(newTime, modifiedDate); + crews.add(crew); + return crews; + } + } + return null; + } + + public void setDangerStatus() { + for (Crew crew : crews) { + crew.setDangerStatus(); + } + } } diff --git a/src/main/java/attendance/domain/Crew.java b/src/main/java/attendance/domain/Crew.java index d0e0db2..adb3830 100644 --- a/src/main/java/attendance/domain/Crew.java +++ b/src/main/java/attendance/domain/Crew.java @@ -4,15 +4,17 @@ import java.time.LocalTime; import java.time.format.TextStyle; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; -public class Crew { +public class Crew implements Comparable { private String name; - private DangerState state; + private String dangerState; private List attendances; + private int attendanceCount; private int lateCount; private int absenceCount; @@ -21,6 +23,14 @@ public Crew(String name) { attendances = new ArrayList<>(); } + public Crew(String name, List attendances, int lateCount, int absenceCount, String dangerState) { + this.name = name; + this.attendances = attendances; + this.lateCount = lateCount; + this.absenceCount = absenceCount; + this.dangerState = dangerState; + } + @Override public boolean equals(Object object) { if (object == null || getClass() != object.getClass()) { @@ -47,7 +57,7 @@ public void registerDateAndTime(LocalDate date, LocalTime time) { public String toString() { return "Crew{" + "name='" + name + '\'' + - ", state=" + state + + ", state=" + dangerState + ", attendances=" + attendances + ", lateCount=" + lateCount + ", absenceCount=" + absenceCount + @@ -82,14 +92,21 @@ private String getAttendanceState(LocalTime time, String dayOfWeek) { } String attendanceState = "출석"; - if (time.isAfter(lateTime)) { - attendanceState = "지각"; - lateCount++; - } + if (time.isAfter(absenceTime)) { attendanceState = "결석"; absenceCount++; + return attendanceState; } + + if (time.isAfter(lateTime)) { + attendanceState = "지각"; + lateCount++; + return attendanceState; + } + + attendanceCount++; + return attendanceState; } @@ -104,14 +121,117 @@ public String getInfoAt(LocalDate nowDate) { String state = attendance.getState(); if (state.equals("결석")) { - return "\n" + month + "월 " + day + "일 " + dayOfWeek + "요일 " + return month + "월 " + day + "일 " + dayOfWeek + "요일 " + "--:--" + " (" + state + ")"; } - return "\n" + month + "월 " + day + "일 " + dayOfWeek + "요일 " + return month + "월 " + day + "일 " + dayOfWeek + "요일 " + String.format("%02d:%02d", hour, minute) + " (" + state + ")"; } } return null; } + + public Crew clone() { + List copyAttendances = new ArrayList<>(); + for (Attendance attendance : attendances) { + copyAttendances.add(attendance.clone()); + } + return new Crew(this.name, copyAttendances, this.lateCount, this.absenceCount, this.dangerState); + } + + public void modifyAttendance(LocalTime newTime, LocalDate modifiedDate) { + String dayOfWeek = modifiedDate.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + for (Attendance attendance : attendances) { + if (attendance.getDate().isEqual(modifiedDate)) { + attendance.modify(modifiedDate, dayOfWeek, newTime, getAttendanceState(newTime, dayOfWeek)); + return; + } + } + attendances.add(new Attendance(modifiedDate, dayOfWeek, newTime, getAttendanceState(newTime, dayOfWeek))); + } + + public int getLateCount() { + return lateCount; + } + + public int getAbsenceCount() { + return absenceCount; + } + + public int getAttendanceCount() { + return attendanceCount; + } + + public String getDangerState() { + return dangerState; + } + + public List getDates() { + List dates = new ArrayList<>(); + Collections.sort(attendances); + for (Attendance attendance : attendances) { + dates.add(attendance.getDate()); + } + return dates; + } + + public void addAbsenceDate(List allDates) { + for (LocalDate date : allDates) { + boolean isContain = false; + for (Attendance attendance : attendances) { + if (attendance.getDate().isEqual(date)) { + isContain = true; + break; + } + } + if (!isContain) { + this.absenceCount++; + String dayOfWeek = date.getDayOfWeek().getDisplayName(TextStyle.NARROW, Locale.KOREAN); + attendances.add(new Attendance(date, dayOfWeek, LocalTime.of(0,0), "결석")); + } + } + } + + public void setDangerStatus() { + int absenceCount = this.absenceCount + lateCount / 3; + + if (absenceCount > 5) { + dangerState = "제적"; + return; + } + + if (absenceCount >= 3) { + dangerState = "면담"; + return; + } + + if (absenceCount >= 2) { + dangerState = "경고"; + return; + } + + dangerState = ""; + } + + @Override + public int compareTo(Crew o) { + if (this.absenceCount > o.absenceCount) { + return -1; + } + + if (this.absenceCount == o.absenceCount) { + if (this.lateCount > o.lateCount) { + return -1; + } + + if (this.lateCount < o.lateCount) { + return 1; + } + + return 0; + } + + return 0; + } } diff --git a/src/main/java/attendance/domain/DangerState.java b/src/main/java/attendance/domain/DangerState.java new file mode 100644 index 0000000..ded3b40 --- /dev/null +++ b/src/main/java/attendance/domain/DangerState.java @@ -0,0 +1,4 @@ +package attendance.domain; + +public enum DangerState { +} diff --git a/src/main/java/attendance/util/InputParser.java b/src/main/java/attendance/util/InputParser.java index 6d4c543..f771986 100644 --- a/src/main/java/attendance/util/InputParser.java +++ b/src/main/java/attendance/util/InputParser.java @@ -1,5 +1,8 @@ package attendance.util; +import static attendance.constant.ErrorMessage.FORMAT_ERROR; + +import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; @@ -47,4 +50,16 @@ public static LocalTime parseTime(String rawTime) { Validator.validateTimeFormat(rawTime); return LocalTime.parse(rawTime); } + + public static LocalDate parseDate(String rawModifiedDate) { + rawModifiedDate = rawModifiedDate.strip(); + if (!rawModifiedDate.matches("\\d+")) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } + int date = NumberConvertor.convertToNumber(rawModifiedDate); + if (date < 1 || date > 31) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } + return LocalDate.of(2024, 12, date); + } } diff --git a/src/main/java/attendance/util/NumberConvertor.java b/src/main/java/attendance/util/NumberConvertor.java new file mode 100644 index 0000000..908f5c1 --- /dev/null +++ b/src/main/java/attendance/util/NumberConvertor.java @@ -0,0 +1,12 @@ +package attendance.util; + +public final class NumberConvertor { + + public static Integer convertToNumber(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/attendance/util/Validator.java b/src/main/java/attendance/util/Validator.java index e08a8cb..8633b9f 100644 --- a/src/main/java/attendance/util/Validator.java +++ b/src/main/java/attendance/util/Validator.java @@ -16,6 +16,7 @@ public static void validateCsvFormat(String input) { throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); } } + public static void validateChoiceFormat(String rawChoice) { if (!rawChoice.matches(CHOICE)) { throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); @@ -26,5 +27,12 @@ public static void validateTimeFormat(String rawTime) { if (!rawTime.matches(TIME_FORMAT)) { throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); } + + String[] split = rawTime.split(":"); + int hour = Integer.parseInt(split[0]); + int min = Integer.parseInt(split[1]); + if (hour < 0 || hour > 23 || min < 0 || min > 59) { + throw new IllegalArgumentException(FORMAT_ERROR.getErrorMessage()); + } } } diff --git a/src/main/java/attendance/util/file/FileWriter.java b/src/main/java/attendance/util/file/FileWriter.java new file mode 100644 index 0000000..fea7e9e --- /dev/null +++ b/src/main/java/attendance/util/file/FileWriter.java @@ -0,0 +1,43 @@ +package attendance.util.file; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; + +public class FileWriter { + + private final String fileName; + private java.io.FileWriter fw; + + public FileWriter(String fileName) { + this.fileName = fileName; + } + + // 파일에 내용 한 번에 쓰기 - append 기본값(false) + public void writeAll(String contents) throws IOException { + fw = new java.io.FileWriter(fileName, UTF_8, false); + fw.write(contents); + fw.close(); + } + + // 파일에 내용 한 번에 쓰기 - append 선택 + public void writeAll(String contents, boolean append) throws IOException { + fw = new java.io.FileWriter(fileName, UTF_8, append); + fw.write(contents); + fw.close(); + } + + // 파일에 내용 한 줄씩 쓰기 - append 기본값(false) + public void writeLine(String contents) throws IOException { + fw = new java.io.FileWriter(fileName, UTF_8, false); + fw.write(contents); + fw.close(); + } + + // 파일에 내용 한 줄씩 쓰기 - append 선택 + public void writeLine(String contents, boolean append) throws IOException { + fw = new java.io.FileWriter(fileName, UTF_8, append); + fw.write(contents); + fw.close(); + } +} diff --git a/src/main/java/attendance/view/InputView.java b/src/main/java/attendance/view/InputView.java index 2ee012a..68e75ee 100644 --- a/src/main/java/attendance/view/InputView.java +++ b/src/main/java/attendance/view/InputView.java @@ -25,13 +25,23 @@ public static String readChoice(LocalDate nowDate) { return Console.readLine(); } - public static String readName() { + public static String readNameForRegisterAttendance() { System.out.println("닉네임을 입력해주세요."); return Console.readLine(); } + public static String readNameForModifyAttendance() { + System.out.println("출석을 수정하려는 크루의 닉네임을 입력해주세요."); + return Console.readLine(); + } + public static String readTime() { System.out.println("등교 시간을 입력해 주세요."); return Console.readLine(); } + + public static String readDate() { + 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 0000000..977aaf7 --- /dev/null +++ b/src/main/java/attendance/view/OutputView.java @@ -0,0 +1,58 @@ +//package attendance.view; +// +//import java.util.Map; +//import store.domain.Product; +//import store.dto.ReceiptDto; +//import store.dto.StockDto; +// +//public class OutputView { +// +// private static final String WELL_COME_MESSAGE = "\n안녕하세요. W편의점입니다.\n현재 보유하고 있는 상품입니다.\n"; +// +// public static void printStock(StockDto stock) { +// System.out.println(WELL_COME_MESSAGE); +// System.out.println(stock.stock()); +// } +// +// public static void printErrorMessage(IllegalArgumentException e) { +// System.out.println(e.getMessage()); +// } +// +// public static void printReceipt(ReceiptDto receiptDto) { +// Map purchaseProducts = receiptDto.purchaseProducts(); +// Map presentProducts = receiptDto.presentProducts(); +// int membershipDiscountAmount = receiptDto.membershipDiscountAmount(); +// +// int totalPurchaseQuantity = 0; +// int totalPurchasePrice = 0; +// int totalPresentPrice = 0; +// +// +// System.out.println("\n============W 편의점============"); +// System.out.println("상품명 수량 금액"); +// for (Product product : purchaseProducts.keySet()) { +// int quantity = purchaseProducts.get(product); +// int price = product.getPrice(quantity); +// System.out.printf("%-16s%-8s%,d\n", product.getName(), quantity, price); +// +// totalPurchaseQuantity += quantity; +// totalPurchasePrice += price; +// } +// +// System.out.println("============증 정============"); +// for (Product product : presentProducts.keySet()) { +// int quantity = presentProducts.get(product); +// int price = product.getPrice(quantity); +// System.out.printf("%-16s%d\n", product.getName(), quantity); +// +// totalPresentPrice += price; +// } +// +// int finalPrice = totalPurchasePrice - totalPresentPrice - membershipDiscountAmount; +// System.out.println("=============================="); +// System.out.printf("%-15s%-8s%,d\n", "총구매액", totalPurchaseQuantity, totalPurchasePrice); +// System.out.printf("%-23s-%,d\n", "행사할인", totalPresentPrice); +// System.out.printf("%-23s-%,d\n", "멤버십할인", membershipDiscountAmount); +// System.out.printf("%-24s%,d\n", "내실돈", finalPrice); +// } +//} From 73769595b3f425ef902e25792e947c7a7b658f34 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Wed, 17 Dec 2025 19:14:38 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=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 --- .../controller/AttendanceController.java | 19 +++++++++++++++++++ .../attendance/service/AttendanceService.java | 8 ++++++++ src/test/java/attendance/ApplicationTest.java | 7 ++----- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/main/java/attendance/controller/AttendanceController.java create mode 100644 src/main/java/attendance/service/AttendanceService.java diff --git a/src/main/java/attendance/controller/AttendanceController.java b/src/main/java/attendance/controller/AttendanceController.java new file mode 100644 index 0000000..19db5a6 --- /dev/null +++ b/src/main/java/attendance/controller/AttendanceController.java @@ -0,0 +1,19 @@ +package attendance.controller; + +import attendance.service.AttendanceService; + +public class AttendanceController { + + private final AttendanceService attendanceService; + + public AttendanceController(AttendanceService attendanceService) { + this.attendanceService = attendanceService; + } + + public void run() { + + } + + +} + diff --git a/src/main/java/attendance/service/AttendanceService.java b/src/main/java/attendance/service/AttendanceService.java new file mode 100644 index 0000000..4d56247 --- /dev/null +++ b/src/main/java/attendance/service/AttendanceService.java @@ -0,0 +1,8 @@ +package attendance.service; + +public class AttendanceService { + + // 도메인 객체 인스턴스 변수로 저장 + + // 메서드 +} diff --git a/src/test/java/attendance/ApplicationTest.java b/src/test/java/attendance/ApplicationTest.java index e089cb0..c1dfa71 100644 --- a/src/test/java/attendance/ApplicationTest.java +++ b/src/test/java/attendance/ApplicationTest.java @@ -11,6 +11,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; class ApplicationTest extends NsTest { + @Test void 잘못된_형식_예외_테스트() { assertNowTest( @@ -80,10 +81,6 @@ class ApplicationTest extends NsTest { @Override protected void runMain() { - try { - Application.main(new String[]{}); - } catch (IOException e) { - throw new RuntimeException(e); - } + Application.main(new String[]{}); } } From 10a03171ff5773214acee90ca51321a7ec65459e Mon Sep 17 00:00:00 2001 From: khcho96 Date: Wed, 17 Dec 2025 19:14:42 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=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 --- src/main/java/attendance/Application.java | 1 - src/main/java/attendance/constant/ErrorMessage.java | 8 ++++---- src/main/java/attendance/domain/DangerState.java | 4 ++++ src/main/java/attendance/util/file/FileReader.java | 13 ------------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/main/java/attendance/Application.java b/src/main/java/attendance/Application.java index f1bdc9d..be71cdb 100644 --- a/src/main/java/attendance/Application.java +++ b/src/main/java/attendance/Application.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.time.LocalTime; diff --git a/src/main/java/attendance/constant/ErrorMessage.java b/src/main/java/attendance/constant/ErrorMessage.java index d571980..fc1984f 100644 --- a/src/main/java/attendance/constant/ErrorMessage.java +++ b/src/main/java/attendance/constant/ErrorMessage.java @@ -3,8 +3,8 @@ public enum ErrorMessage { FORMAT_ERROR("잘못된 형식을 입력하였습니다."), - EXCEED_ERROR("재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요."), - INVALID_ERROR("잘못된 입력입니다. 다시 입력해 주세요."); + ERROR("%d월 %d일 %s요일은 등교일이 아닙니다."), + ; private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; private final String errorMessage; @@ -13,7 +13,7 @@ public enum ErrorMessage { this.errorMessage = errorMessage; } - public String getErrorMessage() { - return ERROR_MESSAGE_PREFIX + errorMessage; + public String getErrorMessage(Object... args) { + return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); } } diff --git a/src/main/java/attendance/domain/DangerState.java b/src/main/java/attendance/domain/DangerState.java index ded3b40..13ccf85 100644 --- a/src/main/java/attendance/domain/DangerState.java +++ b/src/main/java/attendance/domain/DangerState.java @@ -1,4 +1,8 @@ package attendance.domain; public enum DangerState { + 위험, + 출석, + 지각, + ; } diff --git a/src/main/java/attendance/util/file/FileReader.java b/src/main/java/attendance/util/file/FileReader.java index 20c8ca9..a77afe2 100644 --- a/src/main/java/attendance/util/file/FileReader.java +++ b/src/main/java/attendance/util/file/FileReader.java @@ -15,19 +15,6 @@ public FileReader(String fileName) throws IOException { fr = new java.io.FileReader(fileName, UTF_8); } - // 파일 전체를 한 번에 읽기 - public String readAll() throws IOException { - StringBuilder contents = new StringBuilder(); - int ch; - while ((ch = fr.read()) != -1) { - contents.append((char) ch); - } - fr.close(); - - return contents.toString(); - } - - // 파일 전체를 한 줄 씩 나눠서 읽기 public List readLines() throws IOException { List contents = new ArrayList<>(); BufferedReader br = new BufferedReader(fr); From 83afe910201ca4a53ae43665edea848977915a15 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Wed, 17 Dec 2025 19:15:01 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=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 --- src/test/java/attendance/ApplicationTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/attendance/ApplicationTest.java b/src/test/java/attendance/ApplicationTest.java index c1dfa71..0cf183f 100644 --- a/src/test/java/attendance/ApplicationTest.java +++ b/src/test/java/attendance/ApplicationTest.java @@ -81,6 +81,10 @@ class ApplicationTest extends NsTest { @Override protected void runMain() { - Application.main(new String[]{}); + try { + Application.main(new String[]{}); + } catch (IOException e) { + throw new RuntimeException(e); + } } }