sp_generate_dashboard_data menebak Mcu_CheckinoutOutTime dari MAX() timestamp event lab (sample receive/sampling done), bukan dari checkout fisik pasien, sehingga pasien yang belum checkout bisa tampil "Checked Out" walau stationnya belum selesai. Ganti sumbernya ke cpone.preregister_checkin_checkout (log checkin/checkout asli) dan hentikan pemaksaan Mcu_CheckinoutID = order_id yang bentrok untuk project multi-hari. KPI "Checked Out" di GetKPI juga diselaraskan dengan detail pasien: sekarang hitung dari GetAllPatients dan mensyaratkan semua station wajib sudah selesai, bukan cuma checkout fisik saja, supaya angka KPI dan modal detail selalu sinkron.
539 lines
16 KiB
Go
539 lines
16 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"cpone-dashboard/db"
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
const checkinOutTimestampExpr = "TIMESTAMP(Mcu_CheckinoutDate, Mcu_CheckinoutOutTime)"
|
|
|
|
type ProjectInfo struct {
|
|
McuID int
|
|
CorporateName string
|
|
Label string
|
|
Number string
|
|
StartDate string
|
|
EndDate string
|
|
TotalStaff int
|
|
}
|
|
|
|
type KPIData struct {
|
|
InvitedStaff int // dari mcu_participant_daily, date-filtered — untuk widget kanan atas
|
|
TotalStaff int // dari mcu_checkinout, seluruh project — untuk KPI card
|
|
CheckedIn int // dari mcu_checkinout, date-filtered
|
|
CheckedOut int // dari mcu_checkinout, date-filtered
|
|
}
|
|
|
|
type StationRow struct {
|
|
Station string
|
|
Processed int
|
|
Pending int
|
|
Total int
|
|
Pct float64
|
|
}
|
|
|
|
type ArrivalRow struct {
|
|
Name string
|
|
InTime string
|
|
Date string
|
|
Station string
|
|
}
|
|
|
|
type TATData struct {
|
|
AvgMinutes int
|
|
Fastest int
|
|
Median int
|
|
CheckedOut int
|
|
}
|
|
|
|
type ChartPoint struct {
|
|
Label string
|
|
Value float64
|
|
}
|
|
|
|
type TrendPoint struct {
|
|
Label string
|
|
CheckedIn int
|
|
CheckedOut int
|
|
}
|
|
|
|
// GetProject returns project by mcuID. If mcuID == 0, returns the active project.
|
|
func GetProject(mcuID int) (ProjectInfo, error) {
|
|
var p ProjectInfo
|
|
var err error
|
|
const cols = `SELECT Mcu_ProjectMcuID, Mcu_ProjectCorporateName, Mcu_ProjectLabel,
|
|
Mcu_ProjectNumber,
|
|
DATE_FORMAT(Mcu_ProjectStartDate, '%Y-%m-%d'),
|
|
DATE_FORMAT(Mcu_ProjectEndDate, '%Y-%m-%d'),
|
|
Mcu_ProjectTotalParticipant
|
|
FROM mcu_project`
|
|
if mcuID > 0 {
|
|
err = db.DB.QueryRow(cols+` WHERE Mcu_ProjectMcuID = ?`, mcuID).
|
|
Scan(&p.McuID, &p.CorporateName, &p.Label, &p.Number, &p.StartDate, &p.EndDate, &p.TotalStaff)
|
|
} else {
|
|
err = db.DB.QueryRow(cols+` WHERE Mcu_ProjectIsActive = 'Y' ORDER BY Mcu_ProjectStartDate DESC LIMIT 1`).
|
|
Scan(&p.McuID, &p.CorporateName, &p.Label, &p.Number, &p.StartDate, &p.EndDate, &p.TotalStaff)
|
|
}
|
|
if err == sql.ErrNoRows {
|
|
return p, nil
|
|
}
|
|
return p, err
|
|
}
|
|
|
|
func GetKPI(mcuID int, dateFrom, dateTo string) (KPIData, error) {
|
|
var d KPIData
|
|
|
|
// Invited staff: dari mcu_participant_daily, date-filtered — widget kanan atas
|
|
db.DB.QueryRow(`
|
|
SELECT COALESCE(SUM(Mcu_ParticipantDailyTotal), 0)
|
|
FROM mcu_participant_daily
|
|
WHERE Mcu_ParticipantDailyMcuID = ?
|
|
AND Mcu_ParticipantDailyDate BETWEEN ? AND ?
|
|
AND Mcu_ParticipantDailyIsActive = 'Y'
|
|
`, mcuID, dateFrom, dateTo).Scan(&d.InvitedStaff)
|
|
|
|
// Total staff / checked-in / checked-out dihitung dari sumber yang sama dengan
|
|
// detail pasien (GetAllPatients), supaya KPI card dan modal detail selalu sinkron.
|
|
// "Checked Out" hanya terhitung kalau pasien SUDAH checkout DAN semua station
|
|
// wajibnya sudah selesai — checkout fisik saja tidak cukup.
|
|
patients, err := GetAllPatients(mcuID, dateFrom, dateTo)
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
for _, p := range patients {
|
|
if p.InTime == "" {
|
|
continue
|
|
}
|
|
d.TotalStaff++
|
|
if p.HasOut && p.ProgressStatus == "done" {
|
|
d.CheckedOut++
|
|
} else {
|
|
d.CheckedIn++
|
|
}
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
func GetCheckinDates(mcuID int) ([]string, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT DATE_FORMAT(Mcu_CheckinoutDate, '%Y-%m-%d') AS checkin_date
|
|
FROM mcu_checkinout
|
|
WHERE Mcu_CheckinoutMcuID = ?
|
|
AND Mcu_CheckinoutIsActive = 'Y'
|
|
GROUP BY Mcu_CheckinoutDate
|
|
ORDER BY Mcu_CheckinoutDate 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 GetTAT(mcuID int, dateFrom, dateTo string) (TATData, error) {
|
|
var d TATData
|
|
rows, err := db.DB.Query(`
|
|
SELECT TIMESTAMPDIFF(MINUTE,
|
|
TIMESTAMP(Mcu_CheckinoutDate, Mcu_CheckinoutInTime),
|
|
`+checkinOutTimestampExpr+`
|
|
) AS tat
|
|
FROM mcu_checkinout
|
|
WHERE Mcu_CheckinoutMcuID = ?
|
|
AND Mcu_CheckinoutDate BETWEEN ? AND ?
|
|
AND Mcu_CheckinoutOutTime IS NOT NULL
|
|
AND Mcu_CheckinoutInTime IS NOT NULL
|
|
AND Mcu_CheckinoutIsActive = 'Y'
|
|
ORDER BY tat
|
|
`, mcuID, dateFrom, dateTo)
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var vals []int
|
|
for rows.Next() {
|
|
var v int
|
|
if rows.Scan(&v) == nil && v > 0 {
|
|
vals = append(vals, v)
|
|
}
|
|
}
|
|
if len(vals) == 0 {
|
|
return d, nil
|
|
}
|
|
sum := 0
|
|
for _, v := range vals {
|
|
sum += v
|
|
}
|
|
d.CheckedOut = len(vals)
|
|
d.AvgMinutes = sum / len(vals)
|
|
d.Fastest = vals[0]
|
|
d.Median = vals[len(vals)/2]
|
|
return d, nil
|
|
}
|
|
|
|
func GetStations(mcuID int, dateFrom, dateTo string) ([]StationRow, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT rs.station_name,
|
|
COUNT(DISTINCT CASE
|
|
WHEN spd.first_done_date IS NULL OR spd.first_done_date >= mc.Mcu_CheckinoutDate
|
|
THEN mc.Mcu_CheckinoutPreregisterID
|
|
END) AS total_required,
|
|
COUNT(DISTINCT CASE
|
|
WHEN spd.first_done_date = mc.Mcu_CheckinoutDate
|
|
THEN mc.Mcu_CheckinoutPreregisterID
|
|
ELSE NULL
|
|
END) AS processed
|
|
FROM mcu_checkinout mc
|
|
JOIN mcu_patient_required_station rs
|
|
ON rs.preregister_id = mc.Mcu_CheckinoutPreregisterID
|
|
AND rs.mcu_id = mc.Mcu_CheckinoutMcuID
|
|
LEFT JOIN (
|
|
SELECT
|
|
sp.Mcu_StationProgressPreregisterID AS preregister_id,
|
|
sp.Mcu_StationProgressStationID AS station_id,
|
|
MIN(CASE
|
|
WHEN sp.Mcu_StationProgressSource = 'lab'
|
|
AND sp.Mcu_StationProgressReceiveAt IS NOT NULL
|
|
THEN DATE(sp.Mcu_StationProgressReceiveAt)
|
|
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
|
AND sp.Mcu_StationProgressDoneAt IS NOT NULL
|
|
THEN DATE(sp.Mcu_StationProgressDoneAt)
|
|
ELSE NULL
|
|
END) AS first_done_date
|
|
FROM mcu_station_progress sp
|
|
WHERE sp.Mcu_StationProgressMcuID = ?
|
|
GROUP BY sp.Mcu_StationProgressPreregisterID, sp.Mcu_StationProgressStationID
|
|
) spd
|
|
ON spd.preregister_id = mc.Mcu_CheckinoutPreregisterID
|
|
AND spd.station_id = rs.sample_station_id
|
|
WHERE mc.Mcu_CheckinoutMcuID = ?
|
|
AND mc.Mcu_CheckinoutDate BETWEEN ? AND ?
|
|
AND mc.Mcu_CheckinoutIsActive = 'Y'
|
|
GROUP BY rs.station_name
|
|
HAVING total_required > 0
|
|
ORDER BY processed DESC, rs.station_name ASC
|
|
`, mcuID, mcuID, dateFrom, dateTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []StationRow
|
|
for rows.Next() {
|
|
var r StationRow
|
|
if err := rows.Scan(&r.Station, &r.Total, &r.Processed); err != nil {
|
|
continue
|
|
}
|
|
r.Pending = r.Total - r.Processed
|
|
if r.Pending < 0 {
|
|
r.Pending = 0
|
|
}
|
|
if r.Total > 0 {
|
|
r.Pct = float64(r.Processed) / float64(r.Total) * 100
|
|
}
|
|
result = append(result, r)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func GetArrivals(mcuID int, dateFrom, dateTo string, limit int) ([]ArrivalRow, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT mp.Mcu_PatientName,
|
|
DATE_FORMAT(mc.Mcu_CheckinoutInTime, '%H:%i:%s') AS in_time,
|
|
DATE_FORMAT(mc.Mcu_CheckinoutDate, '%Y-%m-%d') AS checkin_date,
|
|
COALESCE(
|
|
(SELECT Mcu_StationProgressStationName
|
|
FROM mcu_station_progress
|
|
WHERE Mcu_StationProgressPreregisterID = mc.Mcu_CheckinoutPreregisterID
|
|
AND Mcu_StationProgressCheckinDate = mc.Mcu_CheckinoutDate
|
|
AND Mcu_StationProgressDoneAt IS NOT NULL
|
|
ORDER BY Mcu_StationProgressDoneAt DESC LIMIT 1),
|
|
'Check-in'
|
|
) AS last_station
|
|
FROM mcu_checkinout mc
|
|
JOIN mcu_patient mp ON mp.Mcu_PatientPreregisterID = mc.Mcu_CheckinoutPreregisterID
|
|
WHERE mc.Mcu_CheckinoutMcuID = ?
|
|
AND mc.Mcu_CheckinoutDate BETWEEN ? AND ?
|
|
AND mc.Mcu_CheckinoutIsActive = 'Y'
|
|
ORDER BY mc.Mcu_CheckinoutDate DESC, mc.Mcu_CheckinoutInTime DESC
|
|
LIMIT ?
|
|
`, mcuID, dateFrom, dateTo, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []ArrivalRow
|
|
for rows.Next() {
|
|
var r ArrivalRow
|
|
if rows.Scan(&r.Name, &r.InTime, &r.Date, &r.Station) == nil {
|
|
result = append(result, r)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// GetPeriodTAT — tampilkan agregasi per jam untuk daily maupun range
|
|
func GetPeriodTAT(mcuID int, dateFrom, dateTo string, isRange bool) ([]ChartPoint, error) {
|
|
_ = isRange
|
|
groupExpr := "HOUR(Mcu_CheckinoutInTime)"
|
|
labelExpr := "CONCAT(LPAD(HOUR(Mcu_CheckinoutInTime), 2, '0'), ':00')"
|
|
|
|
q := fmt.Sprintf(`
|
|
SELECT %s AS label,
|
|
AVG(TIMESTAMPDIFF(MINUTE,
|
|
TIMESTAMP(Mcu_CheckinoutDate, Mcu_CheckinoutInTime),
|
|
%s
|
|
)) AS avg_tat
|
|
FROM mcu_checkinout
|
|
WHERE Mcu_CheckinoutMcuID = ?
|
|
AND Mcu_CheckinoutDate BETWEEN ? AND ?
|
|
AND Mcu_CheckinoutOutTime IS NOT NULL
|
|
AND Mcu_CheckinoutInTime IS NOT NULL
|
|
AND Mcu_CheckinoutIsActive = 'Y'
|
|
GROUP BY %s
|
|
ORDER BY %s
|
|
`, labelExpr, checkinOutTimestampExpr, groupExpr, groupExpr)
|
|
|
|
rows, err := db.DB.Query(q, mcuID, dateFrom, dateTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []ChartPoint
|
|
for rows.Next() {
|
|
var p ChartPoint
|
|
if rows.Scan(&p.Label, &p.Value) == nil {
|
|
result = append(result, p)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// GetPeriodTrend — tampilkan hitungan per jam untuk daily maupun range
|
|
func GetPeriodTrend(mcuID int, dateFrom, dateTo string, isRange bool) ([]TrendPoint, error) {
|
|
_ = isRange
|
|
q := `
|
|
SELECT hour_no,
|
|
CONCAT(LPAD(hour_no, 2, '0'), ':00') AS label,
|
|
SUM(checked_in) AS checked_in,
|
|
SUM(checked_out) AS checked_out
|
|
FROM (
|
|
SELECT HOUR(Mcu_CheckinoutInTime) AS hour_no,
|
|
COUNT(*) AS checked_in,
|
|
0 AS checked_out
|
|
FROM mcu_checkinout
|
|
WHERE Mcu_CheckinoutMcuID = ?
|
|
AND Mcu_CheckinoutDate BETWEEN ? AND ?
|
|
AND Mcu_CheckinoutInTime IS NOT NULL
|
|
AND Mcu_CheckinoutIsActive = 'Y'
|
|
GROUP BY HOUR(Mcu_CheckinoutInTime)
|
|
|
|
UNION ALL
|
|
|
|
SELECT HOUR(Mcu_CheckinoutOutTime) AS hour_no,
|
|
0 AS checked_in,
|
|
COUNT(*) AS checked_out
|
|
FROM mcu_checkinout
|
|
WHERE Mcu_CheckinoutMcuID = ?
|
|
AND Mcu_CheckinoutDate BETWEEN ? AND ?
|
|
AND Mcu_CheckinoutOutTime IS NOT NULL
|
|
AND Mcu_CheckinoutIsActive = 'Y'
|
|
GROUP BY HOUR(Mcu_CheckinoutOutTime)
|
|
) t
|
|
GROUP BY hour_no
|
|
ORDER BY hour_no
|
|
`
|
|
|
|
rows, err := db.DB.Query(q, mcuID, dateFrom, dateTo, mcuID, dateFrom, dateTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
// Build cumulative
|
|
var points []TrendPoint
|
|
cumCI, cumCO := 0, 0
|
|
for rows.Next() {
|
|
var hourNo int
|
|
var label string
|
|
var ci, co int
|
|
if rows.Scan(&hourNo, &label, &ci, &co) == nil {
|
|
cumCI += ci
|
|
cumCO += co
|
|
points = append(points, TrendPoint{
|
|
Label: label,
|
|
CheckedIn: cumCI,
|
|
CheckedOut: cumCO,
|
|
})
|
|
}
|
|
}
|
|
return points, nil
|
|
}
|
|
|
|
type PatientStationStatus struct {
|
|
Station string
|
|
Done bool
|
|
Progress bool
|
|
ProcessAt string
|
|
DoneAt string
|
|
}
|
|
|
|
type PatientDetail struct {
|
|
ID int
|
|
Name string
|
|
Date string
|
|
InTime string
|
|
OutTime string
|
|
HasOut bool
|
|
Stations []PatientStationStatus
|
|
DoneCount int
|
|
ProgressCount int
|
|
ProgressStatus string
|
|
}
|
|
|
|
type patientKey struct {
|
|
ID int
|
|
Date string
|
|
}
|
|
|
|
func GetAllPatients(mcuID int, dateFrom, dateTo string) ([]PatientDetail, error) {
|
|
rows, err := db.DB.Query(`
|
|
SELECT
|
|
s.Mcu_PatientSchedulePreregisterID,
|
|
mp.Mcu_PatientName,
|
|
DATE_FORMAT(s.Mcu_PatientScheduleDate, '%Y-%m-%d'),
|
|
COALESCE(DATE_FORMAT(mc.Mcu_CheckinoutInTime, '%H:%i'), ''),
|
|
COALESCE(DATE_FORMAT(mc.Mcu_CheckinoutOutTime, '%H:%i'), ''),
|
|
IF(mc.Mcu_CheckinoutOutTime IS NOT NULL, 1, 0),
|
|
COALESCE(rs.station_name, ''),
|
|
IF(
|
|
(sp.Mcu_StationProgressSource = 'lab' AND sp.Mcu_StationProgressReceiveAt IS NOT NULL AND DATE(sp.Mcu_StationProgressReceiveAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressSource = 'nonlab' AND sp.Mcu_StationProgressProcessAt IS NOT NULL AND DATE(sp.Mcu_StationProgressProcessAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressSource = 'nonlab' AND sp.Mcu_StationProgressDoneAt IS NOT NULL AND DATE(sp.Mcu_StationProgressDoneAt) <= s.Mcu_PatientScheduleDate),
|
|
1, 0),
|
|
IF(
|
|
(sp.Mcu_StationProgressProcessAt IS NOT NULL AND DATE(sp.Mcu_StationProgressProcessAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressReceiveAt IS NOT NULL AND DATE(sp.Mcu_StationProgressReceiveAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressSamplingAt IS NOT NULL AND DATE(sp.Mcu_StationProgressSamplingAt) <= s.Mcu_PatientScheduleDate),
|
|
1, 0),
|
|
COALESCE(DATE_FORMAT(
|
|
CASE
|
|
WHEN sp.Mcu_StationProgressSource = 'lab'
|
|
THEN COALESCE(sp.Mcu_StationProgressSamplingAt, sp.Mcu_StationProgressProcessAt, sp.Mcu_StationProgressReceiveAt)
|
|
WHEN sp.Mcu_StationProgressSource = 'nonlab'
|
|
THEN COALESCE(sp.Mcu_StationProgressProcessAt, sp.Mcu_StationProgressDoneAt)
|
|
ELSE NULL
|
|
END,
|
|
'%d/%m/%Y %H:%i'), ''),
|
|
COALESCE(DATE_FORMAT(
|
|
CASE
|
|
WHEN sp.Mcu_StationProgressSource = 'lab' THEN sp.Mcu_StationProgressReceiveAt
|
|
WHEN sp.Mcu_StationProgressSource = 'nonlab' THEN COALESCE(sp.Mcu_StationProgressDoneAt, sp.Mcu_StationProgressProcessAt)
|
|
ELSE NULL
|
|
END,
|
|
'%d/%m/%Y %H:%i'), '')
|
|
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 = s.Mcu_PatientSchedulePreregisterID
|
|
AND mc.Mcu_CheckinoutMcuID = ?
|
|
AND mc.Mcu_CheckinoutDate = s.Mcu_PatientScheduleDate
|
|
AND mc.Mcu_CheckinoutIsActive = 'Y'
|
|
LEFT JOIN mcu_patient_required_station rs ON
|
|
rs.preregister_id = s.Mcu_PatientSchedulePreregisterID
|
|
AND rs.mcu_id = ?
|
|
LEFT JOIN mcu_station_progress sp ON
|
|
sp.Mcu_StationProgressPreregisterID = s.Mcu_PatientSchedulePreregisterID
|
|
AND sp.Mcu_StationProgressStationID = rs.sample_station_id
|
|
AND sp.Mcu_StationProgressMcuID = ?
|
|
AND (
|
|
(sp.Mcu_StationProgressCheckinDate IS NOT NULL AND sp.Mcu_StationProgressCheckinDate <= s.Mcu_PatientScheduleDate)
|
|
OR
|
|
(sp.Mcu_StationProgressCheckinDate IS NULL AND (
|
|
(sp.Mcu_StationProgressDoneAt IS NOT NULL AND DATE(sp.Mcu_StationProgressDoneAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressProcessAt IS NOT NULL AND DATE(sp.Mcu_StationProgressProcessAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressReceiveAt IS NOT NULL AND DATE(sp.Mcu_StationProgressReceiveAt) <= s.Mcu_PatientScheduleDate)
|
|
OR (sp.Mcu_StationProgressSamplingAt IS NOT NULL AND DATE(sp.Mcu_StationProgressSamplingAt) <= s.Mcu_PatientScheduleDate)
|
|
))
|
|
)
|
|
WHERE s.Mcu_PatientScheduleDate BETWEEN ? AND ?
|
|
AND s.Mcu_PatientScheduleIsActive = 'Y'
|
|
ORDER BY s.Mcu_PatientScheduleDate DESC, mc.Mcu_CheckinoutInTime DESC, mp.Mcu_PatientName ASC, rs.station_name
|
|
`, mcuID, mcuID, mcuID, mcuID, dateFrom, dateTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var patients []PatientDetail
|
|
keyIndex := map[patientKey]int{}
|
|
|
|
for rows.Next() {
|
|
var pid int
|
|
var name, date, inTime, outTime, stationName, processAt, doneAt string
|
|
var hasOutInt, doneInt, progressInt int
|
|
if err := rows.Scan(&pid, &name, &date, &inTime, &outTime, &hasOutInt, &stationName, &doneInt, &progressInt, &processAt, &doneAt); err != nil {
|
|
continue
|
|
}
|
|
k := patientKey{ID: pid, Date: date}
|
|
idx, ok := keyIndex[k]
|
|
if !ok {
|
|
p := PatientDetail{
|
|
ID: pid,
|
|
Name: name,
|
|
Date: date,
|
|
InTime: inTime,
|
|
OutTime: outTime,
|
|
HasOut: hasOutInt == 1,
|
|
}
|
|
keyIndex[k] = len(patients)
|
|
patients = append(patients, p)
|
|
idx = len(patients) - 1
|
|
}
|
|
if stationName != "" {
|
|
done := doneInt == 1
|
|
patients[idx].Stations = append(patients[idx].Stations, PatientStationStatus{
|
|
Station: stationName,
|
|
Done: done,
|
|
Progress: progressInt == 1,
|
|
ProcessAt: processAt,
|
|
DoneAt: doneAt,
|
|
})
|
|
if done {
|
|
patients[idx].DoneCount++
|
|
}
|
|
if progressInt == 1 {
|
|
patients[idx].ProgressCount++
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := range patients {
|
|
totalStations := len(patients[i].Stations)
|
|
switch {
|
|
case totalStations > 0 && patients[i].DoneCount == totalStations:
|
|
patients[i].ProgressStatus = "done"
|
|
case patients[i].DoneCount > 0 || patients[i].ProgressCount > 0:
|
|
patients[i].ProgressStatus = "partial"
|
|
default:
|
|
patients[i].ProgressStatus = "not_yet"
|
|
}
|
|
}
|
|
|
|
return patients, nil
|
|
}
|