3,192,822,080 đ
2,431,852,190 đ
760,969,890 đ
524,185,420 đ
NHẬT KÝ ĐỒNG BỘ ONLINE 3 BỘ PHẬN
Mọi thao tác sửa ngày công (Nhân sự), chốt số lượng SP (8 Tổ SX) hay điều chỉnh phụ cấp (Kế toán) đều được lưu vào SQLite luong_dat_dang.db và đồng bộ tức thì:
Quỹ Lương Thực Lĩnh & Thu Nhập Bình Quân Theo Bộ Phận (20 Bộ Phận)
Tự động vẽ lại theo thời gian thực khi các bộ phận cập nhật dữ liệu
BẢNG ĐỐI CHIẾU CÔNG THỨC TÍNH LƯƠNG: KHỐI TỔ SẢN XUẤT vs. KHỐI BỘ PHẬN PHỤC VỤ
| Khoản mục lương (Cột) | Khối Tổ Sản Xuất (Tổ 1 ➔ Tổ 8: Ăn lương Sản Phẩm) | Khối Bộ Phận Phục Vụ (Kho, Cắt, QC, KT, CĐ, VP, Bếp, BV...) |
|---|---|---|
| 1. Lương chính (Cột U) Nguồn: Cột E |
Ăn theo Tổng Lương Sản Phẩm thực tế (
U = E)• Mỗi mã hàng: |
Ăn theo Lương thời gian / Hệ số & Ngày công (
U = (E / 26) × G)• Định mức |
| 2. Lương tăng ca ngày thường (Cột V) & Chủ nhật (Cột X) |
Đơn giá bậc thang cố định theo số giờ OT mỗi ngày (Sheet CONG cột AK):
• 0.5h = 7,500đ | 1.0h = 15,000đ | 1.5h = 25,000đ |
Tính theo đơn giá giờ chuẩn 9 tiếng/ngày × Hệ số 150% và 200%:
• Tăng ca ngày thường: |
| 3. Khuyến khích năng suất (Cột AL) |
Thưởng lũy tiến theo Mốc Lương Sản Phẩm (chuẩn 26 công):
• |
Thưởng theo tỷ lệ cố định 4.5% trên lương thời gian thực tế:
• Công thức: |
BẢNG TỔNG HỢP LƯƠNG TOÀN CÔNG TY THEO TỔ / BỘ PHẬN (SHEET TL)
Tự động cập nhật khi Nhân sự hoặc Tổ SX điều chỉnh công / sản lượng ở các phân hệ
| STT | Tổ / Bộ phận | Khối | Phương thức tính | Số LĐ | Lương chính (U) | Lương tăng ca (V+X) | Chuyên cần (AK) | KK Năng suất (AL) | Bù lương ABC (BA) | Khấu trừ (BH+CĐ+Phạt) | Thực lĩnh (BE) | Bình quân/Người |
|---|
Quản Lý Bảng Chấm Công (CONG, CN200), Xét Chuyên Cần (CC) & Chế Độ Phúc Lợi
Mọi chỉnh sửa ngày công, giờ tăng ca, nghỉ phép, 6S, con nhỏ hoặc biên bản phạt tại đây sẽ tự động lưu Database và đẩy sang Kế toán tiền lương.
Khi thêm tháng mới (VD: 09/2026, 10/2026...) hoặc up 5 file Nhân sự & file Quy trình, dữ liệu được tự động lưu vĩnh viễn tại 2 nơi ngay trong thư mục chứa TL.py trên máy tính:
(1) File JSON riêng có tên đúng của tháng đó THANG_03_2026.json (nằm ngay cạnh file TL.py), (2) Thư mục riêng DU_LIEU_CAC_THANG/THANG_03_2026/ và (3) CSDL tổng luong_dat_dang_db.json.
Tổng Hợp Tự Động 5 File Nhân Sự Tháng 03/2026 (Vân Tay + Lỗi Quẹt Thẻ + Đi Muộn Về Sớm + Đào Tạo Nội Quy + Trích Ngang) ➔ Tạo BANG_CONG_TT_THANG_03_2026
Khi chọn bất kỳ tháng nào ở thanh KỲ LƯƠNG (ví dụ Tháng 09/2026 hoặc Tháng 10/2026), toàn bộ 5 ô nạp file bên dưới sẽ tự động chuyển sang đúng tháng đó. Sau khi nạp 5 file và nhấn ghép nối, hệ thống tự động lưu vào DU_LIEU_CAC_THANG/THANG_03_2026/ và xuất ra file BANG_CONG_TT_THANG_03_2026.xls để kiểm tra!
DỮ LIỆU VÂN TAY T03.2026.xlsx
Log máy chấm công (Vào 1, Ra 1, Vào 2, Ra 2) ngày 01..31 của 21 chuyền/bộ phận.
LỖI QUẸT THẺ T03_2026.xlsx
Bù giờ quên quẹt thẻ & diện miễn chấm vân tay (00797 Chiến, 00998 Thông, 00067 Dương, Bếp).
ĐI MUỘN VỀ SỚM T03_2026.xlsx
Quy đổi Cắt phép 1h➔7/1P, 4.5h➔3.75/4.25P, P, Ro, R, Ô & phạt đi muộn 30,000đ.
ĐÀO TẠO NỘI QUY T03.2026.xls
Công nhân mới học nội quy 30P = 0.5h = 0.0625 công, ghi 7.5/0.5H hưởng nguyên lương.
DANH_SACH_TRICH_NGANG_T03_2026.xlsx
Phân bổ Tổ/Bộ phận mới, Thời vụ (52 LĐ), Thai sản & LĐ nghỉ việc trong tháng (NV).
Công Cụ Tính Nhanh Chuyên Cần & Tăng Ca
Chuẩn công thức Sheet CONG (AK) & Sheet CC (AJ)
BẢNG DỮ LIỆU CHẤM CÔNG & CHẾ ĐỘ NHÂN SỰ — THÁNG 03/2026 (313 LAO ĐỘNG)
Nhấn vào nút "Sửa Công/Chế độ" để cập nhật trực tuyến và tự động tính lại bảng lương
| MNV | Họ tên | Bộ phận | Công (G/AI) | Giờ OT (H/AJ) | [Ẩn AK] Tiền OT | Giờ CN (I) | Phép (L) | [Ẩn N] Học NQ | [Ẩn O] Hiếu hỉ | [Ẩn T] Nghỉ Ro | [Ẩn AY] Nghỉ NV | Chuyên cần (AK) | 6S (AT) | Phạt TT (BD) | Tác vụ |
|---|
Quản Lý Lương Sản Phẩm & Mua Bán Công Đoạn Theo Từng Tổ (Hỗ Trợ Up 1 Hoặc Nhiều Mã Hàng / Tổ Trong Tháng)
Chọn Tổ Sản Xuất (Tổ 1 ➔ Tổ 8) bên phải để xem và tải lên 1 file hoặc nhiều file mã hàng (.xlsx / .xls) của Tổ đó trong Tháng 03/2026. Hệ thống tự động cộng dồn tất cả các mã hàng và đẩy thẳng sang Kế toán tiền lương.
Quản Lý & Upload File Lương Sản Phẩm + Mua Bán Công Đoạn Của Tổ 1 — Tháng 03/2026
Trong 1 tháng, Tổ 1 có thể làm 1 mã hàng hoặc nhiều mã hàng. Bạn chỉ cần chọn đúng Tổ rồi nhấn nút "📤 Up (1 hoặc Nhiều) File Mã Hàng Của Tổ 1" (cấu trúc chuẩn 4 sheet: QT • MUA BÁN • LƯƠNG • LÍ LỊCH). Hệ thống sẽ lưu từng file mã hàng và tự động cộng dồn toàn bộ các mã hàng trong tháng của Tổ 1 sang Bảng lương Kế toán!
Mô Phỏng Tính Lương Sản Phẩm & Bộ Khung
Test nhanh công thức LUONG_SAN_PHAM.xlsx
BẢNG LƯƠNG SẢN PHẨM CHI TIẾT - TỔ 1
Hiển thị chi tiết Đơn giá, Số lượng, Mua/Bán từng Mã hàng và đồng bộ trực tiếp sang Kế toán
| MNV | Họ tên | Vị trí | Chi tiết các Mã hàng (Đơn giá × SL + Mua - Bán) | Tổng Lương SP (Z) | Tác vụ Tổ SX |
|---|
SỔ NHẬT KÝ MUA – BÁN CÔNG ĐOẠN HÀNG NGÀY TRÊN CHUYỀN (TỰ ĐỘNG CÂN BẰNG: TỔNG MUA == TỔNG BÁN)
Khi Công nhân A làm hộ công đoạn cho Công nhân B, hệ thống tự động +Cộng tiền Mua hàng cho người làm hộ và -Trừ tiền Bán hàng của người nhờ làm, không bao giờ lệch quỹ lương tổ.
| Ngày | Tổ SX | Mã hàng | Tên công đoạn hỗ trợ | Người Làm Hộ (+Mua hàng) | Người Nhờ Làm (-Bán hàng) | Số lượng × Đơn giá | Số tiền đối ứng (±) |
|---|
Bảng Thanh Toán Lương Tổng Hợp (Kỳ Lương Tháng 03/2026), Máy Tính Mô Phỏng & Xuất Chuẩn File BL.xlsx (61 Cột)
Tự động nhận dữ liệu Chấm công từ Nhân sự + Lương sản phẩm từ 8 Tổ SX + Hệ số từ TN_CC_XX để tính Thực lĩnh (Cột BE).
Tại đây, Kế toán có thể nhấn "Tổng Hợp Nhân Sự + Quy Trình" để khớp toàn bộ dữ liệu trong tháng, sau đó bấm chọn từng Tổ / Bộ phận ở thanh bên dưới để kiểm tra bảng lương chi tiết 61 cột (A ➔ BI) của riêng Tổ/Bộ phận đó và nhấn "📥 Xuất BL.xlsx Riêng Của Bộ Phận Đang Chọn" hoặc "📥 Xuất BL.xlsx Toàn Công Ty"!
BL.xlsx RIÊNG CỦA BỘ PHẬN ĐÓ (THÁNG 03/2026):
Đang xem: Tất cả bộ phận
MÁY TÍNH MÔ PHỎNG LƯƠNG ĐA NĂNG (CHUẨN CÔNG THỨC ĐẠT ĐĂNG THÁI THUỴ)
Chuyển đổi linh hoạt giữa Công nhân Tổ SX (Ăn sản phẩm) và Nhân viên Bộ phận Phục vụ (Ăn hệ số/thời gian 9h/ngày)
BẢNG THANH TOÁN LƯƠNG CHI TIẾT TOÀN CÔNG TY — ĐẦY ĐỦ 61 CỘT CHUẨN FILE BL.XLSX (A ➔ BI) Đã mở khóa 28 Cột Ẩn Excel
Nhấn "Chế độ 58 Cột (A ➔ BF)" để xem toàn bộ 58 cột (bao gồm các cột ẩn G..T, Y, Z, AA..AJ, AO..AY, BA, BD), hoặc dùng bộ lọc nhanh bên dưới để kiểm tra từng khoản đặc thù.
17 Sheet Ẩn, 28 Cột Ẩn (A ➔ BF), 7 Dòng Gán BE=0 (NV) & Các Dòng Ẩn Dưới Dòng TỔNG
Toàn bộ dữ liệu từ các sheet ẩn HTĐV, Bộ khung, TH, CN, AT, TT, TS, PS, CN200, 6S, TN_CC_XX, SP QC 2 đã được giải mã và tích hợp trực tiếp.
1. 17 Sheet Ẩn & 28 Cột Ẩn Trong BANG_LUONG_THANH_TOAN.xlsx
• 17 Sheet ẩn (`state=hidden`): TH, BHXH, CHECK_BHXH, DSTN, CN, AT, TT, TS, HTĐV, PS, CN200, 6S, TN_CC_XX, CC, Phiếu lương, M, M_GT, Bộ khung.
• 28 Cột ẩn trung gian trong 58 cột (A..BF): Gồm D, E, F (Lương HĐLĐ, Lương gốc SP/Hệ số, Định mức TN), I, J, K, M, N, O, P, Q, R, S, T (Giờ CN200, Học nội quy 0.0625 công, Hiếu hỉ, Ngừng việc, Hỗ trợ GT, Công Ro), X, Y, Z, AC, AD, AE, AF, AG, AH, AI, AJ (Lương CN, Giới tính M/F, Thai sản TS, Lương học tập AD, Lương điều chuyển AH, Hỗ trợ GT AI/AJ), AO, AP, AQ, AR, AS, AU, AW, AX, AY, BA, BD.
• 7 Dòng gán cứng BE = 0 (Nghỉ việc NV / Chuyển Thời vụ): 01242 Đoàn Thị Yến (Tổ 4), 01107 Phạm Thị Yên (Tổ 5), 01464 Nguyễn Thị Nguyệt (Tổ 5 ➔ Thời vụ), 01470 Lê Thị Liền (Tổ 5), 00322 Trần Mạnh Lộc (Tổ 6), 01404 Nguyễn Thị Quyên (Cut), 00337 Nguyễn Thị Hảo (Hoàn thiện).
2. Giải Mã Các Khoản Tiền Đặc Thù Ở Cột Ẩn (AH, AI, AQ, AR, AS, BA)
• Cột AR (Hỗ trợ 4 ngày vào chuyền / Chia nguồn): 175 công nhân tại Tổ 2, 3, 4, 5, 6, 8 và 01239 (Tổ 7) nhận từ 190,222đ đến 1,426,242đ/người (Tổng = 100,987,510 đ).
• Cột AS (Hỗ trợ đầu vào + Giới thiệu CN may - Sheet ẩn HTĐV): Bậc A = 2.1tr/26*Công, Bậc B = 1.5tr/26*Công, Bậc C = 500k/26*Công + Thưởng đi làm đúng hẹn 300k + Giới thiệu 1tr-1.5tr (Tổng = 16,275,096 đ).
• Cột BA (Bù lương ABC CN mới & Bù lương Bộ khung): Bù thu nhập tối thiểu CN mới (Bậc A 10.1tr, Bậc B 9.46tr, Bậc C 8.46tr) và Bù lương Tổ trưởng/Tổ phó theo Sheet ẩn Bộ khung (Tổng = 8,155,520 đ).
• Cột AH & AI: 01131 Lâm Thị Minh (Tổ 7) có Lương điều chuyển AH = 4,457,847 đ; 01448 Vũ Thị Tơ (Tổ 6) có Hỗ trợ GT AI = 1,222,906 đ; 01447 Bùi Thị Ngát (Tổ 8) có AI = 524,103 đ.
3. Cột Ẩn AK (BANG_CONG) & Dòng Ẩn Dưới TỔNG (LUONG_SAN_PHAM)
• Cột ẩn AK (BANG_CONG.xlsx): Tính tiền tăng ca ngày thường cho 8 Tổ SX theo bậc thang từng ca: 0.5h=7.5k | 1h=15k | 1.5h=25k | 2h=35k | 2.5h=45k | 3h=60k | 3.5h=72.5k.
• Dòng Nguồn tổ & NNM (LUONG_SAN_PHAM.xlsx): Cuối mỗi Tổ SX có dòng Nguồn tổ (chia đều về cột AU bên Bảng lương và trừ ngược -Nguồn tổ ở dòng cuối để cân bằng) và dòng NNM (Nguồn nhà máy).
• Các dòng nằm DƯỚI dòng TỔNG (Dòng 35..57): Gồm dòng QC2 (chuyển sang sheet SP QC 2 nhân 8% thưởng NS) và các dòng Mua hàng chéo giữa các tổ (link về cột Y - MUA HÀNG CÁC TỔ của tổ gốc).
| Nhóm Bộ Phận | Cách Tính Lương Chính (U, V, W, X) | Thưởng KKNS (AL) | Chuyên Cần (AK) | Xăng Xe (AN) | Thưởng 6S (AT) | Giấy VS + Khẩu Trang + Kéo Bấm (AV) | Bù Lương / Phụ Cấp Đặc Thù | Các Khoản Trừ (BB, BC, BD) |
|---|
| MNV | Họ tên | Bộ phận | Còn hưởng (AT) | Mức bị trừ | Ghi chú |
|---|
| MNV | Họ tên | Bộ phận | Mức đóng (D) | DN đóng (21.5%) | Trừ NLĐ 10.5% (BB) |
|---|
| STT | Mã NV | Họ và tên | Bộ phận | Vị trí | Bậc tay nghề | Ngày vào | Mức thưởng gốc (H) | Tổng cộng (I) | Loại hỗ trợ | Người được giới thiệu |
|---|
| STT | Mã NV | Họ tên | Lương HĐLĐ (D) | Lương CB Bộ khung (E) | Định mức TN (F) | Công (G) | Giờ TC (H) | Thưởng Doanh thu (AQ = TH!J/26*G) | Tổng TN theo Doanh thu (AZ) | Tổng TN theo SP tại Tổ (BA) | Chênh lệch (BB) | Bù lương vào Cột BA (BC) |
|---|
| STT | Mã NV | Họ tên | Bộ phận | Mức hưởng (AM) |
|---|
| Sheet Ẩn | Mã NV / Tổ | Họ tên / Nội dung | Bộ phận | Giá trị / Ngày chế độ |
|---|
| STT | Mã NV | Họ và tên | Bộ phận | Tiền phạt lần này | Tổng trừ vào Cột BD |
|---|
File TL.py Độc Lập 100% — Gõ python TL.py Là Chạy Ngay!
Sử dụng 100% thư viện có sẵn trong bộ cài Python chuẩn của Windows (http.server, sqlite3, json, socket, webbrowser). Tự động tạo Database SQLite luong_dat_dang.db và phát Web App qua mạng LAN cho cả 3 bộ phận.
TL.py vào thư mục TINHLUONG
Tải file TL.py (đã đính kèm bên ngoài khung chat) lưu đè vào thư mục C:\\Users\\Welcome\\Desktop\\TINHLUONG\\TL.py. Bạn không cần cài thêm bất kỳ thư viện nào.
python TL.py
Tại cửa sổ CMD:C:\\Users\\Welcome\\Desktop\\TINHLUONG>python TL.py
Ứng dụng sẽ tự tạo file luong_dat_dang.db và tự bật trình duyệt tại http://localhost:8000.
Trên màn hình CMD sẽ hiện dòng:Mở từ máy khác (LAN): http://192.168.x.x:8000
Gửi đường link đó qua Zalo cho máy tính/điện thoại của Nhân sự, 8 Tổ trưởng SX và Kế toán để làm việc chung trực tuyến!
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
================================================================================
PHẦN MỀM QUẢN TRỊ & TÍNH LƯƠNG ONLINE (TL.PY - BẢN CHẠY NGAY KHÔNG CẦN CÀI THƯ VIỆN)
Đơn vị: CÔNG TY TNHH MAY ĐẠT ĐĂNG THÁI THUỴ
================================================================================
ƯU ĐIỂM:
- Sử dụng 100% Thư viện Chuẩn có sẵn của Python (http.server, sqlite3, json,
zlib, base64, socket, webbrowser). KHÔNG CẦN cài đặt fastapi hay bất kỳ
thư viện ngoài nào -> Không bao giờ bị lỗi ModuleNotFoundError!
- Tự động khởi tạo Cơ sở dữ liệu SQLite (luong_dat_dang.db) ngay trong thư mục
chứa file TL.py với đầy đủ 313 lao động, 20 bộ phận và bảng lương sản phẩm
của 8 Tổ Sản Xuất (Tháng 03/2026).
- Kết nối Online qua mạng nội bộ (LAN / Wi-Fi) giữa 3 bộ phận:
1. PHÒNG NHÂN SỰ: Chấm công, nghỉ phép, xét chuyên cần, 6S, con nhỏ, phạt TT
2. TỔ SẢN XUẤT (Tổ 1 -> Tổ 8): Nhập sản lượng chốt chuyền, đơn giá, mua/bán công đoạn
3. KẾ TOÁN TIỀN LƯƠNG: Tự động tổng hợp bảng lương thanh toán, in Phiếu lương, xuất CSV
CÁCH CHẠY TRÊN WINDOWS:
Chỉ cần mở Command Prompt (CMD) tại thư mục chứa file và gõ:
python TL.py
Trình duyệt sẽ tự động mở địa chỉ http://localhost:8000
================================================================================
"""
import os
import sys
import json
import zlib
import base64
import socket
import sqlite3
import threading
import webbrowser
from datetime import datetime
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse
# Đảm bảo hiển thị tiếng Việt có dấu trên màn hình CMD Windows
if sys.platform == "win32":
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(BASE_DIR, "luong_dat_dang_db.json")
DB_LOCK = threading.Lock()
MEMORY_DB = {}
# ==============================================================================
# 1. HẰNG SỐ ĐỊNH MỨC & ENGINE TÍNH LƯƠNG CHUẨN ĐẠT ĐĂNG THÁI THUỴ
# ==============================================================================
NGAY_CONG_CHE_DO = 26.0
LUONG_CB_CHUAN = 4655000.0
DINH_MUC_CHUYEN_CAN = 1300000.0
DINH_MUC_XANG_XE = 500000.0
DINH_MUC_GIAY_VS = 50000.0
TY_LE_BHXH_NLD = 0.105
DOAN_PHI_CONG_DOAN = 23275.0
BANG_DON_GIA_OT_SAN_XUAT = {
0.5: 7500.0,
1.0: 15000.0,
1.5: 25000.0,
2.0: 35000.0,
2.5: 45000.0,
3.0: 60000.0,
3.5: 72500.0,
}
def tinh_chuyen_can_chuan(cong_lam_viec: float, phep: float, nghi_kl: float, is_new: bool = False) -> int:
"""Công thức Sheet CC: Miễn trừ 1 ngày phép/tháng, làm tròn bậc 0.5 - 1.0 - 1.5 công."""
aa = float(phep) + float(nghi_kl)
ag = 0.0 if aa <= 0 else max(0.0, aa - 1.0)
if ag < 3.75 / 8.0:
ah = ag
elif ag < 7.5 / 8.0:
ah = 0.5
elif ag <= 8.5 / 8.0:
ah = 1.0
elif ag <= 12.75 / 8.0:
ah = 1.5
else:
ah = ag
if not is_new:
if ah < 0.5:
return 1300000
elif ah == 0.5:
return 1170000
elif ah <= 1.0:
return 910000
elif ah <= 1.5:
return 650000
else:
return 0
else:
if ah > 0:
return 0
return round((1300000.0 / 26.0) * float(cong_lam_viec))
def cap_nhat_bang_luong_nhan_vien(emp: dict) -> dict:
"""
Tính toán lại toàn bộ 58 cột lương (A -> BF) của 1 nhân sự khi có thay đổi từ:
- Phòng Nhân Sự (Bảng công 55 cột A..BC & 10 Sheet phụ trợ ẩn: BHXH, CN, AT, TT, TS, HTĐV, PS, 6S, TN_CC_XX)
- Tổ Sản Xuất (Bảng lương sản phẩm 37 cột A..AK, Nguồn tổ, NNM, QC2 & Mua bán công đoạn)
- Kế Toán Tiền Lương (Bảng lương thanh toán 58 cột A..BF & Sheet ẩn Bộ khung / TH)
"""
group = emp.get("group", "Tổ SX")
dept = emp.get("dept", "Tổ 1")
base_e = float(emp.get("baseOrPiece", 0))
resp_f = float(emp.get("respBase", 0))
bhxh_d = float(emp.get("bhxhBase", 4655000))
cong_g = float(emp.get("cong", 26))
ot_h = float(emp.get("otHours", 0))
cn_i = float(emp.get("cnHours", 0))
cn_j = float(emp.get("cnHoursPhu", 0))
phep_l = float(emp.get("phep", 0))
le_m = float(emp.get("congLe", 0))
hoctap_n = float(emp.get("congHocTap", 0))
hieuhi_o = float(emp.get("congHieuHi", 0))
ngungviec_p = float(emp.get("congNgungViec", 0))
tnld_q = float(emp.get("congTNLD", 0))
unpaid_t = float(emp.get("unpaidLeave", 0))
if group == "Tổ SX":
emp["luongCong"] = round(base_e)
if emp.get("_ot_edited"):
emp["luongOT"] = round(ot_h * 17291.67)
emp["luongCN"] = 0
u = emp["luongCong"]
if u >= 6000000:
emp["kkns"] = round(u * 0.08)
elif u >= 5000000:
emp["kkns"] = round(u * 0.07)
elif u >= 4000000:
emp["kkns"] = round(u * 0.06)
else:
emp["kkns"] = 0
elif dept == "Thời vụ":
emp["luongCong"] = round(base_e * cong_g)
emp["luongOT"] = 0
emp["luongCN"] = 0
emp["kkns"] = 0
else:
emp["luongCong"] = round((base_e / 26.0) * cong_g)
emp["luongOT"] = round((base_e / 26.0 / 9.0) * ot_h * 1.5)
emp["luongCN"] = round((base_e / 26.0 / 9.0) * cn_i * 2.0)
rate_kk = 0.0 if dept in ("Bảo vệ VS", "Bếp") else 0.045
emp["kkns"] = round((base_e * rate_kk / 26.0) * cong_g)
emp["luongResp"] = round((resp_f / 26.0) * (cong_g + cn_j / 9.0 + cn_i / 9.0))
# Các cột chế độ tính trên nền Lương cơ bản BHXH 4,655,000đ (AA -> AG)
sex_y = str(emp.get("sex", "F"))
ts_z = str(emp.get("thaiSan", ""))
if cong_g >= 3 and sex_y == "F" and ts_z in ("", "0", "None") and dept not in ("Thời vụ", "Cơ điện"):
emp["vspn"] = round(4655000.0 / 26.0 / 8.0 * 1.5)
elif cong_g < 3 or sex_y == "M" or ts_z not in ("", "0", "None"):
emp["vspn"] = 0
emp["luongPhep"] = round((4655000.0 / 26.0) * phep_l)
emp["luongLe"] = round((4655000.0 / 26.0) * le_m)
emp["luongHocTap"] = round((4655000.0 / 26.0) * hoctap_n)
emp["luongHieuHi"] = round((4655000.0 / 26.0) * hieuhi_o)
emp["luongNgungViec"] = round((4655000.0 / 26.0) * ngungviec_p)
emp["luongTNLD"] = round((4655000.0 / 26.0) * tnld_q)
if dept != "Thời vụ":
emp["chuyenCan"] = tinh_chuyen_can_chuan(cong_g, phep_l, unpaid_t, hoctap_n > 0)
if dept not in ("Bếp", "Bảo vệ VS"):
emp["xangXe"] = round((500000.0 / 26.0) * (cong_g + cn_j / 8.0 + hoctap_n + cn_i / 9.0))
if emp.get("maKhongAnTrua") not in (None, "", "0", 0):
emp["anTrua"] = round(10000.0 * (cong_g + hoctap_n + cn_j / 8.0))
dm_giay = 50000.0 if (group == "Tổ SX" or dept in ("Kỹ thuật", "Chất lượng", "Thợ nhảy")) else 40000.0
emp["giayVS"] = round(dm_giay) if cong_g > 20 else round((dm_giay / 26.0) * (cong_g + cn_i / 8.0 + cn_j / 8.0 + hoctap_n))
else:
emp["chuyenCan"] = 0
emp["xangXe"] = 0
emp["giayVS"] = 0
emp.pop("_ot_edited", None)
# Cột AZ: Tổng thu nhập = SUM(U..AY) đầy đủ 28 khoản thu nhập
emp["gross"] = round(
float(emp.get("luongCong", 0))
+ float(emp.get("luongOT", 0))
+ float(emp.get("luongResp", 0))
+ float(emp.get("luongCN", 0))
+ float(emp.get("vspn", 0))
+ float(emp.get("luongPhep", 0))
+ float(emp.get("luongLe", 0))
+ float(emp.get("luongHocTap", 0))
+ float(emp.get("luongHieuHi", 0))
+ float(emp.get("luongNgungViec", 0))
+ float(emp.get("luongTNLD", 0))
+ float(emp.get("luongDieuChuyen", 0))
+ float(emp.get("luongHoTroGT", 0))
+ float(emp.get("luongTangCaGT", 0))
+ float(emp.get("chuyenCan", 0))
+ float(emp.get("kkns", 0))
+ float(emp.get("conNho", 0))
+ float(emp.get("xangXe", 0))
+ float(emp.get("anTrua", 0))
+ float(emp.get("docHai", 0))
+ float(emp.get("ht4Ngay", 0))
+ float(emp.get("htDauVao", 0))
+ float(emp.get("thuong6S", 0))
+ float(emp.get("nguonChuyen", 0))
+ float(emp.get("giayVS", 0))
+ float(emp.get("nangNong", 0))
+ float(emp.get("congTacPhi", 0))
+ float(emp.get("phatSinhKhac", 0))
)
emp["bhxhEmp"] = round(bhxh_d * TY_LE_BHXH_NLD)
emp["congDoan"] = 23275 if ((cong_g + le_m) >= 10 and dept != "Thời vụ") else 0
if emp.get("isZeroBE"):
emp["netPay"] = 0
else:
emp["netPay"] = round(
emp["gross"]
+ float(emp.get("buLuongABC", 0))
- emp["bhxhEmp"]
- emp["congDoan"]
- float(emp.get("khauTruKhac", 0))
)
return emp
# ==============================================================================
# 2. DỮ LIỆU GỐC THÁNG 03/2026 (NÉN ZLIB TỰ ĐỘNG NẠP VÀO SQLITE)
# ==============================================================================
SEED_DATA_B64 = "<DỮ_LIỆU_313_LAO_ĐỘNG_ĐẦY_ĐỦ_58_CỘT_VÀ_17_SHEET_ẨN_THÁNG_03_2026_NÉN_ZLIB>"
def load_seed_data() -> dict:
raw_bytes = zlib.decompress(base64.b64decode(SEED_DATA_B64))
return json.loads(raw_bytes.decode("utf-8"))
# ==============================================================================
# 3. QUẢN LÝ CƠ SỞ DỮ LIỆU ĐỒNG BỘ TRỰC TUYẾN (LUONG_DAT_DANG_DB.JSON)
# ==============================================================================
def _persist_to_disk():
try:
with open(DB_PATH, "w", encoding="utf-8") as f:
json.dump(MEMORY_DB, f, ensure_ascii=False, indent=2)
except Exception:
pass
def init_db(force_reset: bool = False):
global MEMORY_DB
with DB_LOCK:
if not force_reset and os.path.exists(DB_PATH):
try:
with open(DB_PATH, "r", encoding="utf-8") as f:
loaded = json.load(f)
if loaded and "employees" in loaded:
MEMORY_DB = loaded
return
except Exception:
pass
seed = load_seed_data()
now_str = datetime.now().strftime("%H:%M:%S %d/%m/%Y")
MEMORY_DB = {
"version": 1,
"currentMonth": "03/2026",
"stdDays": 26.0,
"muaBanLogs": [
{
"id": 1,
"date": "28/03/2026",
"team": "Tổ 1",
"style": "Mã PAN 110/109/145",
"congDoan": "May ráp sườn & tra cổ",
"buyerMnv": "00010",
"buyerName": "Nguyễn Thị Nhung",
"sellerMnv": "00157",
"sellerName": "Nguyễn Thị Diệp",
"soLuong": 450,
"donGia": 520,
"thanhTien": 234000
}
],
"months": {},
"employees": seed["employees"],
"deptSummaries": seed["deptSummaries"],
"lspTeams": seed["lspTeams"],
"boKhung": seed.get("boKhung", []),
"htdv": seed.get("htdv", []),
"ttViPham": seed.get("ttViPham", []),
"cnConNho": seed.get("cnConNho", []),
"tsThaiSan": seed.get("tsThaiSan", []),
"psPhatSinh": seed.get("psPhatSinh", []),
"spQC2": seed.get("spQC2", []),
"spExtra": seed.get("spExtra", {}),
"ds6S_note": seed.get("ds6S_note", []),
"dsAT": seed.get("dsAT", []),
"dsBHXH": seed.get("dsBHXH", []),
"dsTH": seed.get("dsTH", []),
"deptFixedRules": seed.get("deptFixedRules", []),
"auditLogs": [
{
"id": 1,
"timestamp": now_str,
"role": "Hệ thống",
"actor": "Server Python (TL.py)",
"action": "Khởi tạo dữ liệu gốc Tháng 03/2026 (313 LĐ, 20 bộ phận, tổng thực lĩnh 3.192.822.080 đ)"
}
]
}
_persist_to_disk()
def get_full_state() -> dict:
with DB_LOCK:
if not MEMORY_DB:
init_db(force_reset=False)
return MEMORY_DB
def save_state_and_log(employees: list, lsp_teams: dict, role: str, actor: str, action: str) -> dict:
with DB_LOCK:
new_ver = int(MEMORY_DB.get("version", 1)) + 1
MEMORY_DB["version"] = new_ver
MEMORY_DB["employees"] = employees
MEMORY_DB["lspTeams"] = lsp_teams
logs = MEMORY_DB.get("auditLogs", [])
now_str = datetime.now().strftime("%H:%M:%S %d/%m/%Y")
logs.insert(0, {
"id": new_ver,
"timestamp": now_str,
"role": role,
"actor": actor,
"action": action
})
MEMORY_DB["auditLogs"] = logs[:40]
_persist_to_disk()
return MEMORY_DB
# ==============================================================================
# 4. GIAO DIỆN WEB HTML/JS TÍCH HỢP SẴN TRONG SERVER PYTHON
# ==============================================================================
HTML_PAGE_B64 = "<GIAO_DIỆN_WEB_HTML_ĐẦY_ĐỦ_CHỨC_NĂNG_ĐÃ_NÉN_ZLIB>"
def get_html_page() -> bytes:
return zlib.decompress(base64.b64decode(HTML_PAGE_B64))
# ==============================================================================
# 5. HTTP SERVER ĐA LUỒNG KẾT NỐI TRỰC TUYẾN 3 BỘ PHẬN (NHÂN SỰ - TỔ SX - KẾ TOÁN)
# ==============================================================================
class PayrollHTTPRequestHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
# Ghi log gọn gàng trên cửa sổ CMD
sys.stdout.write(f"[ONLINE] {self.address_string()} - {format % args}\n")
def _send_json(self, data: dict, status: int = 200):
raw = json.dumps(data, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(raw)))
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
self.wfile.write(raw)
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
def do_GET(self):
parsed = urlparse(self.path)
if parsed.path in ("/", "/index.html"):
html_bytes = get_html_page()
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(html_bytes)))
self.end_headers()
self.wfile.write(html_bytes)
elif parsed.path == "/api/state":
self._send_json(get_full_state())
elif parsed.path == "/api/download_tl_py":
with open(os.path.abspath(__file__), "rb") as f:
py_bytes = f.read()
self.send_response(200)
self.send_header("Content-Type", "text/x-python; charset=utf-8")
self.send_header("Content-Disposition", "attachment; filename=TL.py")
self.send_header("Content-Length", str(len(py_bytes)))
self.end_headers()
self.wfile.write(py_bytes)
else:
self._send_json({"error": "Not found"}, 404)
def do_POST(self):
parsed = urlparse(self.path)
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length).decode("utf-8") if length > 0 else "{}"
try:
payload = json.loads(body)
except Exception:
payload = {}
state = get_full_state()
employees = state["employees"]
lsp_teams = state["lspTeams"]
if parsed.path == "/api/update_employee":
emp_id = str(payload.get("id", ""))
dept = str(payload.get("dept", ""))
role = str(payload.get("role", "Nhân sự / Kế toán"))
actor = str(payload.get("actor", "Người dùng Online"))
target = None
for e in employees:
if e["id"] == emp_id and (not dept or e["dept"] == dept):
target = e
break
if not target:
self._send_json({"error": "Không tìm thấy nhân viên"}, 404)
return
old_ot = target.get("otHours", 0)
for field in (
"baseOrPiece", "respBase", "bhxhBase", "cong", "otHours", "cnHours", "cnHoursPhu", "cnOtHours",
"phep", "congLe", "congHocTap", "congHieuHi", "congNgungViec", "congTNLD", "unpaidLeave", "congNV",
"luongDieuChuyen", "luongHoTroGT", "luongTangCaGT", "conNho", "anTrua", "docHai",
"ht4Ngay", "htDauVao", "thuong6S", "nguonChuyen", "nangNong", "congTacPhi", "phatSinhKhac",
"buLuongABC", "khauTruKhac"
):
if field in payload:
target[field] = float(payload[field])
if "isZeroBE" in payload:
target["isZeroBE"] = bool(payload["isZeroBE"])
if float(payload.get("otHours", old_ot)) != old_ot:
target["_ot_edited"] = True
cap_nhat_bang_luong_nhan_vien(target)
# Đồng bộ sang bảng Lương Sản Phẩm nếu thuộc Tổ SX
if target["dept"] in lsp_teams:
for r in lsp_teams[target["dept"]]:
if r["mnv"] == target["id"]:
r["totalLSP"] = target["luongCong"]
action_msg = (
f"Cập nhật [{target['dept']}] {target['name']} ({target['id']}): "
f"Công={target['cong']}, OT={target['otHours']}h, Lương chính={target['luongCong']:,}đ -> Thực lĩnh={target['netPay']:,}đ"
)
new_state = save_state_and_log(employees, lsp_teams, role, actor, action_msg)
self._send_json(new_state)
elif parsed.path == "/api/quick_batch_update":
items = payload.get("items", [])
actor = str(payload.get("actor", "Kế toán / Nhân sự"))
role = str(payload.get("role", "Kế toán tiền lương"))
updated_count = 0
for item in items:
emp_id = str(item.get("id", "")).strip()
dept_name = str(item.get("dept", "")).strip()
target = next((e for e in employees if e["id"] == emp_id and (not dept_name or e["dept"] == dept_name)), None)
if not target:
continue
if "otHours" in item and float(item["otHours"]) != float(target.get("otHours", 0)):
target["_ot_edited"] = True
for f_key in ("baseOrPiece", "respBase", "bhxhBase", "cong", "otHours", "cnHours", "phep", "unpaidLeave", "thuong6S", "conNho", "anTrua", "ht4Ngay", "htDauVao", "buLuongABC", "khauTruKhac"):
if f_key in item:
target[f_key] = float(item[f_key])
cap_nhat_bang_luong_nhan_vien(target)
if target["dept"] in lsp_teams:
for r in lsp_teams[target["dept"]]:
if r["mnv"] == target["id"]:
r["totalLSP"] = target["luongCong"]
updated_count += 1
new_state = save_state_and_log(employees, lsp_teams, role, actor, f"Nhập nhanh hàng loạt {updated_count} lao động (Lương SP, Ngày công, 6S, Vi phạm TT)")
self._send_json(new_state)
elif parsed.path == "/api/report_violation":
emp_id = str(payload.get("id", "")).strip()
dept_name = str(payload.get("dept", "")).strip()
v_type = str(payload.get("type", "6S")).strip()
amount = float(payload.get("amount", 50000))
reason = str(payload.get("reason", "Vi phạm nội quy / 6S")).strip()
actor = str(payload.get("actor", "Phòng Nhân sự"))
role = str(payload.get("role", "Nhân sự"))
target = next((e for e in employees if e["id"] == emp_id and (not dept_name or e["dept"] == dept_name)), None)
if not target:
self._send_json({"error": "Không tìm thấy nhân viên"}, 404)
return
if v_type == "6S":
target["thuong6S"] = max(0.0, float(target.get("thuong6S", 250000)) - amount)
elif v_type == "RESET_6S":
target["thuong6S"] = 250000.0
elif v_type == "TT":
target["khauTruKhac"] = float(target.get("khauTruKhac", 0)) + amount
elif v_type == "CN":
target["conNho"] = amount
cap_nhat_bang_luong_nhan_vien(target)
new_state = save_state_and_log(employees, lsp_teams, role, actor, f"Cập nhật [{v_type}] cho {target['name']} ({target['id']}): {reason} -> Thực lĩnh = {target['netPay']:,}đ")
self._send_json(new_state)
elif parsed.path == "/api/update_lsp":
team = str(payload.get("team", "Tổ 1"))
mnv = str(payload.get("mnv", ""))
styles = payload.get("styles", [])
role = str(payload.get("role", "Tổ Sản Xuất"))
actor = str(payload.get("actor", f"Tổ trưởng {team}"))
team_rows = lsp_teams.get(team, [])
target_row = None
for r in team_rows:
if r["mnv"] == mnv:
target_row = r
break
if not target_row:
self._send_json({"error": "Không tìm thấy công nhân trong tổ"}, 404)
return
total_lsp = 0
new_styles = []
for s in styles:
dg = float(s.get("donGia", 0))
sl = float(s.get("soLuong", 0))
mua = float(s.get("muaHang", 0))
ban = float(s.get("banHang", 0))
tt = round(dg * sl + mua - ban)
total_lsp += tt
new_styles.append({
"style": s.get("style", "Mã hàng"),
"donGia": dg,
"soLuong": round(sl),
"muaHang": round(mua),
"banHang": round(ban),
"thanhTien": tt
})
target_row["styles"] = new_styles
target_row["totalLSP"] = round(total_lsp)
# Đẩy tự động sang Bảng Thanh Toán Lương của Kế toán
emp_target = None
for e in employees:
if e["id"] == mnv and e["dept"] == team:
emp_target = e
break
if emp_target:
emp_target["baseOrPiece"] = round(total_lsp)
cap_nhat_bang_luong_nhan_vien(emp_target)
action_msg = (
f"Chốt SP [{team}] {target_row['name']} ({mnv}): "
f"Tổng Lương SP mới = {round(total_lsp):,}đ"
+ (f" -> Thực lĩnh = {emp_target['netPay']:,}đ" if emp_target else "")
)
new_state = save_state_and_log(employees, lsp_teams, role, actor, action_msg)
self._send_json(new_state)
elif parsed.path == "/api/add_employee":
emp_id = str(payload.get("id", "")).strip()
name = str(payload.get("name", "")).strip()
dept = str(payload.get("dept", "Tổ 1")).strip()
sex = str(payload.get("sex", "F"))
base_e = float(payload.get("baseOrPiece", 6500000))
cong_g = float(payload.get("cong", 26))
ot_h = float(payload.get("otHours", 40))
role = str(payload.get("role", "Phòng Nhân Sự"))
actor = str(payload.get("actor", "Nhân sự"))
group = "Tổ SX" if dept.startswith("Tổ ") else "Phục vụ"
pay_type = "Sản phẩm" if group == "Tổ SX" else ("Công nhật (400k/ngày)" if dept == "Thời vụ" else "Thời gian / Hệ số")
new_emp = {
"id": emp_id,
"name": name,
"dept": dept,
"group": group,
"payType": pay_type,
"sex": sex,
"bhxhBase": 4655000 if dept != "Thời vụ" else 0,
"baseOrPiece": round(base_e),
"respBase": float(payload.get("respBase", 0)),
"cong": cong_g,
"otHours": ot_h,
"cnHours": 0,
"phep": 0,
"unpaidLeave": 0,
"luongCong": 0,
"luongOT": round(ot_h * 17291.67) if group == "Tổ SX" else 0,
"luongResp": 0,
"luongCN": 0,
"vspn": 33570 if (sex == "F" and cong_g >= 3 and dept != "Thời vụ") else 0,
"luongPhep": 0,
"chuyenCan": 1300000 if dept != "Thời vụ" else 0,
"kkns": 0,
"conNho": 0,
"xangXe": 500000 if dept not in ("Thời vụ", "Bếp") else 0,
"anTrua": 0,
"docHai": 0,
"ht4Ngay": 0,
"htDauVao": 0,
"thuong6S": 250000 if dept != "Thời vụ" else 0,
"nguonChuyen": 35471 if group == "Tổ SX" else 0,
"giayVS": 50000 if dept != "Thời vụ" else 0,
"gross": 0,
"buLuongABC": 0,
"bhxhEmp": 0,
"congDoan": 23275 if dept != "Thời vụ" else 0,
"khauTruKhac": 0,
"netPay": 0
}
cap_nhat_bang_luong_nhan_vien(new_emp)
employees.insert(0, new_emp)
if group == "Tổ SX":
lsp_teams.setdefault(dept, []).append({
"mnv": emp_id,
"name": name,
"role": "CN",
"styles": [{"style": "Mã hàng chính", "donGia": round(base_e / 2000, 2), "soLuong": 2000, "muaHang": 0, "banHang": 0, "thanhTien": round(base_e)}],
"totalLSP": round(base_e)
})
action_msg = f"Thêm nhân sự mới [{dept}] {name} ({emp_id}) -> Thực lĩnh dự kiến = {new_emp['netPay']:,}đ"
new_state = save_state_and_log(employees, lsp_teams, role, actor, action_msg)
self._send_json(new_state)
elif parsed.path == "/api/create_new_month":
month_name = str(payload.get("month", "04/2026")).strip()
std_days = float(payload.get("stdDays", 26.0))
mode = str(payload.get("mode", "blank")) # 'blank' (reset sản lượng = 0) hoặc 'copy' (sao chép T3)
role = str(payload.get("role", "Kế Toán / Nhân Sự"))
actor = str(payload.get("actor", "Quản trị viên"))
with DB_LOCK:
months_db = MEMORY_DB.setdefault("months", {})
# Save current month snapshot
cur_m = MEMORY_DB.get("currentMonth", "03/2026")
months_db[cur_m] = {
"stdDays": MEMORY_DB.get("stdDays", 26.0),
"employees": json.loads(json.dumps(employees)),
"lspTeams": json.loads(json.dumps(lsp_teams)),
"muaBanLogs": json.loads(json.dumps(MEMORY_DB.get("muaBanLogs", [])))
}
# Prepare new month employees (keep roster, BHXH, base salary for service, reset attendance/piece-rate if blank)
new_emps = json.loads(json.dumps(employees))
new_lsp = json.loads(json.dumps(lsp_teams))
if mode == "blank":
for e in new_emps:
e["cong"] = std_days
e["otHours"] = 0.0
e["cnHours"] = 0.0
e["phep"] = 0.0
e["unpaidLeave"] = 0.0
e["khauTruKhac"] = 0.0
e["buLuongABC"] = 0.0
if e["group"] == "Tổ SX":
e["baseOrPiece"] = 0.0
e["luongOT"] = 0.0
cap_nhat_bang_luong_nhan_vien(e)
for tname, rows in new_lsp.items():
for r in rows:
r["totalLSP"] = 0
for s in r.get("styles", []):
s["soLuong"] = 0
s["muaHang"] = 0
s["banHang"] = 0
s["thanhTien"] = 0
MEMORY_DB["currentMonth"] = month_name
MEMORY_DB["stdDays"] = std_days
MEMORY_DB["employees"] = new_emps
MEMORY_DB["lspTeams"] = new_lsp
MEMORY_DB["muaBanLogs"] = []
months_db[month_name] = {
"stdDays": std_days,
"employees": new_emps,
"lspTeams": new_lsp,
"muaBanLogs": []
}
action_msg = f"Khởi tạo kỳ lương mới [Tháng {month_name}] ({std_days:g} công chuẩn) - Chế độ: {'Làm mới sản lượng đầu tháng' if mode == 'blank' else 'Sao chép định mức'}"
new_state = save_state_and_log(MEMORY_DB["employees"], MEMORY_DB["lspTeams"], role, actor, action_msg)
self._send_json(new_state)
elif parsed.path == "/api/switch_month":
target_m = str(payload.get("month", "03/2026")).strip()
with DB_LOCK:
months_db = MEMORY_DB.setdefault("months", {})
cur_m = MEMORY_DB.get("currentMonth", "03/2026")
months_db[cur_m] = {
"stdDays": MEMORY_DB.get("stdDays", 26.0),
"employees": MEMORY_DB.get("employees", []),
"lspTeams": MEMORY_DB.get("lspTeams", {}),
"muaBanLogs": MEMORY_DB.get("muaBanLogs", [])
}
if target_m in months_db:
snap = months_db[target_m]
MEMORY_DB["currentMonth"] = target_m
MEMORY_DB["stdDays"] = snap.get("stdDays", 26.0)
MEMORY_DB["employees"] = snap.get("employees", [])
MEMORY_DB["lspTeams"] = snap.get("lspTeams", {})
MEMORY_DB["muaBanLogs"] = snap.get("muaBanLogs", [])
MEMORY_DB["version"] = int(MEMORY_DB.get("version", 1)) + 1
_persist_to_disk()
self._send_json(get_full_state())
elif parsed.path == "/api/add_muaban_daily":
# Giao dịch mua bán công đoạn hàng ngày trên chuyền: Tự động +Mua cho người làm hộ và -Bán cho người nhờ làm
date_str = str(payload.get("date", datetime.now().strftime("%d/%m/%Y")))
team = str(payload.get("team", "Tổ 1"))
style_name = str(payload.get("style", "Mã hàng chính"))
cong_doan = str(payload.get("congDoan", "May công đoạn"))
buyer_mnv = str(payload.get("buyerMnv", "")) # Người làm hộ (+Mua hàng)
seller_mnv = str(payload.get("sellerMnv", "")) # Người nhờ làm (-Bán hàng)
qty = float(payload.get("soLuong", 0))
price = float(payload.get("donGia", 0))
amount = round(qty * price)
role = str(payload.get("role", f"Tổ Sản Xuất ({team})"))
actor = str(payload.get("actor", f"Thống kê {team}"))
team_rows = lsp_teams.get(team, [])
buyer_row = next((r for r in team_rows if r["mnv"] == buyer_mnv), None)
seller_row = next((r for r in team_rows if r["mnv"] == seller_mnv), None)
if not buyer_row or not seller_row:
self._send_json({"error": "Không tìm thấy công nhân Mua hoặc Bán trong tổ"}, 404)
return
def _apply_trade(row_obj, delta_mua, delta_ban):
styles = row_obj.setdefault("styles", [])
st_match = next((s for s in styles if s["style"] == style_name), None)
if not st_match:
st_match = styles[0] if styles else {"style": style_name, "donGia": 0, "soLuong": 0, "muaHang": 0, "banHang": 0, "thanhTien": 0}
if not styles:
styles.append(st_match)
st_match["muaHang"] = round(float(st_match.get("muaHang", 0)) + delta_mua)
st_match["banHang"] = round(float(st_match.get("banHang", 0)) + delta_ban)
st_match["thanhTien"] = round(float(st_match.get("donGia", 0)) * float(st_match.get("soLuong", 0)) + st_match["muaHang"] - st_match["banHang"])
row_obj["totalLSP"] = round(sum(float(x.get("thanhTien", 0)) for x in styles))
_apply_trade(buyer_row, amount, 0)
_apply_trade(seller_row, 0, amount)
# Đồng bộ tức thì sang bảng lương Kế toán cho cả 2 công nhân
for mnv_code, r_obj in ((buyer_mnv, buyer_row), (seller_mnv, seller_row)):
emp_obj = next((e for e in employees if e["id"] == mnv_code and e["dept"] == team), None)
if emp_obj:
emp_obj["baseOrPiece"] = r_obj["totalLSP"]
cap_nhat_bang_luong_nhan_vien(emp_obj)
with DB_LOCK:
mb_logs = MEMORY_DB.setdefault("muaBanLogs", [])
mb_logs.insert(0, {
"id": len(mb_logs) + 1,
"date": date_str,
"team": team,
"style": style_name,
"congDoan": cong_doan,
"buyerMnv": buyer_mnv,
"buyerName": buyer_row["name"],
"sellerMnv": seller_mnv,
"sellerName": seller_row["name"],
"soLuong": round(qty),
"donGia": price,
"thanhTien": amount
})
action_msg = (
f"Mua bán CĐ [{team} - {date_str}] {cong_doan} ({style_name}): "
f"{buyer_row['name']} (+Mua {amount:,}đ) <-> {seller_row['name']} (-Bán {amount:,}đ)"
)
new_state = save_state_and_log(employees, lsp_teams, role, actor, action_msg)
self._send_json(new_state)
elif parsed.path == "/api/bulk_attendance":
dept_filter = str(payload.get("dept", "ALL"))
cong_val = float(payload.get("cong", 26.0))
ot_val = float(payload.get("otHours", 48.0))
role = str(payload.get("role", "Phòng Nhân Sự"))
actor = str(payload.get("actor", "Nhân sự"))
count_updated = 0
for e in employees:
if dept_filter == "ALL" or e["dept"] == dept_filter:
e["cong"] = cong_val
e["otHours"] = ot_val
e["_ot_edited"] = True
cap_nhat_bang_luong_nhan_vien(e)
count_updated += 1
action_msg = f"Chấm công hàng loạt [{dept_filter}] cho {count_updated} LĐ: Công={cong_val:g}, OT={ot_val:g}h"
new_state = save_state_and_log(employees, lsp_teams, role, actor, action_msg)
self._send_json(new_state)
elif parsed.path == "/api/reset_db":
init_db(force_reset=True)
self._send_json(get_full_state())
else:
self._send_json({"error": "Unknown API endpoint"}, 404)
def get_lan_ip() -> str:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return "127.0.0.1"
def main():
init_db(force_reset=False)
if "--self-test" in sys.argv:
st = get_full_state()
print(f"SELF_TEST_OK: employees={len(st['employees'])}, depts={len(st['deptSummaries'])}, teams={len(st['lspTeams'])}")
return
port = 8000
server = None
for p in range(8000, 8020):
try:
server = ThreadingHTTPServer(("0.0.0.0", p), PayrollHTTPRequestHandler)
port = p
break
except OSError:
continue
if server is None:
print("Không thể mở cổng mạng (8000-8020). Vui lòng kiểm tra lại.")
return
lan_ip = get_lan_ip()
local_url = f"http://localhost:{port}"
lan_url = f"http://{lan_ip}:{port}"
print("=" * 74)
print(" HỆ THỐNG TÍNH LƯƠNG ONLINE - CÔNG TY TNHH MAY ĐẠT ĐĂNG THÁI THUỴ")
print("=" * 74)
print(f" [OK] Cơ sở dữ liệu SQLite : {DB_PATH}")
print(" [OK] Trạng thái dữ liệu : Đã nạp 313 lao động & 20 bộ phận (Tháng 03/2026)")
print("-" * 74)
print(f" 1. Mở trên máy tính này : {local_url}")
print(f" 2. Mở từ máy khác (LAN) : {lan_url}")
print(" (Gửi link số 2 cho Phòng Nhân Sự, 8 Tổ SX & Kế Toán cùng truy cập)")
print("=" * 74)
print(" Đang tự động mở trình duyệt... (Nhấn Ctrl + C để dừng Server)")
threading.Timer(1.0, lambda: webbrowser.open(local_url)).start()
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nĐã dừng Server tính lương Đạt Đăng Thái Thuỵ.")
server.server_close()
if __name__ == "__main__":
main()