# 1. Module Quản lý nhân sự
### **a. Thông tin cá nhân cơ bản**
* Họ và tên
* Mã nhân viên
* Ngày sinh
* Giới tính
* Số CMND/CCCD/Hộ chiếu
* Địa chỉ liên hệ
* Số điện thoại
* Email
* Ảnh nhân viên (nếu cần)
* Trạng thái
---
### **b. Thông tin công việc**
* Phòng ban / bộ phận
* Chức danh / vị trí công việc
* Loại hợp đồng (dài hạn, thời vụ, thử việc…)
* Hình thức trả lương (theo tháng, ngày, giờ, sản phẩm…)
* Ngày bắt đầu làm việc
* Ngày kết thúc (nếu có)
* Mã số thuế TNCN
* Số tài khoản ngân hàng + Ngân hàng (để chuyển lương)
---
### **c. Thông tin phục vụ tính lương**
* **Mức lương cơ bản** (hoặc đơn giá theo giờ/ngày/sản phẩm)
* **Phụ cấp** (ăn trưa, xăng xe, nhà ở, điện thoại…)
* **Khoản thưởng** mặc định hàng tháng (nếu có)
* **Khoản phạt** mặc định (nếu có, ví dụ tiền ứng trước trừ dần)
* Tỷ lệ đóng bảo hiểm (BHXH, BHYT, BHTN)
* Tỷ lệ thuế TNCN (nếu phải nộp)
* Số ngày phép còn lại trong năm
* Ca làm việc tiêu chuẩn (ca sáng, ca tối, ca kíp…)
---
### **d. Thông tin chấm công & sản lượng**
* Số ngày công thực tế
* Số giờ làm thêm / tăng ca
* Sản lượng sản phẩm (nếu tính theo sản phẩm)
* Ngày nghỉ có phép / không phép
* Các loại nghỉ khác (ốm, thai sản, nghỉ bù…)
---
### **e. Thông tin khác hỗ trợ quản lý**
* Người quản lý trực tiếp
* Mã máy chấm công (nếu dùng máy quét vân tay/khuôn mặt)
* Tình trạng làm việc (đang làm, nghỉ phép dài hạn, nghỉ việc…)
# **2. Module Quản lý Thành phần Lương (Salary Components)**
Đây là "trái tim" của sự linh hoạt. Hệ thống cho phép người dùng tự tạo và quản lý các "biến số" sẽ được sử dụng trong công thức tính lương.
* **Phân loại thành phần:**
* **Thu nhập (Earnings):** Bao gồm các khoản làm tăng tổng thu nhập của nhân viên.
* *Ví dụ:* Lương cơ bản, Lương theo giờ, Phụ cấp ăn trưa, Phụ cấp đi lại, Thưởng KPI, Thưởng doanh số, Tiền làm thêm giờ (OT).
* **Khấu trừ (Deductions):** Bao gồm các khoản làm giảm tổng thu nhập.
* *Ví dụ:* Bảo hiểm xã hội (BHXH), Bảo hiểm y tế (BHYT), Bảo hiểm thất nghiệp (BHTN), Thuế thu nhập cá nhân (TNCN), Các khoản tạm ứng, Khoản phạt.
* **Thuộc tính của mỗi thành phần:**
* **Mã thành phần:** Một mã định danh duy nhất (ví dụ: `LUONG_CO_BAN`, `PHU_CAP_AN_TRUA`).
* **Tên thành phần:** Tên gọi dễ hiểu (ví dụ: "Lương cơ bản", "Phụ cấp ăn trưa").
* **Loại dữ liệu:** Số (Number), Chuỗi (Text), Ngày tháng (Date), Logic (Boolean - True/False).
* **Nguồn dữ liệu:**
* **Cố định (Fixed):** Giá trị được nhập tay và cố định cho từng nhân viên hoặc nhóm nhân viên (ví dụ: Phụ cấp ăn trưa 800,000 VNĐ/tháng).
* **Từ hệ thống (System-based):** Tự động lấy từ các module khác.
* *Ví dụ:* Lương cơ bản (lấy từ module Nhân sự), Ngày công thực tế (lấy từ module Chấm công), Doanh số đạt được (lấy từ module KPI/Bán hàng).
* **Theo công thức (Formula-based):** Giá trị được tính toán từ các thành phần khác.
# **3. Module Công cụ xây dựng Công thức (Formula Builder)**
* **Giao diện Nhập công thức hoặc Gợi ý thông minh (IntelliSense):** Cho phép người dùng dễ dàng chọn các "Mã thành phần" đã tạo, các toán tử và các hàm có sẵn.
* **Các toán tử hỗ trợ:**
* **Toán tử số học:** `+`, `-`, `*`, `/`.
* **Toán tử so sánh:** `>`, `<`, `>=`, `<=`, `==`, `!=`.
* **Toán tử logic:** `AND`, `OR`, `NOT`.
* **Thư viện hàm (Function Library):** Cung cấp các hàm tính toán phổ biến.
* **Hàm logic:** `IF(điều kiện, giá trị đúng, giá trị sai)`. Ví dụ: `IF([KPI_Dat_Chi_Tieu] == TRUE, 500000, 0)` - Nếu đạt KPI thì thưởng 500,000, ngược lại thì 0.
* **Hàm làm tròn:** `ROUND()`, `ROUNDUP()`, `ROUNDDOWN()`.
* **Hàm tìm giá trị:** `MIN()`, `MAX()`.
# **4. Module Quản lý Bảng lương & Kỳ lương (Payroll Period & Payslip)**
* **Thiết lập kỳ lương:** Cho phép tạo các kỳ lương khác nhau (tháng, tuần) và áp dụng các chính sách lương/công thức khác nhau cho từng kỳ.
* **Áp dụng chính sách lương:** Gán một hoặc nhiều bộ công thức (chính sách lương) cho các nhóm đối tượng khác nhau (ví dụ: Chính sách lương cho khối văn phòng, chính sách lương cho khối sản xuất, chính sách lương cho nhân viên thử việc).
* **Quy trình chạy lương:**
1. **Chốt dữ liệu:** Hệ thống "chốt" dữ liệu từ các module liên quan (chấm công, KPI) tại một thời điểm nhất định.
2. **Tính toán tự động:** Hệ thống thực thi các công thức đã thiết lập cho từng nhân viên.
3. **Xem trước và rà soát:** Cho phép bộ phận C&B xem lại bảng lương nháp, kiểm tra và thực hiện các điều chỉnh cuối cùng (nếu cần).
4. **Phê duyệt và ban hành:** Sau khi đã chính xác, bảng lương được phê duyệt và gửi đến từng nhân viên.
* **Phiếu lương cá nhân (Payslip):** Tự động tạo phiếu lương chi tiết cho từng nhân viên, thể hiện rõ ràng các khoản thu nhập, khấu trừ và công thức đã áp dụng. Nhân viên có thể truy cập qua cổng thông tin nhân viên (Employee Portal).
# **5. Module Chấm công & Quản lý thời gian (Time & Attendance)**
* Tích hợp với máy chấm công hoặc cho phép chấm công qua ứng dụng di động, web.
* Quản lý đơn từ: Đơn xin nghỉ phép, đơn xin đi muộn/về sớm, đơn làm thêm giờ.
* Tổng hợp dữ liệu và cung cấp các "biến số" cho module tính lương, ví dụ: `Ngay_Cong_Thuc_Te`, `So_Gio_Lam_Them`, `So_Ngay_Nghi_Phep`.
# **6. Module Báo cáo & Phân tích (Reporting & Analytics)**
* Xuất các báo cáo lương theo mẫu chuẩn của cơ quan nhà nước.
* Tạo các báo cáo quản trị: Báo cáo chi phí lương theo phòng ban, biến động lương qua các kỳ, phân tích cơ cấu thu nhập...
---
# **Ví dụ minh họa cách hoạt động**
**Bài toán:** Công ty A muốn thiết lập công thức tính "Lương thực nhận" cho nhân viên văn phòng.
**Bước 1: Định nghĩa các Thành phần lương**
Người quản trị vào **Module Quản lý Thành phần lương** và tạo/kiểm tra các biến số:
| Mã thành phần | Tên thành phần | Loại | Nguồn dữ liệu | Ví dụ giá trị |
| :--- | :--- | :--- | :--- | :--- |
| `LCB` | Lương Cơ Bản | Số | Từ Module Nhân sự | 10,000,000 |
| `PC_ANTRUA` | Phụ cấp ăn trưa | Số | Cố định | 800,000 |
| `NGAY_CONG_CHUAN` | Ngày công chuẩn | Số | Cố định | 24 |
| `NGAY_CONG_THUCTE`| Ngày công thực tế | Số | Từ Module Chấm công | 23.5 |
| `THUONG_KPI` | Thưởng KPI | Số | Nhập tay/Theo công thức| 500,000 |
| `TAM_UNG` | Tạm ứng | Số | Nhập tay | 1,000,000 |
| `BHXH`, `BHYT`, `BHTN` | Các khoản Bảo hiểm | Theo công thức | Theo công thức | - |
| `GIAM_TRU_GIACANH`| Giảm trừ gia cảnh | Số | Cố định | 11,000,000 |
| `SO_NGUOI_PHUTHUOC` | Số người phụ thuộc | Số | Từ Module Nhân sự | 1 |
| `GIAM_TRU_NPT` | Giảm trừ người phụ thuộc | Theo công thức | Theo công thức | - |
| `THUE_TNCN` | Thuế TNCN | Theo công thức | Theo công thức | - |
**Bước 2: Xây dựng Công thức trong Formula Builder**
Người quản trị vào **Module Công cụ xây dựng Công thức** và tạo một "Chính sách lương Văn phòng".
1. **Tính Lương thực tế theo ngày công:**
* `LUONG_THUC_TE = ([LCB] / [NGAY_CONG_CHUAN]) * [NGAY_CONG_THUCTE]`
2. **Tính Tổng thu nhập:**
* `TONG_THU_NHAP = [LUONG_THUC_TE] + [PC_ANTRUA] + [THUONG_KPI]`
3. **Tính các khoản giảm trừ bắt buộc:**
* `BHXH = [LCB] * 8%` (Giả sử LCB là lương đóng BH)
* `BHYT = [LCB] * 1.5%`
* `BHTN = [LCB] * 1%`
* `TONG_BAO_HIEM = [BHXH] + [BHYT] + [BHTN]`
4. **Tính Thu nhập chịu thuế:**
* `GIAM_TRU_NPT = [SO_NGUOI_PHUTHUOC] * 4400000`
* `THU_NHAP_TINH_THUE = [TONG_THU_NHAP] - [TONG_BAO_HIEM] - [GIAM_TRU_GIACANH] - [GIAM_TRU_NPT]`
* `THU_NHAP_TINH_THUE = IF([THU_NHAP_TINH_THUE] < 0, 0, [THU_NHAP_TINH_THUE])` (Nếu âm thì bằng 0)
5. **Tính Thuế TNCN (dùng hàm bậc thang):**
* `THUE_TNCN = VLOOKUP([THU_NHAP_TINH_THUE], Bang_Bieu_Thue_Luy_Tien)`
6. **Tính Lương cuối cùng (Thực nhận):**
* **`LUONG_THUC_NHAN = [TONG_THU_NHAP] - [TONG_BAO_HIEM] - [THUE_TNCN] - [TAM_UNG]`**
**Bước 3: Áp dụng và Chạy lương**
* Hệ thống sẽ tự động gán "Chính sách lương Văn phòng" này cho tất cả nhân viên thuộc khối văn phòng.
* Vào cuối kỳ, người quản trị chỉ cần thực hiện vài cú nhấp chuột: Chốt công -> Chạy lương -> Rà soát -> Phê duyệt.
### **Lợi ích của thiết kế này**
* **Dễ dàng thay đổi:** Khi chính sách phụ cấp thay đổi, chỉ cần vào Module Thành phần lương và sửa lại giá trị của `PC_ANTRUA`. Khi luật thuế TNCN thay đổi, chỉ cần cập nhật lại `Bang_Bieu_Thue_Luy_Tien` mà không cần thay đổi toàn bộ công thức.
* **Khả năng mở rộng:** Dễ dàng thêm các loại thưởng, phụ cấp mới mà không ảnh hưởng đến cấu trúc hệ thống.
* **Giảm phụ thuộc vào IT:** Phòng nhân sự có thể chủ động quản lý hệ thống tính lương của mình.
---
# Luồng hoạt động:
**[Tại đây](https://drive.google.com/file/d/1Un2PhFHEIXS-PXldxKe_hYtxMVnEY7Na/view?usp=sharing)**
---
``` sql
CREATE TABLE attendance_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
employee_id UUID NOT NULL,
work_date DATE NOT NULL,
-- Thông tin ca làm việc
shift_id UUID, -- Ca làm việc được phân công
actual_shift_start TIME,
actual_shift_end TIME,
-- Check in/out
check_in_time TIMESTAMP,
check_in_device_id VARCHAR(50),
check_in_location VARCHAR(200),
check_out_time TIMESTAMP,
check_out_device_id VARCHAR(50),
check_out_location VARCHAR(200),
-- Tính toán thời gian
total_work_minutes INTEGER, -- Tổng phút làm việc
regular_work_minutes INTEGER, -- Phút làm việc thường
overtime_minutes INTEGER, -- Phút làm thêm
night_work_minutes INTEGER, -- Phút làm đêm
weekend_work_minutes INTEGER, -- Phút làm cuối tuần
holiday_work_minutes INTEGER, -- Phút làm ngày lễ
-- Nghỉ phép & vi phạm
late_minutes INTEGER DEFAULT 0, -- Phút đi muộn
early_leave_minutes INTEGER DEFAULT 0, -- Phút về sớm
break_minutes INTEGER DEFAULT 0, -- Phút nghỉ giải lao
-- Loại ngày công
attendance_type ENUM('present', 'absent', 'leave', 'sick', 'holiday', 'weekend') DEFAULT 'present',
leave_type_id UUID, -- Loại nghỉ phép
-- Ghi chú
notes TEXT,
manager_notes TEXT,
-- Phê duyệt
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
approved_by UUID,
approved_at TIMESTAMP,
-- Metadata
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
sync_source VARCHAR(50), -- 'manual', 'device', 'import'
FOREIGN KEY (employee_id) REFERENCES employees(id),
FOREIGN KEY (shift_id) REFERENCES work_shifts(id),
FOREIGN KEY (leave_type_id) REFERENCES leave_types(id),
FOREIGN KEY (approved_by) REFERENCES employees(id),
UNIQUE(employee_id, work_date)
);
```
## **1. Thông tin định danh**
* Mã nhân viên
* Họ và tên
* Mã máy chấm công / ID quẹt thẻ (nếu dùng máy chấm công)
* Phòng ban / bộ phận
* Ca làm việc được phân công (ca sáng, ca tối, ca kíp, ca linh hoạt)
---
## **2. Thông tin chấm công hàng ngày**
* **Ngày làm việc**
* **Giờ vào – giờ ra** (có thể nhiều lần trong ngày nếu ra/vào giữa ca)
* Số giờ làm trong ca
* Số giờ làm thêm / tăng ca
* Số giờ làm ban đêm
* Số giờ làm ngày nghỉ / ngày lễ (nếu có)
* Ghi chú lý do ra/vào bất thường (nếu có)
---
## **3. Thông tin về vắng mặt**
* Ngày nghỉ có phép
* Ngày nghỉ không phép
* Nghỉ ốm / nghỉ chế độ (thai sản, tang lễ…)
* Nghỉ bù
* Nghỉ công tác (đi làm ở ngoài, vẫn tính công)
---
## **4. Các thông tin bổ sung để tính lương**
* Sản lượng hoàn thành (nếu kết hợp chấm công và tính theo sản phẩm)
* Mức phạt đi muộn / về sớm (số phút và tiền phạt tương ứng)
* Phụ cấp ca (ăn ca, xăng xe, ca đêm…)
* Ghi chú của quản lý hoặc bộ phận nhân sự
---
## **5. Thông tin tổng hợp cuối kỳ**
* Tổng số ngày công thực tế
* Tổng số giờ làm thêm / tăng ca
* Tổng số giờ làm ban đêm
* Tổng số ngày nghỉ các loại
* Dữ liệu chấm công sau khi đã được **xác nhận duyệt** từ quản lý
---
# KIẾN TRÚC HỆ THỐNG TÍNH LƯƠNG CHI TIẾT
## 1. TỔNG QUAN KIẾN TRÚC HỆ THỐNG
### 1.1 Mô hình kiến trúc
```
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENT TIER │
├─────────────────────────────────────────────────────────────────────┤
│ Web Browser │ Mobile App │ Desktop App │ API Client │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ PRESENTATION TIER │
├─────────────────────────────────────────────────────────────────────┤
│ Web Server (Nginx/Apache) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Load Balancer│ │ CDN │ │ SSL/TLS │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ APPLICATION TIER │
├─────────────────────────────────────────────────────────────────────┤
│ Backend Application Server │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ API Gateway │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │Auth Service │ │Rate Limiting│ │API Routing │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Core Services │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │User Service │ │Employee Svc │ │Attendance │ │ │
│ │ └─────────────┘ └─────────────┘ │Service │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ └─────────────┘ │ │
│ │ │Salary Calc │ │Report Svc │ ┌─────────────┐ │ │
│ │ │Service │ └─────────────┘ │Payment Svc │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Business Logic Layer │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │Formula │ │Tax │ │Workflow │ │ │
│ │ │Engine │ │Calculator │ │Engine │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ DATA TIER │
├─────────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Primary DB │ │Cache Layer │ │File Storage │ │
│ │(PostgreSQL) │ │(Redis) │ │(MinIO/S3) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Replica DB │ │Search Engine │ │Backup Storage│ │
│ │(Read Only) │ │(Elasticsearch│ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
## 2. CHI TIẾT TỪNG LAYER
### 2.1 Client Tier (Tầng Client)
#### **Web Application (React.js)**
```javascript
// Cấu trúc thư mục Frontend
src/
├── components/ # Component tái sử dụng
│ ├── common/ # Button, Input, Modal
│ ├── forms/ # AttendanceForm, SalaryForm
│ └── layout/ # Header, Sidebar, Footer
├── pages/ # Các trang chính
│ ├── employees/ # Quản lý nhân viên
│ ├── attendance/ # Chấm công
│ ├── salary/ # Tính lương
│ └── reports/ # Báo cáo
├── services/ # API calls
├── store/ # State management (Redux/Zustand)
├── utils/ # Utility functions
└── hooks/ # Custom React hooks
```
#### **Mobile App (React Native)**
- Chấm công qua GPS
- Xem phiếu lương
- Thông báo push
- Offline capability
#### **Desktop App (Electron)**
- Chức năng đầy đủ như Web
- Tích hợp máy chấm công
- Import/Export Excel offline
- Backup local
### 2.2 Presentation Tier (Tầng Trình bày)
#### **Web Server (Nginx)**
```nginx
# nginx.conf
upstream backend {
server app1:3000;
server app2:3000;
server app3:3000;
}
server {
listen 443 ssl http2;
server_name salary.company.com;
# SSL Configuration
ssl_certificate /etc/ssl/certs/salary.crt;
ssl_certificate_key /etc/ssl/private/salary.key;
# Security Headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# API Routes
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Static Files
location /static/ {
root /var/www/salary-system;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
```
#### **Load Balancer & CDN**
- **Nginx Load Balancer**: Phân tải request
- **CloudFlare CDN**: Cache static files
- **SSL/TLS**: Bảo mật HTTPS
### 2.3 Application Tier (Tầng Ứng dụng)
#### **API Gateway**
```javascript
// API Gateway Structure
const express = require('express');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const app = express();
// Security Middleware
app.use(helmet());
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
}));
// Authentication Middleware
app.use('/api', authenticateToken);
// Route Management
app.use('/api/auth', authRoutes);
app.use('/api/employees', employeeRoutes);
app.use('/api/attendance', attendanceRoutes);
app.use('/api/salary', salaryRoutes);
app.use('/api/reports', reportRoutes);
```
#### **Core Services Architecture**
##### **User Service**
```javascript
// user.service.js
class UserService {
async authenticate(username, password) {
// JWT authentication logic
}
async authorize(user, resource, action) {
// Role-based authorization
}
async getUserProfile(userId) {
// User profile management
}
}
```
##### **Employee Service**
```javascript
// employee.service.js
class EmployeeService {
async createEmployee(employeeData) {
// Create new employee
// Validate data
// Generate employee code
// Save to database
}
async updateEmployee(id, updateData) {
// Update employee information
// Audit trail logging
}
async getEmployeesByType(type) {
// Filter by employee type
// (hanh_chinh, san_xuat, thoi_vu)
}
}
```
##### **Attendance Service**
```javascript
// attendance.service.js
class AttendanceService {
async recordAttendance(attendanceData) {
// Validate attendance data
// Apply business rules
// Calculate temporary amount
// Save to database
}
async calculateDailyEarnings(employeeId, date) {
// Get attendance records
// Apply salary formulas
// Return calculated amount
}
async importAttendanceFromExcel(file) {
// Parse Excel file
// Validate data
// Bulk insert with transactions
}
}
```
##### **Salary Calculation Service**
```javascript
// salary-calc.service.js
class SalaryCalculationService {
async calculateSalary(employeeId, periodId) {
// Get employee configuration
// Get attendance data
// Apply salary formulas
// Calculate taxes and deductions
// Return salary breakdown
}
async calculateTax(grossSalary, employeeId) {
// Get tax configuration
// Apply tax brackets
// Calculate personal income tax
}
async processBulkSalary(periodId) {
// Get all active employees
// Process in parallel batches
// Handle errors gracefully
// Generate reports
}
}
```
#### **Business Logic Layer**
##### **Formula Engine**
```javascript
// formula-engine.js
class FormulaEngine {
constructor() {
this.formulaCache = new Map();
}
async evaluateFormula(formula, variables) {
try {
// Parse formula expression
// Validate variables
// Calculate result safely
// Cache results
return this.safeEval(formula, variables);
} catch (error) {
throw new Error(`Formula evaluation failed: ${error.message}`);
}
}
safeEval(expression, context) {
// Safe expression evaluation
// Prevent code injection
// Mathematical operations only
}
// Formula examples:
// "so_qua * don_gia"
// "luong_co_ban + phu_cap"
// "so_gio * luong_gio * he_so"
}
```
##### **Tax Calculator**
```javascript
// tax-calculator.js
class TaxCalculator {
constructor() {
this.taxBrackets = [
{ from: 0, to: 5000000, rate: 0.05 },
{ from: 5000000, to: 10000000, rate: 0.10 },
{ from: 10000000, to: 18000000, rate: 0.15 },
{ from: 18000000, to: 32000000, rate: 0.20 },
{ from: 32000000, to: 52000000, rate: 0.25 },
{ from: 52000000, to: 80000000, rate: 0.30 },
{ from: 80000000, to: Infinity, rate: 0.35 }
];
}
calculatePersonalIncomeTax(taxableIncome) {
let tax = 0;
for (const bracket of this.taxBrackets) {
if (taxableIncome > bracket.from) {
const taxableAtThisBracket = Math.min(
taxableIncome - bracket.from,
bracket.to - bracket.from
);
tax += taxableAtThisBracket * bracket.rate;
}
}
return tax;
}
calculateSocialInsurance(grossSalary) {
const maxBase = 29800000; // Mức đóng BHXH tối đa
const baseAmount = Math.min(grossSalary, maxBase);
return {
bhxh: baseAmount * 0.08, // 8%
bhyt: baseAmount * 0.015, // 1.5%
bhtn: baseAmount * 0.01 // 1%
};
}
}
```
##### **Workflow Engine**
```javascript
// workflow-engine.js
class WorkflowEngine {
async processWorkflow(entityType, entityId, action) {
const workflow = await this.getWorkflowDefinition(entityType);
const currentState = await this.getCurrentState(entityId);
if (this.canTransition(currentState, action, workflow)) {
await this.executeTransition(entityId, action);
await this.notifyStakeholders(entityId, action);
} else {
throw new Error(`Invalid transition: ${currentState} -> ${action}`);
}
}
// Workflow states:
// attendance: draft -> submitted -> approved -> processed
// salary: draft -> calculated -> reviewed -> approved -> paid
// payslip: generated -> sent -> viewed
}
```
### 2.4 Data Tier (Tầng Dữ liệu)
#### **Primary Database (PostgreSQL)**
```sql
-- Database Configuration
-- postgresql.conf optimizations for salary system
# Memory settings
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
# Connection settings
max_connections = 200
shared_preload_libraries = 'pg_stat_statements'
# Logging
log_statement = 'mod'
log_min_duration_statement = 1000
# Performance
random_page_cost = 1.1
effective_io_concurrency = 200
-- Key Indexes for Performance
CREATE INDEX CONCURRENTLY idx_attendance_employee_date
ON attendance_records(employee_id, work_date);
CREATE INDEX CONCURRENTLY idx_salary_calc_period
ON salary_calculations(period_id, status);
CREATE INDEX CONCURRENTLY idx_employees_active
ON employees(is_active, employee_type);
```
#### **Cache Layer (Redis)**
```javascript
// redis-config.js
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
});
// Cache Strategies
class CacheService {
async cacheEmployeeData(employeeId, data) {
await redis.setex(`employee:${employeeId}`, 3600, JSON.stringify(data));
}
async cacheSalaryFormulas() {
const formulas = await this.getSalaryFormulas();
await redis.setex('salary:formulas', 7200, JSON.stringify(formulas));
}
async cacheAttendanceSummary(employeeId, month) {
const key = `attendance:${employeeId}:${month}`;
// Cache for 1 day
await redis.setex(key, 86400, JSON.stringify(summary));
}
}
```
#### **File Storage (MinIO/S3)**
```javascript
// file-storage.service.js
const AWS = require('aws-sdk');
class FileStorageService {
constructor() {
this.s3 = new AWS.S3({
endpoint: process.env.MINIO_ENDPOINT,
accessKeyId: process.env.MINIO_ACCESS_KEY,
secretAccessKey: process.env.MINIO_SECRET_KEY,
s3ForcePathStyle: true
});
}
async uploadPayslip(employeeId, periodId, pdfBuffer) {
const key = `payslips/${periodId}/${employeeId}.pdf`;
return await this.s3.upload({
Bucket: 'salary-documents',
Key: key,
Body: pdfBuffer,
ContentType: 'application/pdf',
ServerSideEncryption: 'AES256'
}).promise();
}
async uploadAttendanceFile(file) {
const key = `attendance/${Date.now()}_${file.originalname}`;
return await this.s3.upload({
Bucket: 'attendance-files',
Key: key,
Body: file.buffer,
ContentType: file.mimetype
}).promise();
}
}
```
## 3. BẢO MẬT VÀ TUÂN THỦ
### 3.1 Authentication & Authorization
```javascript
// auth.middleware.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
class AuthService {
async login(username, password) {
const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.password_hash)) {
throw new Error('Invalid credentials');
}
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '8h' }
);
// Log login activity
await AuditLog.create({
user_id: user.id,
action: 'login',
ip_address: req.ip
});
return { token, user };
}
authorize(roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
}
```
### 3.2 Data Encryption
```javascript
// encryption.service.js
const crypto = require('crypto');
class EncryptionService {
constructor() {
this.algorithm = 'aes-256-gcm';
this.secretKey = process.env.ENCRYPTION_KEY;
}
encrypt(text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(this.algorithm, this.secretKey, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
encryptedData: encrypted
};
}
decrypt(encryptedData) {
const decipher = crypto.createDecipher(
this.algorithm,
this.secretKey,
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
// Encrypt sensitive data before storing
const encryptionService = new EncryptionService();
// Before saving employee data
employee.citizen_id = encryptionService.encrypt(employee.citizen_id);
employee.bank_account = encryptionService.encrypt(employee.bank_account);
```
## 4. HIỆU NĂNG VÀ TỐI ƯU HÓA
### 4.1 Database Optimization
```sql
-- Partitioning for large tables
CREATE TABLE attendance_records_y2024 PARTITION OF attendance_records
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- Materialized views for reports
CREATE MATERIALIZED VIEW monthly_salary_summary AS
SELECT
e.department_id,
DATE_TRUNC('month', sc.created_at) as salary_month,
COUNT(*) as employee_count,
SUM(sc.gross_salary) as total_gross,
SUM(sc.net_salary) as total_net
FROM salary_calculations sc
JOIN employees e ON sc.employee_id = e.employee_id
GROUP BY e.department_id, DATE_TRUNC('month', sc.created_at);
-- Refresh materialized view daily
SELECT cron.schedule('refresh-salary-summary', '0 1 * * *',
'REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_salary_summary;');
```
### 4.2 Application Performance
```javascript
// connection-pool.js
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
max: 20, // Maximum connections
min: 5, // Minimum connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
// Bulk operations for salary calculations
class BulkSalaryProcessor {
async processBatch(employeeIds, periodId) {
const batchSize = 50;
const batches = this.chunkArray(employeeIds, batchSize);
const promises = batches.map(batch =>
this.processSalaryBatch(batch, periodId)
);
return await Promise.allSettled(promises);
}
async processSalaryBatch(employeeIds, periodId) {
const client = await pool.connect();
try {
await client.query('BEGIN');
for (const employeeId of employeeIds) {
await this.calculateEmployeeSalary(client, employeeId, periodId);
}
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
}
```
## 5. MONITORING VÀ LOGGING
### 5.1 Application Monitoring
```javascript
// monitoring.js
const prometheus = require('prom-client');
// Custom metrics
const salaryCalculationDuration = new prometheus.Histogram({
name: 'salary_calculation_duration_seconds',
help: 'Duration of salary calculations',
labelNames: ['employee_type', 'period']
});
const activeUsers = new prometheus.Gauge({
name: 'active_users_total',
help: 'Number of active users'
});
const errorRate = new prometheus.Counter({
name: 'salary_errors_total',
help: 'Total number of salary calculation errors',
labelNames: ['error_type']
});
// Middleware to track metrics
function metricsMiddleware(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
salaryCalculationDuration.observe(duration / 1000);
});
next();
}
```
### 5.2 Structured Logging
```javascript
// logger.js
const winston = require('winston');
const ElasticsearchTransport = require('winston-elasticsearch');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'salary-system' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new ElasticsearchTransport({
level: 'info',
clientOpts: { node: process.env.ELASTICSEARCH_URL },
index: 'salary-logs'
})
]
});
// Usage examples
logger.info('Salary calculation started', {
employeeId: 123,
periodId: 456,
userId: req.user.id
});
logger.error('Formula evaluation failed', {
formula: 'so_qua * don_gia',
variables: { so_qua: 100, don_gia: 500 },
error: error.message
});
```
## 6. BACKUP VÀ DISASTER RECOVERY
### 6.1 Database Backup Strategy
```bash
#!/bin/bash
# backup-salary-db.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/salary-system"
DB_NAME="salary_db"
# Full backup daily
pg_dump -h $DB_HOST -U $DB_USER -Fc $DB_NAME > \
"$BACKUP_DIR/full_backup_$DATE.dump"
# Incremental backup using WAL archiving
pg_receivewal -h $DB_HOST -U $DB_USER -D "$BACKUP_DIR/wal" -v
# Upload to cloud storage
aws s3 cp "$BACKUP_DIR/full_backup_$DATE.dump" \
s3://salary-backups/database/ --storage-class STANDARD_IA
# Cleanup old backups (keep 30 days)
find $BACKUP_DIR -name "*.dump" -mtime +30 -delete
# Test backup integrity
pg_restore --list "$BACKUP_DIR/full_backup_$DATE.dump" > /dev/null
if [ $? -eq 0 ]; then
echo "Backup verification successful"
else
echo "Backup verification failed" | mail -s "Backup Alert" admin@company.com
fi
```
### 6.2 Application Recovery
```javascript
// disaster-recovery.service.js
class DisasterRecoveryService {
async createSystemSnapshot() {
const snapshot = {
timestamp: new Date(),
database_backup: await this.createDatabaseBackup(),
file_storage_backup: await this.backupFileStorage(),
configuration_backup: await this.backupConfigurations()
};
return await this.uploadSnapshotToSecureStorage(snapshot);
}
async restoreFromSnapshot(snapshotId) {
try {
const snapshot = await this.downloadSnapshot(snapshotId);
// Restore database
await this.restoreDatabase(snapshot.database_backup);
// Restore files
await this.restoreFileStorage(snapshot.file_storage_backup);
// Restore configurations
await this.restoreConfigurations(snapshot.configuration_backup);
// Verify system integrity
await this.verifySystemIntegrity();
return { success: true, message: 'System restored successfully' };
} catch (error) {
throw new Error(`Recovery failed: ${error.message}`);
}
}
}
```
## 7. TRIỂN KHAI VÀ CI/CD
### 7.1 Docker Configuration
```dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Change ownership and switch to non-root user
CHOWN nodejs:nodejs /app
USER nodejs
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
EXPOSE 3000
CMD ["node", "server.js"]
```
### 7.2 Kubernetes Deployment
```yaml
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: salary-system
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: salary-system
template:
metadata:
labels:
app: salary-system
spec:
containers:
- name: salary-app
image: salary-system:latest
ports:
- containerPort: 3000
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: salary-service
spec:
selector:
app: salary-system
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
```
### 7.3 CI/CD Pipeline
```yaml
# .github/workflows/deploy.yml
name: Deploy Salary System
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: salary_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
env:
DATABASE_URL: postgresql://postgres:test@localhost:5432/salary_test
- name: Run security audit
run: npm audit --audit-level high
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t salary-system:${{ github.sha }} .
- name: Push to registry
run: |
docker tag salary-system:${{ github.sha }} registry.company.com/salary-system:${{ github.sha }}
docker push registry.company.com/salary-system:${{ github.sha }}
deploy:
needs: [test, build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
kubectl set image deployment/salary-system \
salary-app=registry.company.com/salary-system:${{ github.sha }} \
-n production
kubectl rollout status deployment/salary-system -n production
```
## 8. KẾT LUẬN
Kiến trúc hệ thống tính lương này được thiết kế để:
### **Tính mở rộng (Scalability)**
- Microservices architecture
- Horizontal scaling với container
- Database partitioning
- Load balancing
### **Độ tin cậy (Reliability)**
- Database replication
- Automatic backup & recovery
- Health checks & monitoring
- Error handling & retry logic
### **Bảo mật (Security)**
- Multi-layer authentication
- Data encryption at rest & in transit
- Audit trail cho tất cả actions
- Role-based access control
### **Hiệu năng (Performance)**
- Redis caching layer
- Database optimization
- Async processing
- CDN cho static files
### **Bảo trì (Maintainability)**
- Clean architecture
- Comprehensive logging
- Automated testing
- CI/CD pipeline
Hệ thống này có thể xử lý từ vài trăm đến hàng chục nghìn nhân viên, đáp ứng được các yêu cầu về tính lương linh hoạt và phức tạp của doanh nghiệp Việt Nam.