niki/patientapp/repository/database/analytic_repo.go

304 lines
7.1 KiB
Go

package database
import (
"context"
"database/sql"
"strings"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)
type DataBase struct {
conn *mysql.DB
}
func NewPatientRepo(conn *mysql.DB) *DataBase {
return &DataBase{
conn: conn,
}
}
func (db *DataBase) GetPatients(ctx context.Context, f analytic.PatientFilter) ([]entity.Patient, error) {
const op = "mysqlpatient.GetPatients"
limit, offset := clampLimitOffset(f.Limit, f.Offset)
q := `
SELECT
p.id,
p.first_name,
p.last_name,
p.date_of_birth,
p.sex,
p.phone,
p.country_id,
p.province_id,
p.city_id,
p.postal_code,
p.address_line1,
p.address_line2,
p.case_status,
p.referral_source,
p.assigned_staff_id,
p.start_date,
p.end_date
FROM patients p
WHERE p.deleted_at IS NULL
`
stmt, err := db.conn.PrepareStatement(ctx, mysql.StatementKeyCityGetProvinceIDByID, q)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgCantPrepareStatement).WithKind(richerror.KindUnexpected)
}
args := make([]any, 0)
// filters
if f.Sex != nil {
q += " AND p.sex = ?"
args = append(args, string(*f.Sex))
}
if f.DOBFrom != nil {
q += " AND p.date_of_birth >= ?"
args = append(args, f.DOBFrom.Format("2006-01-02"))
}
if f.DOBTo != nil {
q += " AND p.date_of_birth <= ?"
args = append(args, f.DOBTo.Format("2006-01-02"))
}
if f.Country != nil {
q += " AND p.country_id = ?"
args = append(args, *f.Country)
}
if f.Province != nil {
q += " AND p.province_id = ?"
args = append(args, *f.Province)
}
if f.City != nil {
q += " AND p.city_id = ?"
args = append(args, *f.City)
}
if f.Search != nil && strings.TrimSpace(*f.Search) != "" {
term := "%" + strings.TrimSpace(*f.Search) + "%"
q += " AND (p.first_name LIKE ? OR p.last_name LIKE ? OR p.phone LIKE ?)"
args = append(args, term, term, term)
}
q += " ORDER BY p.id DESC LIMIT ? OFFSET ?"
args = append(args, limit, offset)
rows, err := stmt.QueryContext(ctx, args...)
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]entity.Patient, 0, limit)
for rows.Next() {
var p entity.Patient
var dob sql.NullTime
var provinceID, cityID uint
if err := rows.Scan(
&p.ID,
&p.FirstName,
&p.LastName,
&dob,
&p.Sex,
&p.Phone,
&provinceID,
&cityID,
&p.Address.PostalCode,
&p.CaseStatus,
&p.ReferralSource,
&p.AssignedStaffId,
&p.StartDate,
&p.EndDate,
); err != nil {
return nil, err
}
if dob.Valid {
t := dob.Time
p.DateOfBirth = &t
}
p.Address.ProvinceID = provinceID
p.Address.CityID = cityID
out = append(out, p)
}
return out, rows.Err()
}
func (db *DataBase) CountPatients(ctx context.Context, f analytic.PatientFilter) (int, error) {
const op = "mysqlpatient.CountPatients"
q := `SELECT COUNT(*) FROM patients p WHERE p.deleted_at IS NULL`
args := make([]any, 0)
stmt, err := db.conn.PrepareStatement(ctx, mysql.StatementKeyCityGetProvinceIDByID, q)
if err != nil {
return 0, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgCantPrepareStatement).WithKind(richerror.KindUnexpected)
}
if f.Sex != nil {
q += " AND p.sex = ?"
args = append(args, string(*f.Sex))
}
if f.DOBFrom != nil {
q += " AND p.date_of_birth >= ?"
args = append(args, f.DOBFrom.Format("2006-01-02"))
}
if f.DOBTo != nil {
q += " AND p.date_of_birth <= ?"
args = append(args, f.DOBTo.Format("2006-01-02"))
}
if f.Country != nil {
q += " AND p.country_id = ?"
args = append(args, *f.Country)
}
if f.Province != nil {
q += " AND p.province_id = ?"
args = append(args, *f.Province)
}
if f.City != nil {
q += " AND p.city_id = ?"
args = append(args, *f.City)
}
var total int
err = stmt.QueryRowContext(ctx, args...).Scan(&total)
return total, err
}
func (db *DataBase) SummaryByCity(ctx context.Context, provinceID int, f analytic.PatientMapFilter) ([]entity.MapSummaryItem, error) {
const op = "mysqlpatient.SummaryByCity"
q := `
SELECT
c.id AS location_id,
c.name,
c.centroid_lat,
c.centroid_lng,
COUNT(p.id) AS count
FROM locations c
JOIN patients p ON p.city_id = c.id
WHERE c.level = 'city'
AND c.parent_id = ?
AND p.deleted_at IS NULL
`
stmt, err := db.conn.PrepareStatement(ctx, mysql.StatementKeyCityGetProvinceIDByID, q)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgCantPrepareStatement).WithKind(richerror.KindUnexpected)
}
args := []any{provinceID}
if f.Sex != nil {
q += " AND p.sex = ?"
args = append(args, string(*f.Sex))
}
if f.MinDOB != nil {
q += " AND p.date_of_birth >= ?"
args = append(args, f.MinDOB.Format("2006-01-02"))
}
if f.MaxDOB != nil {
q += " AND p.date_of_birth <= ?"
args = append(args, f.MaxDOB.Format("2006-01-02"))
}
if f.Search != nil && strings.TrimSpace(*f.Search) != "" {
term := "%" + strings.TrimSpace(*f.Search) + "%"
q += " AND (p.first_name LIKE ? OR p.last_name LIKE ? OR p.phone LIKE ?)"
args = append(args, term, term, term)
}
q += ` GROUP BY c.id, c.name, c.centroid_lat, c.centroid_lng
ORDER BY count DESC`
rows, err := stmt.QueryContext(ctx, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var out []entity.MapSummaryItem
for rows.Next() {
var it entity.MapSummaryItem
if err := rows.Scan(&it.LocationID, &it.Name, &it.CentroidLat, &it.CentroidLng, &it.Count); err != nil {
return nil, err
}
out = append(out, it)
}
return out, rows.Err()
}
func (db *DataBase) SummaryByProvince(ctx context.Context, countryID int, f analytic.PatientMapFilter) ([]entity.MapSummaryItem, error) {
const op = "mysqlpatient.SummaryByProvince"
q := `
SELECT
prov.id AS location_id,
prov.name,
prov.centroid_lat,
prov.centroid_lng,
COUNT(p.id) AS count
FROM patients p
JOIN locations city ON city.id = p.city_id AND city.level = 'city'
JOIN locations prov ON prov.id = city.parent_id AND prov.level = 'province'
WHERE prov.parent_id = ?
AND p.deleted_at IS NULL
`
stmt, err := db.conn.PrepareStatement(ctx, mysql.StatementKeyCityGetProvinceIDByID, q)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgCantPrepareStatement).WithKind(richerror.KindUnexpected)
}
args := []any{countryID}
if f.Sex != nil {
q += " AND p.sex = ?"
args = append(args, string(*f.Sex))
}
if f.MinDOB != nil {
q += " AND p.date_of_birth >= ?"
args = append(args, f.MinDOB.Format("2006-01-02"))
}
if f.MaxDOB != nil {
q += " AND p.date_of_birth <= ?"
args = append(args, f.MaxDOB.Format("2006-01-02"))
}
q += ` GROUP BY prov.id, prov.name, prov.centroid_lat, prov.centroid_lng
ORDER BY count DESC`
rows, err := stmt.QueryContext(ctx, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var out []entity.MapSummaryItem
for rows.Next() {
var it entity.MapSummaryItem
if err := rows.Scan(&it.LocationID, &it.Name, &it.CentroidLat, &it.CentroidLng, &it.Count); err != nil {
return nil, err
}
out = append(out, it)
}
return out, rows.Err()
}