package storage import "context" // DoctorQuality — то, чего нет в списке врачей админки: записи биографии по видам, // отзывы и заявки по каждому врачу. Полноту карточки считает страница «Дашборд врачей». type DoctorQuality struct { ID int64 `json:"id"` Education int `json:"education"` Experience int `json:"experience"` Training int `json:"training"` Awards int `json:"awards"` Conferences int `json:"conferences"` ReviewsPending int `json:"reviews_pending"` ReviewsLive int `json:"reviews_published"` RatingSum int `json:"rating_sum"` // сумма оценок всех отзывов с оценкой RatingCount int `json:"rating_count"` // сколько отзывов с оценкой Leads int `json:"leads"` // заявки к врачу или со страницы врача, без спама KidsFrom bool `json:"kids_from"` Languages bool `json:"languages"` PrimarySpecialty *string `json:"primary_specialty"` } type DoctorQualityReport struct { Doctors []DoctorQuality `json:"doctors"` ReviewsPendingTotal int `json:"reviews_pending_total"` ReviewsPendingNoDoc int `json:"reviews_pending_clinic"` // отзывы о клинике в целом ReviewsLiveTotal int `json:"reviews_published_total"` LeadsTotal int `json:"leads_total"` } func (s *Storage) DoctorQuality(ctx context.Context) (*DoctorQualityReport, error) { rep := &DoctorQualityReport{Doctors: []DoctorQuality{}} rows, err := s.pool.Query(ctx, ` SELECT d.id, count(*) FILTER (WHERE e.kind = 'education'), count(*) FILTER (WHERE e.kind = 'experience'), count(*) FILTER (WHERE e.kind = 'training'), count(*) FILTER (WHERE e.kind = 'award'), count(*) FILTER (WHERE e.kind = 'conference'), (SELECT count(*) FROM reviews r WHERE r.doctor_id = d.id AND r.status = 'pending'), (SELECT count(*) FROM reviews r WHERE r.doctor_id = d.id AND r.status = 'published'), (SELECT COALESCE(sum(r.rating), 0) FROM reviews r WHERE r.doctor_id = d.id AND r.rating IS NOT NULL AND r.status <> 'rejected'), (SELECT count(r.rating) FROM reviews r WHERE r.doctor_id = d.id AND r.status <> 'rejected'), (SELECT count(*) FROM leads l WHERE l.status <> 'spam' AND (l.doctor_id = d.id OR (l.doctor_id IS NULL AND l.page_url LIKE '%/company/staff/%/' || d.slug || '/%'))), d.accepts_kids_from IS NOT NULL, cardinality(d.languages) > 0, (SELECT sp.name_plural FROM doctor_specialties ds JOIN specialties sp ON sp.id = ds.specialty_id WHERE ds.doctor_id = d.id ORDER BY ds.is_primary DESC, sp.sort, sp.id LIMIT 1) FROM doctors d LEFT JOIN doctor_bio_entries e ON e.doctor_id = d.id WHERE d.deleted_at IS NULL GROUP BY d.id ORDER BY d.id`) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var q DoctorQuality if err := rows.Scan(&q.ID, &q.Education, &q.Experience, &q.Training, &q.Awards, &q.Conferences, &q.ReviewsPending, &q.ReviewsLive, &q.RatingSum, &q.RatingCount, &q.Leads, &q.KidsFrom, &q.Languages, &q.PrimarySpecialty); err != nil { return nil, err } rep.Doctors = append(rep.Doctors, q) } if err := rows.Err(); err != nil { return nil, err } err = s.pool.QueryRow(ctx, ` SELECT count(*) FILTER (WHERE status = 'pending'), count(*) FILTER (WHERE status = 'pending' AND doctor_id IS NULL), count(*) FILTER (WHERE status = 'published'), (SELECT count(*) FROM leads WHERE status <> 'spam') FROM reviews`).Scan(&rep.ReviewsPendingTotal, &rep.ReviewsPendingNoDoc, &rep.ReviewsLiveTotal, &rep.LeadsTotal) return rep, err }