Files
cpone_dashboard/cpone-dashboard/menu/arrival/query.go
2026-04-30 14:27:01 +07:00

308 lines
7.6 KiB
Go

package arrival
import (
"cpone-dashboard/db"
"fmt"
"sort"
"strings"
)
type StationBadge struct {
Name string
Tone string // "success" | "warning" | "danger" | "neutral"
}
type StationStat struct {
Name string
Count int
}
type ArrivalRow struct {
PreregisterID int
NIP string
Name string
Department string
InTime string
Status string
StatusTone string
Stations []StationBadge
}
type DepartmentStat struct {
Name string
CheckedIn int
Pending int
Total int
}
type ArrivalSummary struct {
CheckedIn int
Pending int
Total int
}
func GetArrivalDates(mcuID int) ([]string, error) {
rows, err := db.DB.Query(`
SELECT DATE_FORMAT(Mcu_PatientScheduleDate, '%Y-%m-%d') AS schedule_date
FROM mcu_patient_schedule
WHERE Mcu_PatientSchedulePreregisterID IN (
SELECT Mcu_PatientPreregisterID
FROM mcu_patient
WHERE Mcu_PatientMcuID = ?
AND Mcu_PatientIsActive = 'Y'
)
AND Mcu_PatientScheduleIsActive = 'Y'
GROUP BY Mcu_PatientScheduleDate
ORDER BY Mcu_PatientScheduleDate DESC
`, mcuID)
if err != nil {
return nil, err
}
defer rows.Close()
var dates []string
for rows.Next() {
var d string
if rows.Scan(&d) == nil && d != "" {
dates = append(dates, d)
}
}
return dates, nil
}
func GetArrivalRows(mcuID int, date string) ([]ArrivalRow, error) {
rows, err := db.DB.Query(`
SELECT
mp.Mcu_PatientPreregisterID,
COALESCE(NULLIF(TRIM(mp.Mcu_PatientNIP), ''), '') AS nip,
COALESCE(NULLIF(TRIM(mp.Mcu_PatientName), ''), '') AS patient_name,
COALESCE(
NULLIF(TRIM(mp.Mcu_PatientDepartment), ''),
NULLIF(TRIM(mp.Mcu_PatientDivision), ''),
NULLIF(TRIM(mp.Mcu_PatientPosisi), ''),
'-'
) AS department_name,
COALESCE(DATE_FORMAT(mc.Mcu_CheckinoutInTime, '%H:%i'), '') AS in_time,
CASE
WHEN mc.Mcu_CheckinoutOutTime IS NOT NULL THEN 'Performed'
WHEN mc.Mcu_CheckinoutInTime IS NOT NULL THEN 'In Progress'
ELSE 'Not Check-in Yet'
END AS status_text,
CASE
WHEN mc.Mcu_CheckinoutOutTime IS NOT NULL THEN 'success'
WHEN mc.Mcu_CheckinoutInTime IS NOT NULL THEN 'warning'
ELSE 'neutral'
END AS status_tone
FROM mcu_patient_schedule s
JOIN mcu_patient mp
ON mp.Mcu_PatientPreregisterID = s.Mcu_PatientSchedulePreregisterID
AND mp.Mcu_PatientMcuID = ?
AND mp.Mcu_PatientIsActive = 'Y'
LEFT JOIN mcu_checkinout mc
ON mc.Mcu_CheckinoutPreregisterID = mp.Mcu_PatientPreregisterID
AND mc.Mcu_CheckinoutMcuID = ?
AND mc.Mcu_CheckinoutDate = s.Mcu_PatientScheduleDate
AND mc.Mcu_CheckinoutIsActive = 'Y'
WHERE s.Mcu_PatientScheduleIsActive = 'Y'
AND s.Mcu_PatientScheduleDate = ?
ORDER BY
CASE WHEN mc.Mcu_CheckinoutInTime IS NULL THEN 1 ELSE 0 END,
mc.Mcu_CheckinoutInTime DESC,
mp.Mcu_PatientName ASC
`, mcuID, mcuID, date)
if err != nil {
return nil, err
}
defer rows.Close()
var result []ArrivalRow
for rows.Next() {
var r ArrivalRow
if err := rows.Scan(&r.PreregisterID, &r.NIP, &r.Name, &r.Department, &r.InTime, &r.Status, &r.StatusTone); err != nil {
continue
}
if strings.TrimSpace(r.NIP) == "" {
r.NIP = "-"
}
if strings.TrimSpace(r.Name) == "" {
r.Name = "-"
}
if strings.TrimSpace(r.Department) == "" {
r.Department = "-"
}
result = append(result, r)
}
return result, nil
}
func GetStationProgress(mcuID int, date string) (map[int][]StationBadge, error) {
rows, err := db.DB.Query(`
SELECT
sp.Mcu_StationProgressPreregisterID,
sp.Mcu_StationProgressStationName,
CASE
WHEN sp.Mcu_StationProgressDoneAt IS NOT NULL THEN 'success'
WHEN sp.Mcu_StationProgressProcessAt IS NOT NULL
OR sp.Mcu_StationProgressReceiveAt IS NOT NULL
OR sp.Mcu_StationProgressSamplingAt IS NOT NULL THEN 'warning'
ELSE 'neutral'
END AS tone
FROM mcu_station_progress sp
WHERE sp.Mcu_StationProgressMcuID = ?
AND sp.Mcu_StationProgressPreregisterID IN (
SELECT mp.Mcu_PatientPreregisterID
FROM mcu_patient_schedule s
JOIN mcu_patient mp ON mp.Mcu_PatientPreregisterID = s.Mcu_PatientSchedulePreregisterID
WHERE mp.Mcu_PatientMcuID = ?
AND mp.Mcu_PatientIsActive = 'Y'
AND s.Mcu_PatientScheduleDate = ?
AND s.Mcu_PatientScheduleIsActive = 'Y'
)
ORDER BY sp.Mcu_StationProgressPreregisterID, sp.Mcu_StationProgressStationName
`, mcuID, mcuID, date)
if err != nil {
return nil, err
}
defer rows.Close()
result := map[int][]StationBadge{}
for rows.Next() {
var preregID int
var name, tone string
if err := rows.Scan(&preregID, &name, &tone); err != nil {
continue
}
result[preregID] = append(result[preregID], StationBadge{Name: name, Tone: tone})
}
return result, nil
}
func BuildArrivalStats(rows []ArrivalRow) (ArrivalSummary, []DepartmentStat) {
summary := ArrivalSummary{Total: len(rows)}
deptMap := map[string]*DepartmentStat{}
for _, row := range rows {
if row.InTime != "" {
summary.CheckedIn++
}
dept := row.Department
if dept == "" {
dept = "-"
}
stat, ok := deptMap[dept]
if !ok {
stat = &DepartmentStat{Name: dept}
deptMap[dept] = stat
}
stat.Total++
if row.InTime != "" {
stat.CheckedIn++
}
}
summary.Pending = summary.Total - summary.CheckedIn
if summary.Pending < 0 {
summary.Pending = 0
}
stats := make([]DepartmentStat, 0, len(deptMap))
for _, stat := range deptMap {
stat.Pending = stat.Total - stat.CheckedIn
if stat.Pending < 0 {
stat.Pending = 0
}
stats = append(stats, *stat)
}
sort.Slice(stats, func(i, j int) bool {
if stats[i].CheckedIn != stats[j].CheckedIn {
return stats[i].CheckedIn > stats[j].CheckedIn
}
if stats[i].Total != stats[j].Total {
return stats[i].Total > stats[j].Total
}
return stats[i].Name < stats[j].Name
})
return summary, stats
}
func BuildStationChart(stationMap map[int][]StationBadge) []StationStat {
countMap := map[string]int{}
for _, badges := range stationMap {
for _, b := range badges {
countMap[b.Name]++
}
}
stats := make([]StationStat, 0, len(countMap))
for name, count := range countMap {
stats = append(stats, StationStat{Name: name, Count: count})
}
sort.Slice(stats, func(i, j int) bool {
return stats[i].Count > stats[j].Count
})
return stats
}
func FilterArrivalRows(rows []ArrivalRow, search, dept string) []ArrivalRow {
search = strings.ToLower(strings.TrimSpace(search))
dept = strings.TrimSpace(dept)
if search == "" && dept == "" {
return rows
}
out := make([]ArrivalRow, 0, len(rows))
for _, row := range rows {
if dept != "" && dept != "All Departments" && row.Department != dept {
continue
}
if search != "" {
hay := strings.ToLower(row.Name + " " + row.NIP + " " + row.Department)
if !strings.Contains(hay, search) {
continue
}
}
out = append(out, row)
}
return out
}
func UniqueDepartments(rows []ArrivalRow) []string {
seen := map[string]struct{}{}
var out []string
for _, row := range rows {
name := strings.TrimSpace(row.Department)
if name == "" {
name = "-"
}
if _, ok := seen[name]; ok {
continue
}
seen[name] = struct{}{}
out = append(out, name)
}
sort.Strings(out)
return out
}
func activeDateOrLatest(dates []string, selected string, fallback string) string {
selected = strings.TrimSpace(selected)
if selected != "" {
for _, d := range dates {
if d == selected {
return selected
}
}
}
if len(dates) > 0 {
return dates[0]
}
return fallback
}
func mustDayLabel(date string) string {
if len(date) >= 10 {
return fmt.Sprintf("%s/%s/%s", date[8:10], date[5:7], date[0:4])
}
return date
}