niki/patientapp/repository/mysql/analytic_repo.go

387 lines
8.4 KiB
Go

package mysql
import (
"context"
"fmt"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)
type DataBase struct {
conn *mysql.DB
}
func NewPatientRepo(db *mysql.DB) *DataBase {
return &DataBase{
conn: db,
}
}
func (db *DataBase) GetPatients(ctx context.Context, f analytic.PatientFilter) ([]entity.UserMeta, error) {
const Op = "repository.mysql.patient.get"
tx, err := db.conn.Conn().BeginTx(ctx, nil)
if err != nil {
return []entity.UserMeta{}, richerror.New(Op).WithErr(err)
}
defer tx.Rollback()
query := `
SELECT
um.id,
um.user_id,
u.birthDate,
u.sex,
um.birthPlaceState,
um.birthPlaceCity,
um.nationality,
um.addressState,
um.addressCity,
um.address,
um.phone,
um.mobile,
um.spouseName,
um.created_at,
um.updated_at
FROM user_metas um
JOIN users u ON u.id = um.user_id
WHERE 1=1
`
args := []any{}
// Birthdate filters (FROM = born after)
if f.DOBFrom != nil && *f.DOBFrom != "" {
query += " AND u.birth_date >= ?"
args = append(args, *f.DOBFrom)
}
if f.DOBTo != nil && *f.DOBTo != "" {
query += " AND u.birth_date <= ?"
args = append(args, *f.DOBTo)
}
// Sex
if f.Sex != nil && *f.Sex != "" {
query += " AND u.sex = ?"
args = append(args, *f.Sex)
}
// Nationality
if f.Nationality != "" {
query += " AND um.nationality = ?"
args = append(args, f.Nationality)
}
// Address
if f.AddressState != 0 {
query += " AND um.addressState = ?"
args = append(args, f.AddressState)
}
if f.AddressCity != 0 {
query += " AND um.addressCity = ?"
args = append(args, f.AddressCity)
}
// Search on fields from user_metas and users
if f.Search != nil && *f.Search != "" {
like := "%" + *f.Search + "%"
query += `
AND (
um.fName LIKE ? OR
um.mName LIKE ? OR
um.spouseName LIKE ? OR
um.phone LIKE ? OR
um.mobile LIKE ?
)
`
args = append(args, like, like, like, like, like)
}
query += " ORDER BY id DESC"
if f.Limit > 0 {
query += " LIMIT ?"
args = append(args, f.Limit)
}
if f.Offset > 0 {
query += " OFFSET ?"
args = append(args, f.Offset)
}
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, richerror.New(Op).WithErr(err)
}
defer rows.Close()
var result []entity.UserMeta
for rows.Next() {
var u entity.UserMeta
if err := rows.Scan(
&u.ID, &u.UserID, &u.BirthDate, &u.Gender, &u.BirthPlaceState, &u.BirthPlaceCity,
&u.Religion, &u.Nationality, &u.AddressState, &u.AddressCity,
&u.Address, &u.Mobile, &u.SpouseName, &u.SpouseAlive,
&u.CreatedAt, &u.UpdatedAt,
); err != nil {
return nil, richerror.New(Op).WithErr(err)
}
result = append(result, u)
}
if err := rows.Err(); err != nil {
return nil, richerror.New(Op).WithErr(err)
}
return result, nil
}
func (db *DataBase) CountPatients(ctx context.Context, f analytic.PatientFilter) (int, error) {
const Op = "repository.mysql.patient.count"
tx, err := db.conn.Conn().BeginTx(ctx, nil)
if err != nil {
return 0, richerror.New(Op).WithErr(err)
}
defer tx.Rollback()
query := `
SELECT COUNT(*)
FROM user_metas um
JOIN users u ON u.id = um.user_id
WHERE 1=1
`
args := []any{}
// Birthdate range
if f.DOBFrom != nil && *f.DOBFrom != "" {
query += " AND u.birth_date >= ?"
args = append(args, *f.DOBFrom)
}
if f.DOBTo != nil && *f.DOBTo != "" {
query += " AND u.birth_date <= ?"
args = append(args, *f.DOBTo)
}
// Sex
if f.Sex != nil && *f.Sex != "" {
query += " AND u.sex = ?"
args = append(args, *f.Sex)
}
// Nationality
if f.Nationality != "" {
query += " AND um.nationality = ?"
args = append(args, f.Nationality)
}
// Address
if f.AddressState != 0 {
query += " AND um.addressState = ?"
args = append(args, f.AddressState)
}
if f.AddressCity != 0 {
query += " AND um.addressCity = ?"
args = append(args, f.AddressCity)
}
// Search
if f.Search != nil && *f.Search != "" {
like := "%" + *f.Search + "%"
query += `
AND (
um.fName LIKE ? OR
um.mName LIKE ? OR
um.spouseName LIKE ? OR
um.phone LIKE ? OR
um.mobile LIKE ?
)
`
args = append(args, like, like, like, like, like)
}
var count int
err = tx.QueryRowContext(ctx, query, args...).Scan(&count)
if err != nil {
return 0, fmt.Errorf("%s: query error: %w", Op, err)
}
return count, nil
}
func (db *DataBase) SummaryByCity(ctx context.Context, provinceID uint, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
const Op = "repository.mysql.patient.map_summary"
tx, err := db.conn.Conn().BeginTx(ctx, nil)
if err != nil {
return nil, richerror.New(Op).WithErr(err)
}
defer tx.Rollback()
query := `
SELECT
um.addressCity AS city_id,
ANY_VALUE(um.lat) AS lat,
ANY_VALUE(um.lng) AS lng,
COUNT(*) AS user_count
FROM user_metas um
JOIN users u ON u.id = um.user_id
JOIN cities c ON c.id = um.addressCity
WHERE c.state_id = ?
`
args := []any{provinceID}
// Birthdate filters
if f.MinDOB != nil && *f.MinDOB != "" {
query += " AND u.birth_date >= ?"
args = append(args, *f.MinDOB)
}
if f.MaxDOB != nil && *f.MaxDOB != "" {
query += " AND u.birth_date <= ?"
args = append(args, *f.MaxDOB)
}
// Sex filter
if f.Sex != nil && *f.Sex != "" {
query += " AND u.sex = ?"
args = append(args, *f.Sex)
}
// Search filter
if f.Search != nil && *f.Search != "" {
like := "%" + *f.Search + "%"
query += `
AND (
um.fName LIKE ? OR
um.mName LIKE ? OR
um.spouseName LIKE ? OR
um.phone LIKE ? OR
um.mobile LIKE ?
)
`
args = append(args, like, like, like, like, like)
}
// Group by city
query += " GROUP BY um.addressCity"
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("%s: query error: %w", Op, err)
}
defer rows.Close()
result := make(map[uint][]entity.MapSummaryItem)
for rows.Next() {
var item entity.MapSummaryItem
err := rows.Scan(
&item.ID,
&item.Latitude,
&item.Longitude,
&item.Count,
)
if err != nil {
return nil, fmt.Errorf("%s: scan error: %w", Op, err)
}
result[item.ID] = append(result[item.ID], item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("%s: rows error: %w", Op, err)
}
return result, nil
}
func (db *DataBase) SummaryByProvince(ctx context.Context, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
const Op = "repository.mysql.patient.summary_by_province"
tx, err := db.conn.Conn().BeginTx(ctx, nil)
if err != nil {
return nil, richerror.New(Op).WithErr(err)
}
defer tx.Rollback()
query := `
SELECT
c.state_id,
ANY_VALUE(c.lat) AS lat,
ANY_VALUE(c.lng) AS lng,
COUNT(*) AS total
FROM user_metas um
JOIN users u ON u.id = um.user_id
JOIN cities c ON c.id = um.addressCity
WHERE 1 = 1
`
args := []any{}
// Birthdate filters
if f.MinDOB != nil && *f.MinDOB != "" {
query += " AND u.birth_date >= ?"
args = append(args, *f.MinDOB)
}
if f.MaxDOB != nil && *f.MaxDOB != "" {
query += " AND u.birth_date <= ?"
args = append(args, *f.MaxDOB)
}
// Sex filter
if f.Sex != nil && *f.Sex != "" {
query += " AND u.sex = ?"
args = append(args, *f.Sex)
}
// Search filter
if f.Search != nil && *f.Search != "" {
like := "%" + *f.Search + "%"
query += `
AND (
um.fName LIKE ? OR
um.mName LIKE ? OR
um.spouseName LIKE ? OR
um.phone LIKE ? OR
um.mobile LIKE ?
)
`
args = append(args, like, like, like, like, like)
}
query += " GROUP BY c.state_id"
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("%s: query error: %w", Op, err)
}
defer rows.Close()
result := make(map[uint][]entity.MapSummaryItem)
for rows.Next() {
var item entity.MapSummaryItem
if err := rows.Scan(&item.ID, &item.Latitude, &item.Longitude, &item.Count); err != nil {
return nil, fmt.Errorf("%s: scan error: %w", Op, err)
}
result[item.ID] = []entity.MapSummaryItem{item}
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("%s: rows error: %w", Op, err)
}
return result, nil
}