From 42609e45472014064b7162cccee49290a9ca49b7 Mon Sep 17 00:00:00 2001 From: Tran Huu Danh Date: Tue, 5 May 2026 21:50:37 +0700 Subject: [PATCH] cc --- .devcontainer/devcontainer.json | 8 +- .../com/drinkool/dtos/AttendanceRequest.java | 6 + .../java/com/drinkool/dtos/ShiftRequest.java | 10 ++ .../main/java/com/drinkool/enums/Role.java | 5 + .../java/com/drinkool/enums/ShiftStatus.java | 8 ++ .../java/com/drinkool/models/Attendance.java | 20 ++++ .../main/java/com/drinkool/models/Shift.java | 30 +++++ .../com/drinkool/models/ShiftTemlate.java | 19 +++ .../main/java/com/drinkool/models/Staff.java | 22 ++++ .../src/main/resources/ShiftResource.java | 113 ++++++++++++++++++ k8s/deploy-all.yaml | 94 +++++++++++++++ 11 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 eatery-service/src/main/java/com/drinkool/dtos/AttendanceRequest.java create mode 100644 eatery-service/src/main/java/com/drinkool/dtos/ShiftRequest.java create mode 100644 eatery-service/src/main/java/com/drinkool/enums/Role.java create mode 100644 eatery-service/src/main/java/com/drinkool/enums/ShiftStatus.java create mode 100644 eatery-service/src/main/java/com/drinkool/models/Attendance.java create mode 100644 eatery-service/src/main/java/com/drinkool/models/Shift.java create mode 100644 eatery-service/src/main/java/com/drinkool/models/ShiftTemlate.java create mode 100644 eatery-service/src/main/java/com/drinkool/models/Staff.java create mode 100644 eatery-service/src/main/resources/ShiftResource.java create mode 100644 k8s/deploy-all.yaml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e44a899..2ee07b3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,9 +3,9 @@ { "build": { "dockerfile": "Dockerfile", - "args": { - "http_proxy": "http://host.docker.internal:3142" - }, + // "args": { + // "http_proxy": "http://host.docker.internal:3142" + // }, "options": ["--add-host=host.docker.internal:host-gateway"] }, "runArgs": ["--add-host=host.docker.internal:host-gateway"], @@ -30,7 +30,7 @@ "containerEnv": { "TESTCONTAINERS_RYUK_DISABLED": "true", "QUARKUS_DEBUG_HOST": "0.0.0.0", - "http_proxy": "http://host.docker.internal:3142" + // "http_proxy": "http://host.docker.internal:3142" }, "postStartCommand": "redis-server --daemonize yes", "postCreateCommand": "git config --global --add safe.directory '*'", diff --git a/eatery-service/src/main/java/com/drinkool/dtos/AttendanceRequest.java b/eatery-service/src/main/java/com/drinkool/dtos/AttendanceRequest.java new file mode 100644 index 0000000..47eeb59 --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/dtos/AttendanceRequest.java @@ -0,0 +1,6 @@ +package com.drinkool.dtos; + +public class AttendanceRequest { + public Long shiftId; + public String note; +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/dtos/ShiftRequest.java b/eatery-service/src/main/java/com/drinkool/dtos/ShiftRequest.java new file mode 100644 index 0000000..a9624c4 --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/dtos/ShiftRequest.java @@ -0,0 +1,10 @@ +package com.drinkool.dtos; + +import java.time.LocalDateTime; + +public class ShiftRequest { + public Long staffId; + public Long templateId; // Gửi lên nếu dùng mẫu có sẵn + public LocalDateTime startTime; // Tùy chỉnh tự do + public LocalDateTime endTime; // Tùy chỉnh tự do +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/enums/Role.java b/eatery-service/src/main/java/com/drinkool/enums/Role.java new file mode 100644 index 0000000..af82227 --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/enums/Role.java @@ -0,0 +1,5 @@ +package com.drinkool.enums; + +public enum Role { + MANAGER, STAFF +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/enums/ShiftStatus.java b/eatery-service/src/main/java/com/drinkool/enums/ShiftStatus.java new file mode 100644 index 0000000..7f1d555 --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/enums/ShiftStatus.java @@ -0,0 +1,8 @@ +package com.drinkool.enums; + +public enum ShiftStatus { + SCHEDULED, // Đã xếp lịch + IN_PROGRESS, // Đang diễn ra + COMPLETED, // Đã hoàn thành + CANCELLED // Đã hủy +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/models/Attendance.java b/eatery-service/src/main/java/com/drinkool/models/Attendance.java new file mode 100644 index 0000000..0eaad78 --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/models/Attendance.java @@ -0,0 +1,20 @@ +package com.drinkool.models; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "attendance") +public class Attendance extends PanacheEntity { + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "shift_id", nullable = false, unique = true) + public Shift shift; + + public LocalDateTime checkInTime; + public LocalDateTime checkOutTime; + + @Column(length = 500) + public String note; // Ghi chú đi trễ, về sớm... +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/models/Shift.java b/eatery-service/src/main/java/com/drinkool/models/Shift.java new file mode 100644 index 0000000..e977d04 --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/models/Shift.java @@ -0,0 +1,30 @@ +package com.drinkool.models; + +import com.drinkool.enums.ShiftStatus; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "shift") +public class Shift extends PanacheEntity { + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "staff_id", nullable = false) + public Staff staff; + + // Giờ linh hoạt, không bắt buộc phải khớp 100% với template + @Column(nullable = false) + public LocalDateTime startTime; + + @Column(nullable = false) + public LocalDateTime endTime; + + @Enumerated(EnumType.STRING) + public ShiftStatus status = ShiftStatus.SCHEDULED; + + // Lấy danh sách ca làm của 1 nhân viên trong khoảng thời gian + public static java.util.List findByStaffAndDateRange(Long staffId, LocalDateTime start, LocalDateTime end) { + return find("staff.id = ?1 and startTime >= ?2 and endTime <= ?3", staffId, start, end).list(); + } +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/models/ShiftTemlate.java b/eatery-service/src/main/java/com/drinkool/models/ShiftTemlate.java new file mode 100644 index 0000000..6e0b71c --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/models/ShiftTemlate.java @@ -0,0 +1,19 @@ +package com.drinkool.models; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.*; +import java.time.LocalTime; + +@Entity +@Table(name = "shift_template") +public class ShiftTemplate extends PanacheEntity { + + @Column(nullable = false) + public String templateName; // VD: Ca Sáng, Ca Chiều + + @Column(nullable = false) + public LocalTime defaultStartTime; // Giờ bắt đầu mặc định + + @Column(nullable = false) + public LocalTime defaultEndTime; // Giờ kết thúc mặc định +} \ No newline at end of file diff --git a/eatery-service/src/main/java/com/drinkool/models/Staff.java b/eatery-service/src/main/java/com/drinkool/models/Staff.java new file mode 100644 index 0000000..8aae31e --- /dev/null +++ b/eatery-service/src/main/java/com/drinkool/models/Staff.java @@ -0,0 +1,22 @@ +package com.drinkool.models; + +import com.drinkool.enums.Role; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import jakarta.persistence.*; + +@Entity +@Table(name = "staff") +public class Staff extends PanacheEntity { + + @Column(nullable = false, unique = true) + public String staffCode; // Mã NV (vd: NV001) + + @Column(nullable = false) + public String name; + + @Enumerated(EnumType.STRING) + public Role role; + + // Thuộc tính để link sang account-service nếu cần + // public Long accountUserId; +} \ No newline at end of file diff --git a/eatery-service/src/main/resources/ShiftResource.java b/eatery-service/src/main/resources/ShiftResource.java new file mode 100644 index 0000000..16aee88 --- /dev/null +++ b/eatery-service/src/main/resources/ShiftResource.java @@ -0,0 +1,113 @@ +package com.drinkool.resources; + +import com.drinkool.dtos.AttendanceRequest; +import com.drinkool.dtos.ShiftRequest; +import com.drinkool.enums.ShiftStatus; +import com.drinkool.models.Attendance; +import com.drinkool.models.Shift; +import com.drinkool.models.ShiftTemplate; +import com.drinkool.models.Staff; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.time.LocalDateTime; +import java.util.List; + +@Path("/api/shifts") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ShiftResource { + + // --- QUẢN LÝ CA LÀM (XẾP LỊCH) --- + + @POST + @Path("/schedule") + @Transactional + public Response scheduleShift(ShiftRequest request) { + Staff staff = Staff.findById(request.staffId); + if (staff == null) { + throw new BadRequestException("Staff không tồn tại"); + } + + Shift shift = new Shift(); + shift.staff = staff; + shift.status = ShiftStatus.SCHEDULED; + + // Nếu có truyền templateId, lấy giờ từ template (có thể kết hợp với ngày để tạo LocalDateTime) + if (request.templateId != null) { + ShiftTemplate template = ShiftTemplate.findById(request.templateId); + if (template != null) { + // Giả sử xếp cho ngày hiện tại dựa trên template + shift.startTime = LocalDateTime.now().with(template.defaultStartTime); + shift.endTime = LocalDateTime.now().with(template.defaultEndTime); + } + } else { + // Sử dụng giờ tùy chỉnh hoàn toàn + shift.startTime = request.startTime; + shift.endTime = request.endTime; + } + + shift.persist(); + return Response.status(Response.Status.CREATED).entity(shift).build(); + } + + @GET + @Path("/staff/{staffId}") + public Response getShiftsByStaff(@PathParam("staffId") Long staffId, + @QueryParam("start") String startStr, + @QueryParam("end") String endStr) { + // Cần parse chuỗi ISO thành LocalDateTime, ở đây làm mẫu đơn giản + LocalDateTime start = startStr != null ? LocalDateTime.parse(startStr) : LocalDateTime.now().minusDays(7); + LocalDateTime end = endStr != null ? LocalDateTime.parse(endStr) : LocalDateTime.now().plusDays(7); + + List shifts = Shift.findByStaffAndDateRange(staffId, start, end); + return Response.ok(shifts).build(); + } + + // --- ĐIỂM DANH (CHECK-IN / CHECK-OUT) --- + + @POST + @Path("/check-in") + @Transactional + public Response checkIn(AttendanceRequest request) { + Shift shift = Shift.findById(request.shiftId); + if (shift == null) throw new BadRequestException("Ca làm không tồn tại"); + + Attendance attendance = Attendance.find("shift", shift).firstResult(); + if (attendance != null) throw new BadRequestException("Đã check-in cho ca này rồi"); + + attendance = new Attendance(); + attendance.shift = shift; + attendance.checkInTime = LocalDateTime.now(); + attendance.note = request.note; + attendance.persist(); + + shift.status = ShiftStatus.IN_PROGRESS; + shift.persist(); + + return Response.accepted(attendance).build(); + } + + @POST + @Path("/check-out") + @Transactional + public Response checkOut(AttendanceRequest request) { + Shift shift = Shift.findById(request.shiftId); + if (shift == null) throw new BadRequestException("Ca làm không tồn tại"); + + Attendance attendance = Attendance.find("shift", shift).firstResult(); + if (attendance == null) throw new BadRequestException("Chưa check-in nên không thể check-out"); + + attendance.checkOutTime = LocalDateTime.now(); + if (request.note != null) { + attendance.note += " | " + request.note; + } + attendance.persist(); + + shift.status = ShiftStatus.COMPLETED; + shift.persist(); + + return Response.accepted(attendance).build(); + } +} \ No newline at end of file diff --git a/k8s/deploy-all.yaml b/k8s/deploy-all.yaml new file mode 100644 index 0000000..39748d1 --- /dev/null +++ b/k8s/deploy-all.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis +spec: + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:alpine + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-service +spec: + selector: + app: redis + ports: + - port: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: account-service +spec: + selector: + matchLabels: + app: account-service + template: + metadata: + labels: + app: account-service + spec: + containers: + - name: account-service + image: drinkool/account-service:1.0.0-SNAPSHOT + imagePullPolicy: IfNotPresent + env: + - name: QUARKUS_REDIS_HOST + value: "redis-service" + ports: + - containerPort: 8081 +--- +apiVersion: v1 +kind: Service +metadata: + name: account-service +spec: + selector: + app: account-service + ports: + - port: 8081 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gateway-service +spec: + selector: + matchLabels: + app: gateway-service + template: + metadata: + labels: + app: gateway-service + spec: + containers: + - name: gateway-service + image: drinkool/gateway-service:1.0.0-SNAPSHOT + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: gateway-service +spec: + type: NodePort + selector: + app: gateway-service + ports: + - port: 8080 + targetPort: 8080 + nodePort: 30001