682 lines
21 KiB
Go
682 lines
21 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"web-giao-ban-go/config"
|
|
"web-giao-ban-go/db"
|
|
)
|
|
|
|
// Helper to generate partition filters: namkt IN (...) AND thangkt IN (...)
|
|
func GetThangNamFilter(fromStr, toStr, prefix string) string {
|
|
f, err1 := time.Parse("2006-01-02 15:04:05", fromStr)
|
|
t, err2 := time.Parse("2006-01-02 15:04:05", toStr)
|
|
if err1 != nil || err2 != nil {
|
|
// Try date only
|
|
f, err1 = time.Parse("2006-01-02", fromStr[:10])
|
|
t, err2 = time.Parse("2006-01-02", toStr[:10])
|
|
if err1 != nil || err2 != nil {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
thangsMap := make(map[string]bool)
|
|
namsMap := make(map[string]bool)
|
|
|
|
curr := time.Date(f.Year(), f.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
end := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
count := 0
|
|
|
|
for !curr.After(end) && count < 24 {
|
|
thangsMap[fmt.Sprintf("'%02d'", curr.Month())] = true
|
|
namsMap[fmt.Sprintf("'%d'", curr.Year())] = true
|
|
curr = curr.AddDate(0, 1, 0)
|
|
count++
|
|
}
|
|
|
|
var thangsArr, namsArr []string
|
|
for k := range thangsMap {
|
|
thangsArr = append(thangsArr, k)
|
|
}
|
|
for k := range namsMap {
|
|
namsArr = append(namsArr, k)
|
|
}
|
|
|
|
if len(thangsArr) == 0 || len(namsArr) == 0 {
|
|
return ""
|
|
}
|
|
|
|
p := ""
|
|
if prefix != "" {
|
|
p = prefix + "."
|
|
}
|
|
return fmt.Sprintf(" AND %snamkt IN (%s) AND %sthangkt IN (%s) ",
|
|
p, strings.Join(namsArr, ","),
|
|
p, strings.Join(thangsArr, ","),
|
|
)
|
|
}
|
|
|
|
// GET /api/databases
|
|
func HandleGetDatabases(w http.ResponseWriter, r *http.Request) {
|
|
cfg := config.Get()
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": true,
|
|
"data": cfg.Database.Databases,
|
|
})
|
|
}
|
|
|
|
type OutpatientData struct {
|
|
Khoa []map[string]interface{} `json:"khoa"`
|
|
BANT map[string]interface{} `json:"bant"`
|
|
YDCT map[string]interface{} `json:"ydct"`
|
|
CLS []map[string]interface{} `json:"cls"`
|
|
Top10 []map[string]interface{} `json:"top10"`
|
|
TiemNgua int `json:"tiemngua"`
|
|
}
|
|
|
|
func queryOutpatient(database, fromDate, toDate string) (*OutpatientData, error) {
|
|
conn, err := db.GetDB(database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fnKb := GetThangNamFilter(fromDate, toDate, "kb")
|
|
fnLs := GetThangNamFilter(fromDate, toDate, "ls")
|
|
fnNone := GetThangNamFilter(fromDate, toDate, "")
|
|
|
|
out := &OutpatientData{
|
|
Khoa: make([]map[string]interface{}, 0),
|
|
BANT: map[string]interface{}{"moi": 0, "taikham": 0, "tong": 0},
|
|
YDCT: map[string]interface{}{"dien_cham": 0, "vltl": 0, "sac_thuoc": 0, "cay_chi": 0},
|
|
CLS: make([]map[string]interface{}, 0),
|
|
Top10: make([]map[string]interface{}, 0),
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
var mu sync.Mutex
|
|
|
|
// 1. Khoa KCB
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sqlKhoa := fmt.Sprintf(`
|
|
SELECT
|
|
COALESCE(dv.madv, 'KHAC') AS madv,
|
|
COALESCE(dv.tendv, 'Khoa khác') AS tendv,
|
|
COUNT(kb.makb) AS tong_kham,
|
|
SUM(CASE WHEN dt.bhyt IN (1, 2) THEN 1 ELSE 0 END) AS bhyt,
|
|
SUM(CASE WHEN dt.bhyt IN (0, 4) THEN 1 ELSE 0 END) AS thuphi,
|
|
SUM(CASE WHEN kb.dakham = 6 THEN 1 ELSE 0 END) AS chuyenvien
|
|
FROM current.khambenh kb
|
|
LEFT JOIN current.psdangky dk ON kb.mabn = dk.mabn AND kb.makb = dk.makb AND COALESCE(dk.xoa, 0) = 0
|
|
LEFT JOIN current.dmdoituong dt ON dk.madt = dt.madt
|
|
LEFT JOIN current.dmdonvi dv ON kb.madv = dv.madv
|
|
WHERE kb.ngaykcb BETWEEN $1 AND $2 %s
|
|
AND COALESCE(kb.xoa, 0) = 0
|
|
AND COALESCE(kb.dakham, 0) != 0
|
|
GROUP BY dv.madv, dv.tendv
|
|
ORDER BY tong_kham DESC;
|
|
`, fnKb)
|
|
|
|
rows, err := conn.Query(sqlKhoa, fromDate, toDate)
|
|
if err == nil {
|
|
defer rows.Close()
|
|
var list []map[string]interface{}
|
|
for rows.Next() {
|
|
var madv, tendv string
|
|
var tong, bhyt, thuphi, cv int
|
|
_ = rows.Scan(&madv, &tendv, &tong, &bhyt, &thuphi, &cv)
|
|
list = append(list, map[string]interface{}{
|
|
"madv": madv, "tendv": tendv, "tong_kham": tong,
|
|
"bhyt": bhyt, "thuphi": thuphi, "chuyenvien": cv,
|
|
})
|
|
}
|
|
mu.Lock()
|
|
out.Khoa = list
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// 2. BANT
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sqlBant := `
|
|
SELECT
|
|
COALESCE(SUM(CASE WHEN COALESCE(dk.taikham, 0) = 0 THEN 1 ELSE 0 END), 0) AS moi,
|
|
COALESCE(SUM(CASE WHEN COALESCE(dk.taikham, 0) = 1 THEN 1 ELSE 0 END), 0) AS taikham,
|
|
COUNT(*) AS tong
|
|
FROM current.bnnoitru nt
|
|
JOIN current.psdangky dk ON nt.mabn = dk.mabn AND nt.makb = dk.makb AND COALESCE(dk.xoa, 0) = 0
|
|
WHERE nt.namvien = 0 AND COALESCE(nt.xoa, 0) = 0
|
|
AND nt.ngayvv BETWEEN $1 AND $2;
|
|
`
|
|
var moi, taikham, tong int
|
|
err := conn.QueryRow(sqlBant, fromDate, toDate).Scan(&moi, &taikham, &tong)
|
|
if err == nil {
|
|
mu.Lock()
|
|
out.BANT = map[string]interface{}{"moi": moi, "taikham": taikham, "tong": tong}
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// 3. YDCT
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sqlYdct := fmt.Sprintf(`
|
|
SELECT
|
|
COALESCE(SUM(CASE WHEN cls.tencls ILIKE '%%Điện châm%%' THEN ls.soluong ELSE 0 END), 0) AS dien_cham,
|
|
COALESCE(SUM(CASE WHEN cls.tencls ILIKE '%%Vật lý trị liệu%%' OR cls.tencls ILIKE '%%VLTL%%' OR cls.macls IN ('DI131','DI135') THEN ls.soluong ELSE 0 END), 0) AS vltl,
|
|
COALESCE(SUM(CASE WHEN cls.tencls ILIKE '%%Sắc thuốc%%' THEN ls.soluong ELSE 0 END), 0) AS sac_thuoc,
|
|
COALESCE(SUM(CASE WHEN cls.tencls ILIKE '%%Cấy chỉ%%' OR cls.tencls ILIKE '%%Chôn chỉ%%' THEN ls.soluong ELSE 0 END), 0) AS cay_chi
|
|
FROM current.chidinhcls ls
|
|
JOIN current.dmcls cls ON ls.macls = cls.macls
|
|
WHERE ls.ngaykcb BETWEEN $1 AND $2 %s
|
|
AND COALESCE(ls.xoa, 0) = 0;
|
|
`, fnLs)
|
|
var dc, vltl, st, cc float64
|
|
err := conn.QueryRow(sqlYdct, fromDate, toDate).Scan(&dc, &vltl, &st, &cc)
|
|
if err == nil {
|
|
mu.Lock()
|
|
out.YDCT = map[string]interface{}{
|
|
"dien_cham": int(dc), "vltl": int(vltl), "sac_thuoc": int(st), "cay_chi": int(cc),
|
|
}
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// 4. CLS
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sqlCls := fmt.Sprintf(`
|
|
SELECT
|
|
cls.kho,
|
|
cls.maloai,
|
|
COALESCE(loaicls.tenloai, cls.maloai) AS tenloai,
|
|
COUNT(DISTINCT ls.makb) AS so_bn,
|
|
COUNT(*) AS so_luot
|
|
FROM current.chidinhcls ls
|
|
JOIN current.dmcls cls ON ls.macls = cls.macls
|
|
LEFT JOIN current.dmloaicls loaicls ON cls.maloai = loaicls.maloai
|
|
WHERE ls.ngaykcb BETWEEN $1 AND $2 %s
|
|
AND COALESCE(ls.xoa, 0) = 0
|
|
AND cls.kho IN ('XN', 'HA', 'CN')
|
|
GROUP BY cls.kho, cls.maloai, loaicls.tenloai
|
|
ORDER BY cls.kho, count(*) DESC;
|
|
`, fnLs)
|
|
rows, err := conn.Query(sqlCls, fromDate, toDate)
|
|
if err == nil {
|
|
defer rows.Close()
|
|
var list []map[string]interface{}
|
|
for rows.Next() {
|
|
var kho, maloai, tenloai string
|
|
var soBn, soLuot int
|
|
_ = rows.Scan(&kho, &maloai, &tenloai, &soBn, &soLuot)
|
|
list = append(list, map[string]interface{}{
|
|
"kho": kho, "maloai": maloai, "tenloai": tenloai, "so_bn": soBn, "so_luot": soLuot,
|
|
})
|
|
}
|
|
mu.Lock()
|
|
out.CLS = list
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// 5. Top 10
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sqlTop10 := fmt.Sprintf(`
|
|
SELECT maicd, kqcdoan, COUNT(*) AS sl
|
|
FROM current.khambenh
|
|
WHERE ngaykcb BETWEEN $1 AND $2 %s
|
|
AND COALESCE(xoa, 0) = 0
|
|
AND maicd IS NOT NULL AND maicd != ''
|
|
GROUP BY maicd, kqcdoan
|
|
ORDER BY sl DESC
|
|
LIMIT 10;
|
|
`, fnNone)
|
|
rows, err := conn.Query(sqlTop10, fromDate, toDate)
|
|
if err == nil {
|
|
defer rows.Close()
|
|
var list []map[string]interface{}
|
|
for rows.Next() {
|
|
var maicd, kqcdoan string
|
|
var sl int
|
|
_ = rows.Scan(&maicd, &kqcdoan, &sl)
|
|
list = append(list, map[string]interface{}{
|
|
"maicd": maicd, "kqcdoan": kqcdoan, "sl": sl,
|
|
})
|
|
}
|
|
mu.Lock()
|
|
out.Top10 = list
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// 6. Tiêm ngừa
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sqlTn := fmt.Sprintf(`
|
|
SELECT COUNT(kb.makb) AS sl
|
|
FROM current.khambenh kb
|
|
LEFT JOIN current.dmphong ph ON kb.maphong = ph.maphong
|
|
LEFT JOIN current.dmdonvi dv ON kb.madv = dv.madv
|
|
WHERE kb.ngaykcb BETWEEN $1 AND $2 %s
|
|
AND COALESCE(kb.xoa, 0) = 0
|
|
AND COALESCE(kb.dakham, 0) != 0
|
|
AND (
|
|
COALESCE(ph.loai_bc_giaoban, 0) = 2 OR
|
|
ph.maphong = 'TN' OR
|
|
dv.madv = 'PTN' OR
|
|
ph.tenphong ILIKE '%%Tiêm ngừa%%' OR
|
|
ph.tenphong ILIKE '%%Tiêm chủng%%'
|
|
);
|
|
`, fnKb)
|
|
var sl int
|
|
err := conn.QueryRow(sqlTn, fromDate, toDate).Scan(&sl)
|
|
if err == nil {
|
|
mu.Lock()
|
|
out.TiemNgua = sl
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
return out, nil
|
|
}
|
|
|
|
// GET /api/giao-ban-ngoai-tru
|
|
func HandleGiaoBanNgoaiTru(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
q := r.URL.Query()
|
|
database := q.Get("database")
|
|
from := q.Get("from")
|
|
to := q.Get("to")
|
|
compareFrom := q.Get("compareFrom")
|
|
compareTo := q.Get("compareTo")
|
|
|
|
if from == "" {
|
|
from = "2026-03-01 00:00:00"
|
|
}
|
|
if to == "" {
|
|
to = "2026-03-31 23:59:59"
|
|
}
|
|
|
|
curData, err := queryOutpatient(database, from, to)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
var compData *OutpatientData
|
|
if compareFrom != "" && compareTo != "" {
|
|
compData, _ = queryOutpatient(database, compareFrom, compareTo)
|
|
}
|
|
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": true,
|
|
"data": map[string]interface{}{
|
|
"current": curData,
|
|
"compare": compData,
|
|
"periodCurrent": map[string]string{
|
|
"from": from, "to": to,
|
|
},
|
|
"periodCompare": func() interface{} {
|
|
if compareFrom != "" && compareTo != "" {
|
|
return map[string]string{"from": compareFrom, "to": compareTo}
|
|
}
|
|
return nil
|
|
}(),
|
|
},
|
|
})
|
|
}
|
|
|
|
type InpatientData struct {
|
|
Khoa []map[string]interface{} `json:"khoa"`
|
|
}
|
|
|
|
func queryInpatient(database, fromDate, toDate string) (*InpatientData, error) {
|
|
conn, err := db.GetDB(database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sqlKhoa := `
|
|
SELECT
|
|
dv.madv,
|
|
dv.tendv,
|
|
SUM(CASE WHEN nt.ngayvv < $1 AND (nt.ravien = 0 OR nt.ngayrv >= $1) THEN 1 ELSE 0 END) AS benh_cu,
|
|
SUM(CASE WHEN nt.ngayvv BETWEEN $1 AND $2 THEN 1 ELSE 0 END) AS benh_moi,
|
|
SUM(CASE WHEN nt.ngayrv BETWEEN $1 AND $2 AND nt.ravien = 1 AND COALESCE(nt.makq, '01') NOT IN ('04','05','06') THEN 1 ELSE 0 END) AS on_xin_ve,
|
|
SUM(CASE WHEN nt.ngayrv BETWEEN $1 AND $2 AND (nt.makq IN ('04','06') OR (nt.maxt = '09' AND nt.makq = '04')) THEN 1 ELSE 0 END) AS nang_xin_ve,
|
|
SUM(CASE WHEN nt.ngayrv BETWEEN $1 AND $2 AND (nt.ravien = 2 OR nt.maxt = '08') THEN 1 ELSE 0 END) AS tron_vien,
|
|
SUM(CASE WHEN nt.ngayrv BETWEEN $1 AND $2 AND (nt.ravien = 4 OR nt.maxt = '03') THEN 1 ELSE 0 END) AS chuyen_vien,
|
|
SUM(CASE WHEN nt.ngayrv BETWEEN $1 AND $2 AND (nt.ravien = 3 OR nt.maxt = '06') THEN 1 ELSE 0 END) AS tu_vong,
|
|
SUM(CASE WHEN nt.ngayvv <= $2 AND (nt.ravien = 0 OR nt.ngayrv > $2) THEN 1 ELSE 0 END) AS benh_hien_con,
|
|
SUM(CASE WHEN nt.ngayvv <= $2 AND (nt.ravien = 0 OR nt.ngayrv >= $1) AND (COALESCE(nt.benhnang, 0) = 1 OR COALESCE(nt.mabatheodoi, '') != '' OR dv.madv IN ('17','29')) THEN 1 ELSE 0 END) AS benh_theo_doi,
|
|
SUM(CASE WHEN nt.ngayvv <= $2 AND (nt.ravien = 0 OR nt.ngayrv >= $1) AND (nt.chamsoc ILIKE '%cấp I%' OR nt.chamsoc ILIKE '%cấp 1%' OR nt.chamsoc IN ('1', 'CS I', 'CSI')) THEN 1 ELSE 0 END) AS chamsoc_cap1
|
|
FROM current.bnnoitru nt
|
|
JOIN current.dmdonvi dv ON nt.madv = dv.madv
|
|
WHERE nt.namvien = 1 AND COALESCE(nt.xoa, 0) = 0
|
|
AND nt.ngayvv >= ($1::date - INTERVAL '180 days')
|
|
GROUP BY dv.madv, dv.tendv
|
|
HAVING SUM(CASE WHEN nt.ngayvv <= $2 AND (nt.ravien = 0 OR nt.ngayrv >= $1) THEN 1 ELSE 0 END) > 0
|
|
ORDER BY benh_moi DESC;
|
|
`
|
|
|
|
rows, err := conn.Query(sqlKhoa, fromDate, toDate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
list := make([]map[string]interface{}, 0)
|
|
for rows.Next() {
|
|
var madv, tendv string
|
|
var cu, moi, on, nang, tron, cv, tv, hiencon, td, cs1 int
|
|
_ = rows.Scan(&madv, &tendv, &cu, &moi, &on, &nang, &tron, &cv, &tv, &hiencon, &td, &cs1)
|
|
list = append(list, map[string]interface{}{
|
|
"madv": madv, "tendv": tendv,
|
|
"benh_cu": cu, "benh_moi": moi, "on_xin_ve": on, "nang_xin_ve": nang,
|
|
"tron_vien": tron, "chuyen_vien": cv, "tu_vong": tv, "benh_hien_con": hiencon,
|
|
"benh_theo_doi": td, "chamsoc_cap1": cs1,
|
|
})
|
|
}
|
|
|
|
return &InpatientData{Khoa: list}, nil
|
|
}
|
|
|
|
// GET /api/giao-ban-noi-tru
|
|
func HandleGiaoBanNoiTru(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
q := r.URL.Query()
|
|
database := q.Get("database")
|
|
from := q.Get("from")
|
|
to := q.Get("to")
|
|
compareFrom := q.Get("compareFrom")
|
|
compareTo := q.Get("compareTo")
|
|
|
|
if from == "" {
|
|
from = "2026-03-01 00:00:00"
|
|
}
|
|
if to == "" {
|
|
to = "2026-03-31 23:59:59"
|
|
}
|
|
|
|
db.EnsureTable(database)
|
|
|
|
curData, err := queryInpatient(database, from, to)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
var compData *InpatientData
|
|
if compareFrom != "" && compareTo != "" {
|
|
compData, _ = queryInpatient(database, compareFrom, compareTo)
|
|
}
|
|
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": true,
|
|
"data": map[string]interface{}{
|
|
"current": curData,
|
|
"compare": compData,
|
|
"periodCurrent": map[string]string{
|
|
"from": from, "to": to,
|
|
},
|
|
"periodCompare": func() interface{} {
|
|
if compareFrom != "" && compareTo != "" {
|
|
return map[string]string{"from": compareFrom, "to": compareTo}
|
|
}
|
|
return nil
|
|
}(),
|
|
},
|
|
})
|
|
}
|
|
|
|
// GET /api/danh-sach-chuyen-vien
|
|
func HandleDanhSachChuyenVien(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
q := r.URL.Query()
|
|
database := q.Get("database")
|
|
from := q.Get("from")
|
|
to := q.Get("to")
|
|
madv := q.Get("madv")
|
|
|
|
if from == "" {
|
|
from = "2026-03-01 00:00:00"
|
|
}
|
|
if to == "" {
|
|
to = "2026-03-31 23:59:59"
|
|
}
|
|
|
|
conn, err := db.GetDB(database)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
db.EnsureTable(database)
|
|
|
|
sqlWhere := `
|
|
WHERE nt.namvien = 1 AND COALESCE(nt.xoa, 0) = 0
|
|
AND nt.ngayrv BETWEEN $1 AND $2
|
|
AND (nt.ravien = 4 OR nt.maxt = '03')
|
|
`
|
|
args := []interface{}{from, to}
|
|
if madv != "" && madv != "ALL" {
|
|
args = append(args, madv)
|
|
sqlWhere += fmt.Sprintf(" AND nt.madv = $%d ", len(args))
|
|
}
|
|
|
|
sqlStr := fmt.Sprintf(`
|
|
SELECT
|
|
nt.mabn,
|
|
nt.maba,
|
|
bn.holot || ' ' || bn.ten AS hoten,
|
|
COALESCE(bn.diachi, '') AS diachi,
|
|
to_char(nt.ngayvv, 'DD/MM/YYYY HH24:MI') AS ngayvv,
|
|
to_char(nt.ngayrv, 'DD/MM/YYYY HH24:MI') AS ngayrv,
|
|
COALESCE(dv.tendv, nt.madv) AS tendv,
|
|
COALESCE(nt.kqcdoan, '') AS chandoan_chinh,
|
|
COALESCE(nt.kqcdoanp, '') AS chandoan_phu,
|
|
COALESCE(cls_sub.ketqua_cls, 'Chưa có kết quả') AS ketqua_cls,
|
|
COALESCE(gb.ghichu, nt.ghichu, '') AS ghichu
|
|
FROM current.bnnoitru nt
|
|
JOIN current.dmbenhnhan bn ON nt.mabn = bn.mabn
|
|
LEFT JOIN current.dmdonvi dv ON nt.madv = dv.madv
|
|
LEFT JOIN current.giaoban_ghichu gb ON nt.maba = gb.maba AND gb.loai_danhsach = 'CHUYEN_VIEN'
|
|
LEFT JOIN LATERAL (
|
|
SELECT string_agg(DISTINCT cls.tencls || COALESCE(': ' || NULLIF(ha.ketluan,''), ''), '; ') AS ketqua_cls
|
|
FROM current.chidinhcls ls
|
|
JOIN current.dmcls cls ON ls.macls = cls.macls
|
|
LEFT JOIN current.pskhamha ha ON ls.mabn = ha.mabn AND ls.macls = ha.macls AND ls.makb = ha.makb
|
|
WHERE ls.mabn = nt.mabn AND ls.makb = nt.makb AND COALESCE(ls.xoa, 0) = 0
|
|
) cls_sub ON true
|
|
%s
|
|
ORDER BY nt.ngayrv DESC
|
|
LIMIT 100;
|
|
`, sqlWhere)
|
|
|
|
rows, err := conn.Query(sqlStr, args...)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
list := make([]map[string]interface{}, 0)
|
|
for rows.Next() {
|
|
var mabn, maba, hoten, diachi, ngayvv, ngayrv, tendv, cdChinh, cdPhu, cls, ghichu string
|
|
_ = rows.Scan(&mabn, &maba, &hoten, &diachi, &ngayvv, &ngayrv, &tendv, &cdChinh, &cdPhu, &cls, &ghichu)
|
|
list = append(list, map[string]interface{}{
|
|
"mabn": mabn, "maba": maba, "hoten": hoten, "diachi": diachi,
|
|
"ngayvv": ngayvv, "ngayrv": ngayrv, "tendv": tendv,
|
|
"chandoan_chinh": cdChinh, "chandoan_phu": cdPhu,
|
|
"ketqua_cls": db.CleanRTF(cls), "ghichu": ghichu,
|
|
})
|
|
}
|
|
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": true,
|
|
"data": list,
|
|
})
|
|
}
|
|
|
|
// GET /api/danh-sach-theo-doi
|
|
func HandleDanhSachTheoDoi(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
q := r.URL.Query()
|
|
database := q.Get("database")
|
|
from := q.Get("from")
|
|
to := q.Get("to")
|
|
madv := q.Get("madv")
|
|
|
|
if from == "" {
|
|
from = "2026-03-01 00:00:00"
|
|
}
|
|
if to == "" {
|
|
to = "2026-03-31 23:59:59"
|
|
}
|
|
|
|
conn, err := db.GetDB(database)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
db.EnsureTable(database)
|
|
|
|
sqlWhere := `
|
|
WHERE nt.namvien = 1 AND COALESCE(nt.xoa, 0) = 0
|
|
AND (nt.ngayvv <= $2 AND (nt.ravien = 0 OR nt.ngayrv >= $1))
|
|
AND (
|
|
COALESCE(nt.benhnang, 0) = 1 OR
|
|
COALESCE(nt.mabatheodoi, '') != '' OR
|
|
nt.chamsoc ILIKE '%cấp I%' OR
|
|
nt.chamsoc ILIKE '%cấp 1%' OR
|
|
nt.chamsoc IN ('1', 'CS I', 'CSI') OR
|
|
nt.madv IN ('17', '29')
|
|
)
|
|
`
|
|
args := []interface{}{from, to}
|
|
if madv != "" && madv != "ALL" {
|
|
args = append(args, madv)
|
|
sqlWhere += fmt.Sprintf(" AND nt.madv = $%d ", len(args))
|
|
}
|
|
|
|
sqlStr := fmt.Sprintf(`
|
|
SELECT
|
|
nt.mabn,
|
|
nt.maba,
|
|
bn.holot || ' ' || bn.ten AS hoten,
|
|
COALESCE(bn.diachi, '') AS diachi,
|
|
to_char(nt.ngayvv, 'DD/MM/YYYY HH24:MI') AS ngayvv,
|
|
to_char(nt.ngayrv, 'DD/MM/YYYY HH24:MI') AS ngayrv,
|
|
COALESCE(dv.tendv, nt.madv) AS tendv,
|
|
COALESCE(nt.kqcdoan, '') AS chandoan_chinh,
|
|
COALESCE(nt.kqcdoanp, '') AS chandoan_phu,
|
|
COALESCE(nt.chamsoc, 'Theo dõi đặc biệt') AS chamsoc,
|
|
COALESCE(cls_sub.ketqua_cls, 'Chưa có kết quả') AS ketqua_cls,
|
|
COALESCE(gb.ghichu, nt.ghichu, '') AS ghichu
|
|
FROM current.bnnoitru nt
|
|
JOIN current.dmbenhnhan bn ON nt.mabn = bn.mabn
|
|
LEFT JOIN current.dmdonvi dv ON nt.madv = dv.madv
|
|
LEFT JOIN current.giaoban_ghichu gb ON nt.maba = gb.maba AND gb.loai_danhsach = 'THEO_DOI'
|
|
LEFT JOIN LATERAL (
|
|
SELECT string_agg(DISTINCT cls.tencls || COALESCE(': ' || NULLIF(ha.ketluan,''), ''), '; ') AS ketqua_cls
|
|
FROM current.chidinhcls ls
|
|
JOIN current.dmcls cls ON ls.macls = cls.macls
|
|
LEFT JOIN current.pskhamha ha ON ls.mabn = ha.mabn AND ls.macls = ha.macls AND ls.makb = ha.makb
|
|
WHERE ls.mabn = nt.mabn AND ls.makb = nt.makb AND COALESCE(ls.xoa, 0) = 0
|
|
) cls_sub ON true
|
|
%s
|
|
ORDER BY nt.ngayvv DESC
|
|
LIMIT 100;
|
|
`, sqlWhere)
|
|
|
|
rows, err := conn.Query(sqlStr, args...)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
list := make([]map[string]interface{}, 0)
|
|
for rows.Next() {
|
|
var mabn, maba, hoten, diachi, ngayvv, ngayrv, tendv, cdChinh, cdPhu, chamsoc, cls, ghichu string
|
|
_ = rows.Scan(&mabn, &maba, &hoten, &diachi, &ngayvv, &ngayrv, &tendv, &cdChinh, &cdPhu, &chamsoc, &cls, &ghichu)
|
|
list = append(list, map[string]interface{}{
|
|
"mabn": mabn, "maba": maba, "hoten": hoten, "diachi": diachi,
|
|
"ngayvv": ngayvv, "ngayrv": ngayrv, "tendv": tendv,
|
|
"chandoan_chinh": cdChinh, "chandoan_phu": cdPhu,
|
|
"chamsoc": chamsoc, "ketqua_cls": db.CleanRTF(cls), "ghichu": ghichu,
|
|
})
|
|
}
|
|
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": true,
|
|
"data": list,
|
|
})
|
|
}
|
|
|
|
// POST /api/cap-nhat-ghi-chu
|
|
func HandleCapNhatGhiChu(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
var body struct {
|
|
Maba string `json:"maba"`
|
|
Mabn string `json:"mabn"`
|
|
Loai string `json:"loai"`
|
|
Ghichu string `json:"ghichu"`
|
|
Database string `json:"database"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Maba == "" || body.Loai == "" {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": "Thiếu thông tin maba hoặc loai danh sách"})
|
|
return
|
|
}
|
|
|
|
conn, err := db.GetDB(body.Database)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
db.EnsureTable(body.Database)
|
|
|
|
sqlUpsert := `
|
|
INSERT INTO current.giaoban_ghichu (maba, mabn, loai_danhsach, ghichu, ngay_capnhat)
|
|
VALUES ($1, $2, $3, $4, NOW())
|
|
ON CONFLICT (maba, loai_danhsach)
|
|
DO UPDATE SET ghichu = EXCLUDED.ghichu, ngay_capnhat = NOW();
|
|
`
|
|
_, err = conn.Exec(sqlUpsert, body.Maba, body.Mabn, body.Loai, body.Ghichu)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Try sync to bnnoitru.ghichu
|
|
_, _ = conn.Exec("UPDATE current.bnnoitru SET ghichu = $1 WHERE maba = $2;", body.Ghichu, body.Maba)
|
|
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": true,
|
|
"message": "Đã lưu ghi chú thành công!",
|
|
})
|
|
}
|