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 }