Task #2380
Cập nhật bởi Tiến Lực Phạm cách đây 23 ngày
- Khi 1 khn đã đc duyệt và xếp lịch => bị sai/chỉnh sửa lớp huấn luyện
- đưa ra giải pháp khi xoá/điều chỉnh lớp huấn luyện trong KHN
- YÊU CẦU: thay đổi 1 lớp trong khn thì sẽ phải xử lý như thế nào để ít thao tác và nhanh chóng nhất
Giải pháp của Dev Lực:
Thay đổi Kế hoạch năm
Đây là một bài toán nghiệp vụ **rất thực tế và điển hình** trong các hệ thống điều hành huấn luyện quân sự/đào tạo đại học.
Trong hệ thống, chuỗi liên kết dữ liệu đi theo trục:
$$\text{Kế hoạch năm (KHN)} \xrightarrow{} \text{TBNVHL (Notification)} \xrightarrow{} \text{Kế hoạch huấn luyện (KHHL)} \xrightarrow{} \text{Thời khóa biểu (TKB Sessions)}$$
---
### I. ĐÁNH GIÁ NGHẼN NGHIỆP VỤ NẾU LÀM THEO CÁCH THỦ CÔNG HIỆN TẠI
Nếu một Kế hoạch năm (KHN) đã duyệt và đã xếp TKB, mà hệ thống áp dụng cơ chế khóa cứng (như hàm `ValidateTrainingNotificationLockedMonthsAsync` hiện tại đang chặn sửa khi đã có TBNVHL), người dùng sẽ rơi vào **"Cơn ác mộng thao tác"**:
1. Phải hủy duyệt TKB $\rightarrow$ Xóa/dỡ lịch các tuần/tháng.
2. Hủy duyệt/Xóa Kế hoạch huấn luyện.
3. Xóa các Thông báo nhiệm vụ huấn luyện (TBNVHL).
4. Hủy duyệt KHN $\rightarrow$ Chuyển về Lưu nháp $\rightarrow$ Sửa/xóa lớp.
5. Gửi duyệt lại từ đầu toàn bộ các tầng và **phải xếp lại TKB từ đầu** (mất toàn bộ công sức xếp giảng viên, giảng đường, thao trường của cả các lớp không liên quan).
👉 **Yêu cầu đặt ra:** Cần giải pháp **"Khu trú ảnh hưởng (Blast Radius Isolation)"** kết hợp **"Đồng bộ tự động 1 chạm (1-Click Smart Cascade)"** — tức là **sửa tại KHN, hệ thống tự động xử lý các module bên dưới với số lần click chuột ít nhất và không ảnh hưởng đến các lớp khác**.
---
### II. GIẢI PHÁP TỔNG THỂ CHO TỪNG TÌNH HUỐNG
Khi thay đổi lớp trong KHN đã duyệt, thực tế sẽ rơi vào 3 tình huống nghiệp vụ:
```
[ THAY ĐỔI LỚP TRONG KHN ]
│
┌──────────────────────────────┼──────────────────────────────┐
▼ ▼ ▼
【Tình huống 1】 【Tình huống 2】 【Tình huống 3】
XÓA HẲN 1 LỚP ĐỔI / THAY THẾ LỚP ĐIỀU CHỈNH TIẾN ĐỘ
(Lớp hoãn học, gán nhầm) (Chọn nhầm Lớp A -> Lớp B) (Lớp học khung riêng)
│ │ │
▼ ▼ ▼
Thu hồi lịch cục bộ Kế thừa & Chuyển giao Tách nhánh (Fork Plan)
(Chỉ xóa session lớp đó) (Đổi ID, giữ nguyên TKB) thành KHN riêng cho lớp
```
---
#### 1. TÌNH HUỐNG 1: XÓA HOÀN TOÀN 1 LỚP KHỎI KHN
*(Ví dụ: Lớp bị hoãn khóa học, chuyển sang năm sau hoặc chọn nhầm lớp vào KHN này)*
* **Mục tiêu:** Xóa lớp khỏi KHN nhưng **không xóa TKB của các lớp khác**, giải phóng tài nguyên giảng viên/phòng học.
* **Cách xử lý tối ưu (Ít thao tác nhất):**
* **Tại KHN:** Người dùng bấm nút **"Xóa lớp khỏi KHN"** $\rightarrow$ Hệ thống bật Popup xác nhận kèm **Ma trận ảnh hưởng** (Hiển thị rõ: *Lớp đang có 3 TBNVHL, 1 KHHL, 45 tiết TKB đã xếp*).
* **Hệ thống tự động chạy ngầm trong 1 Transaction:**
1. **Với TKB (`TrainingTimetable_Session`):**
* Tự động xóa các tiết của riêng lớp đó (`fixed_class_id = @classId`) từ thời điểm hiện tại trở đi (`date >= TODAY`).
* **Giải phóng tài nguyên:** Giảng viên (`lecturer_id`), Phòng học (`room_id`) của các tiết bị xóa lập tức được giải phóng để lớp khác sử dụng.
* *Lưu ý lớp ghép:* Nếu là tiết học ghép nhiều lớp, hệ thống chỉ tách lớp bị xóa ra khỏi nhóm ghép, **giữ nguyên tiết học cho các lớp còn lại**.
* Các tiết đã diễn ra trong quá khứ (`date < TODAY`) được giữ nguyên trạng thái đóng băng để lưu vết hồ sơ đào tạo.
2. **Với KHHL & TBNVHL:**
* Tự động xóa liên kết của lớp trong `TrainingNotification_FixedClass` và `CH_TrainingClass`.
* Chạy hàm cập nhật lại tổng số tiết/tổng hợp (Recalculate Summary).
3. **Với KHN:** Xóa bản ghi trong `YearPlan_FixedClass`.
* **Thao tác người dùng:** Đúng **1 click** (Chọn lớp $\rightarrow$ Bấm Xóa $\rightarrow$ Nhập lý do $\rightarrow$ Xác nhận).
---
#### 2. TÌNH HUỐNG 2: THAY THẾ LỚP (ĐỔI LỚP A $\rightarrow$ LỚP B)
*(Ví dụ: Do nhập nhầm mã lớp c1 sang c2, thực tế tiến độ bài học, giáo viên, phòng ốc vẫn giữ nguyên)*
* **Mục tiêu:** Không bắt xếp lại TKB, chỉ chuyển đổi đối tượng thụ hưởng từ Lớp A sang Lớp B.
* **Cách xử lý tối ưu (Cơ chế Kế thừa & Chuyển giao - Fast Replace):**
* **Tại KHN:** Người dùng bấm **"Thay thế lớp"** $\rightarrow$ Chọn Lớp mới (Lớp B) từ dropdown.
* **Hệ thống tự động hoán đổi đồng loạt (Cascade Update):**
1. Cập nhật `YearPlan_FixedClass`: Đổi `fixed_class_id` từ A sang B.
2. Cập nhật `TrainingNotification_FixedClass`: Đổi A sang B.
3. Cập nhật `CH_TrainingClass`: Đổi A sang B.
4. Cập nhật `TrainingTimetable_Cohort_FixedClass`: Đổi A sang B.
5. Cập nhật `TrainingTimetable_Session`:
* Toàn bộ các tiết tương lai của Lớp A được gán lại `fixed_class_id = B` (đồng thời trỏ lại `subject_class_id` tương ứng của lớp B).
* Gọi `SyncOneKey` (hàm đồng bộ đã tối ưu trong `Toi_uu_luong_luu_xoa_lich.md`) để làm mới bảng tổng hợp số tiết `TrainingTimetable_SubjectSummaryMethod`.
* **Thao tác người dùng:** Đúng **1 thao tác** duy nhất: Chọn lớp thay thế $\rightarrow$ Bấm **"Đổi và Cập nhật toàn bộ"**. Toàn bộ lịch học chuyển sang Lớp B mà không phải xếp lại dù chỉ 1 tiết!
---
#### 3. TÌNH HUỐNG 3: ĐIỀU CHỈNH TIẾN ĐỘ / NỘI DUNG HỌC CỦA RIÊNG LỚP ĐÓ
*(Ví dụ: Lớp A cần học thêm một số môn đặc thù hoặc giảm tiết, lệch khung với các lớp còn lại trong KHN)*
* **Vấn đề:** Trong hệ thống hiện tại, 1 KHN gồm nhiều lớp dùng chung bảng phân bổ bài học `YearPlan_Lesson`. Nếu sửa trực tiếp bài học trên KHN này sẽ làm lệch luôn tiến độ của các lớp còn lại.
* **Cách xử lý tối ưu (Cơ chế "Tách nhánh KHN" - Forking Plan):**
* **Tại KHN:** Người dùng chọn Lớp A $\rightarrow$ Bấm **"Tách thành Kế hoạch riêng"**.
* **Hệ thống tự động:**
1. Rút Lớp A ra khỏi KHN hiện tại (KHN cũ giữ nguyên cho các lớp còn lại).
2. Clone sang một bản ghi KHN mới dành riêng cho Lớp A (kế thừa lại toàn bộ bài học và lịch TKB đã xếp đến thời điểm hiện tại).
3. Mở giao diện lập kế hoạch của KHN mới để người dùng chỉnh sửa bài học/tiết cho Lớp A.
4. Khi duyệt KHN mới, hệ thống chỉ đẩy chênh lệch (Delta) vào TKB để xếp bổ sung các bài mới.
---
### III. THIẾT KẾ GIAO DIỆN & TRẢI NGHIỆM NGƯỜI DÙNG (UX/UI)
Để đảm bảo tính pháp lý quân sự (không sửa ngầm) nhưng vẫn nhanh chóng:
1. **Vị trí đặt tính năng:**
* Tại màn hình danh sách Kế hoạch năm hoặc màn xem chi tiết (`KeHoachNamViewDetailComponent`), thêm menu thao tác: **"Điều chỉnh lớp"**.
2. **Modal Hướng dẫn Thông minh (Impact Wizard - 2 bước):**
* **Bước 1 (Chọn hành động):**
* Chọn lớp cần thao tác.
* Chọn loại điều chỉnh: `[Xóa bỏ]` hoặc `[Thay thế bằng lớp khác]`.
* Nhập *Lý do điều chỉnh* (Bắt buộc theo nghiệp vụ quân sự: ví dụ *"Theo Quyết định số... của Hiệu trưởng"*).
* **Bước 2 (Xem trước ảnh hưởng & Xác nhận):**
* Hệ thống hiển thị bảng tóm tắt:
* *Số đợt TBNVHL bị ảnh hưởng*: X đợt.
* *Số tiết TKB sẽ tự động hủy/chuyển đổi*: Y tiết (từ ngày DD/MM/YYYY).
* *Cảnh báo tiết học ghép* (nếu có).
* Bấm nút: **`[Xác nhận & Tự động đồng bộ]`**.
---
### IV. KIẾN TRÚC KỸ THUẬT BACKEND (ĐỂ ĐẢM BẢO CHẠY NHANH & TOÀN VẸN)
Để hiện thực hóa luồng này mà không bị lỗi timeout hoặc deadlock:
1. **Tạo API Endpoint chuyên biệt:**
* `POST api/tenant/YearPlans/adjust-fixed-class`
* DTO truyền lên:
```typescript
{
year_plan_id: string;
action_type: 'DELETE' | 'REPLACE' | 'FORK';
source_fixed_class_id: string;
target_fixed_class_id?: string; // Dùng khi REPLACE
reason: string;
effective_from_date: Date; // Mặc định là ngày hôm nay
}
```
2. **Cơ chế xử lý trong Backend:**
* **Bọc toàn bộ trong 1 `DbTransaction`:** Đảm bảo hoặc thành công toàn bộ từ KHN đến TKB, hoặc rollback sạch sẽ nếu có lỗi.
* **Tái sử dụng Engine TKB đã tối ưu:**
* Khi xóa/sửa session hàng loạt của lớp đó, tái sử dụng service `TrainingTimetable_SessionService` với cơ chế **Warm Cache** + **Defer Side-Effects** + **Flush gom nhóm key** (theo tài liệu [Toi_uu_luong_luu_xoa_lich.md](file:///d:/Projects/SQLQ_LichHuanLuyen/source/TaiLieu/Notes/Toi_uu_luong_luu_xoa_lich.md)). Điều này giúp việc xóa 50-100 tiết của lớp diễn ra trong **dưới 1 giây**.
* **Ghi vết lịch sử tự động (`TrainingTimetable_ChangeLog`):**
* Ghi nhận lịch sử điều chỉnh với lý do của người dùng để phục vụ công tác thanh tra/kiểm tra sau này.
* **Bypass Validation chặn thông thường:** Trong luồng này, thêm cờ `Context.IsAdminAdjustment = true` để bỏ qua việc throw exception tại `ValidateTrainingNotificationLockedMonthsAsync`.
---
### BẢNG SO SÁNH HIỆU QUẢ
| Tiêu chí | Quy trình thủ công hiện tại | Giải pháp đề xuất |
| :--- | :--- | :--- |
| **Số bước thao tác** | 8 – 10 bước qua 4 màn hình khác nhau | **1 bước** tại màn hình Kế hoạch năm |
| **Thời gian thực hiện** | 30 phút – vài tiếng (nếu phải xếp lại TKB) | **3 – 5 giây** (xử lý ngầm tự động) |
| **Tác động đến lớp khác** | Nguy cơ làm hỏng hoặc phải xếp lại lịch cả khóa | **0%** (Cách ly hoàn toàn các lớp khác) |
| **Tính toàn vẹn dữ liệu** | Dễ sót dữ liệu giữa các module | 100% đồng bộ qua Database Transaction |
| **Tính pháp lý quân đội** | Mất dấu vết cũ do xóa đi làm lại | Lưu vết đầy đủ qua `ChangeLog` và `RevisionRequest` |
Giải pháp này giải quyết triệt để bài toán: **Người dùng chỉ thao tác tại điểm phát sinh nhu cầu (Kế hoạch năm), toàn bộ các mắt xích phía sau (TBNVHL, KHHL, TKB) tự động thích ứng chính xác theo quan hệ dữ liệu đã được thiết lập sẵn.**