bug fixed

This commit is contained in:
Mohammad Amin 2026-04-03 20:02:43 +03:30
parent 787f338202
commit 564b021a7c
8 changed files with 27 additions and 2607 deletions

View File

@ -9,8 +9,8 @@ import (
func NewPatientAnalyticRouter(s *echo.Group) { func NewPatientAnalyticRouter(s *echo.Group) {
repo := mysql.NewPatientRepo() repo := mysql.NewPatientRepo()
validator := analytic2.NewValidator()
analyticService := analytic2.NewPatientAnalyticService(repo, *validator) analyticService := analytic2.NewPatientAnalyticService(repo)
h := NewHandler(analyticService) h := NewHandler(analyticService)

View File

@ -2,7 +2,6 @@ package mysql
import ( import (
"context" "context"
"log"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic" "git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity" "git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
@ -14,11 +13,8 @@ type DataBase struct {
} }
func NewPatientRepo( /*conn *mysql.DB*/ ) *DataBase { func NewPatientRepo( /*conn *mysql.DB*/ ) *DataBase {
patients, err := LoadPatientsFromJSON("C:\\Users\\cafel\\Documents\\gocast\\eb\\patientapp\\repository\\mysql\\rawdata.json") patients := make([]entity.Patient, 0)
if err != nil {
log.Fatal(err)
return nil
}
return &DataBase{ return &DataBase{
//conn: conn, //conn: conn,
patients: patients, patients: patients,
@ -38,316 +34,13 @@ func (db *DataBase) CountPatients(ctx context.Context, f analytic.PatientFilter)
func (db *DataBase) SummaryByCity(ctx context.Context, provinceID uint, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) { func (db *DataBase) SummaryByCity(ctx context.Context, provinceID uint, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
//var out []entity.MapSummaryItem var out map[uint][]entity.MapSummaryItem
result := make(map[uint][]entity.MapSummaryItem, 0)
for _, patient := range db.patients { return out, nil
if patient.Address.ProvinceID == provinceID {
out := entity.MapSummaryItem{
LocationID: int64(patient.Address.ID),
Name: patient.Address.Name,
CentroidLat: patient.Address.Lat,
CentroidLng: patient.Address.Lon,
}
result[patient.Address.ProvinceID] = append(result[patient.Address.ProvinceID], out)
}
}
return result, nil
} }
func (db *DataBase) SummaryByProvince(ctx context.Context, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) { func (db *DataBase) SummaryByProvince(ctx context.Context, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
result := make(map[uint][]entity.MapSummaryItem, 0) result := make(map[uint][]entity.MapSummaryItem, 0)
for _, patient := range db.patients {
out := entity.MapSummaryItem{
LocationID: int64(patient.Address.ID),
Name: patient.Address.Name,
CentroidLat: patient.Address.Lat,
CentroidLng: patient.Address.Lon,
}
result[patient.Address.ProvinceID] = append(result[patient.Address.ProvinceID], out)
}
return result, nil return result, nil
} }
//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()
//}

View File

@ -1,10 +0,0 @@
postgres_db:
driver: postgres
host: patient-db
port: 5432
user: patient_admin
password: password1234
db_name: patient_db
ssl_mode: disable
path_of_migration: ./patientapp/repository/mysql/migrations

View File

@ -1,56 +0,0 @@
package mysql
import (
"encoding/json"
"fmt"
"os"
"strings"
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
)
type whereClause struct {
sql strings.Builder
args []any
}
func (w *whereClause) add(cond string, args ...any) {
if w.sql.Len() == 0 {
w.sql.WriteString(" WHERE ")
} else {
w.sql.WriteString(" AND ")
}
w.sql.WriteString(cond)
w.args = append(w.args, args...)
}
func (w *whereClause) String() string { return w.sql.String() }
func clampLimitOffset(limit, offset int) (int, int) {
if limit <= 0 {
limit = 50
}
if limit > 100 {
limit = 100
}
if offset < 0 {
offset = 0
}
return limit, offset
}
func LoadPatientsFromJSON(filePath string) ([]entity.Patient, error) {
// Read the file
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
// Unmarshal into slice of Patient
var patients []entity.Patient
if err := json.Unmarshal(data, &patients); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return patients, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,7 @@ type ListPatientAnalyticRequest struct {
Search *string `query:"search,omitempty"` Search *string `query:"search,omitempty"`
Limit int `query:"limit,omitempty"` Pagination *Pagination `query:"pagination,omitempty"`
Offset int `query:"offset,omitempty"`
} }
type PatientAnalyticItem struct { type PatientAnalyticItem struct {
@ -29,10 +28,9 @@ type PatientAnalyticItem struct {
Address entity.Address `json:"address"` Address entity.Address `json:"address"`
} }
type PatientAnalyticResponse struct { type PatientAnalyticResponse struct {
Items []PatientAnalyticItem `json:"items"` Items []PatientAnalyticItem `json:"items"`
Limit int `json:"limit"` Pagination *Pagination `json:"pagination"`
Offset int `json:"offset"` Total int `json:"total"`
Total int `json:"total"`
} }
func ToPatientResponse(patient entity.Patient) PatientAnalyticItem { func ToPatientResponse(patient entity.Patient) PatientAnalyticItem {
@ -50,8 +48,7 @@ func ToPatientResponse(patient entity.Patient) PatientAnalyticItem {
} }
} }
// =========================== Map ================================== // GetPatientMapSummaryRequest =========================== Map ==================================
type GetPatientMapSummaryRequest struct { type GetPatientMapSummaryRequest struct {
Level entity.MapLevel `query:"level"` Level entity.MapLevel `query:"level"`
ParentID *int `query:"parentID"` ParentID *int `query:"parentID"`
@ -66,3 +63,9 @@ type GetPatientMapSummaryResponse struct {
Level entity.MapLevel `json:"level"` Level entity.MapLevel `json:"level"`
Items map[uint][]entity.MapSummaryItem `json:"items"` Items map[uint][]entity.MapSummaryItem `json:"items"`
} }
// Pagination ================================ Pagination =============================
type Pagination struct {
Limit int `query:"limit,omitempty"`
Offset int `query:"offset,omitempty"`
}

View File

@ -19,25 +19,23 @@ type Repository interface {
GetPatients(ctx context.Context, f PatientFilter) ([]entity.Patient, error) GetPatients(ctx context.Context, f PatientFilter) ([]entity.Patient, error)
CountPatients(ctx context.Context, f PatientFilter) (int, error) CountPatients(ctx context.Context, f PatientFilter) (int, error)
SummaryByCity(ctx context.Context, provinceID int, f PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) SummaryByCity(ctx context.Context, provinceID uint, f PatientMapFilter) (map[uint][]entity.MapSummaryItem, error)
SummaryByProvince(ctx context.Context, f PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) SummaryByProvince(ctx context.Context, f PatientMapFilter) (map[uint][]entity.MapSummaryItem, error)
} }
type Service struct { type Service struct {
repository Repository repository Repository
validator Validator
} }
func NewPatientAnalyticService(repo Repository, validator Validator) Service { func NewPatientAnalyticService(repo Repository) Service {
return Service{ return Service{
repository: repo, repository: repo,
validator: validator,
} }
} }
func (s Service) List(ctx context.Context, req ListPatientAnalyticRequest) (PatientAnalyticResponse, error) { func (s Service) List(ctx context.Context, req ListPatientAnalyticRequest) (PatientAnalyticResponse, error) {
limit, offset := normalizeLimitOffset(req.Limit, req.Offset) limit, offset := normalizeLimitOffset(req.Pagination.Limit, req.Pagination.Offset)
// convert age range // convert age range
dobFrom, dobTo := ageRangeToDOB(req.MinAge, req.MaxAge, time.Now()) dobFrom, dobTo := ageRangeToDOB(req.MinAge, req.MaxAge, time.Now())
@ -70,10 +68,12 @@ func (s Service) List(ctx context.Context, req ListPatientAnalyticRequest) (Pati
} }
return PatientAnalyticResponse{ return PatientAnalyticResponse{
Items: out, Items: out,
Limit: limit, Pagination: &Pagination{
Offset: offset, Limit: limit,
Total: total, Offset: offset,
},
Total: total,
}, nil }, nil
} }
@ -95,7 +95,7 @@ func (s Service) GetMapSummary(ctx context.Context, req GetPatientMapSummaryRequ
return GetPatientMapSummaryResponse{}, ErrInvalidProvinceID return GetPatientMapSummaryResponse{}, ErrInvalidProvinceID
} }
items, err := s.repository.SummaryByCity(ctx, *req.ParentID, filter) items, err := s.repository.SummaryByCity(ctx, uint(*req.ParentID), filter)
if err != nil { if err != nil {
return GetPatientMapSummaryResponse{}, fmt.Errorf("SummaryByCity: %w", err) return GetPatientMapSummaryResponse{}, fmt.Errorf("SummaryByCity: %w", err)
} }

View File

@ -1,8 +0,0 @@
package analytic
type Validator struct {
}
func NewValidator() *Validator {
return &Validator{}
}