This commit is contained in:
Tran Huu Danh
2026-05-05 21:50:37 +07:00
parent a6ec0c39c5
commit 42609e4547
11 changed files with 331 additions and 4 deletions
+4 -4
View File
@@ -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 '*'",
@@ -0,0 +1,6 @@
package com.drinkool.dtos;
public class AttendanceRequest {
public Long shiftId;
public String note;
}
@@ -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
}
@@ -0,0 +1,5 @@
package com.drinkool.enums;
public enum Role {
MANAGER, STAFF
}
@@ -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
}
@@ -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...
}
@@ -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<Shift> findByStaffAndDateRange(Long staffId, LocalDateTime start, LocalDateTime end) {
return find("staff.id = ?1 and startTime >= ?2 and endTime <= ?3", staffId, start, end).list();
}
}
@@ -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
}
@@ -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;
}
@@ -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<Shift> 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();
}
}
+94
View File
@@ -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