Files
wxapp-guantong/backend/internal/service/admin.go
T
2026-09-22 22:51:19 +08:00

295 lines
7.8 KiB
Go

package service
import (
"context"
"database/sql"
"errors"
"strings"
"github.com/rogeecn/wxapp-kouqiang-guahao/backend/internal/db"
)
const HomeHeroImageConfigKey = "home_hero_image"
type AdminPhoneRecord struct {
Phone string `json:"phone"`
UserID string `json:"user_id"`
OpenID string `json:"openid"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
LastLoginAt string `json:"last_login_at"`
}
type AdminBookingRecord struct {
BookingID string `json:"booking_id"`
Phone string `json:"phone"`
ProjectID string `json:"project_id"`
ProjectName string `json:"project_name"`
Date string `json:"date"`
StartTime string `json:"start_time"`
Status string `json:"status"`
StatusText string `json:"status_text"`
CreatedAt string `json:"created_at"`
}
type AdminPriceInquiryRecord struct {
InquiryID string `json:"inquiry_id"`
Phone string `json:"phone"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Region string `json:"region"`
ProjectName string `json:"project_name"`
Status string `json:"status"`
StatusClass string `json:"status_class"`
StatusText string `json:"status_text"`
Remark string `json:"remark"`
LastSubmittedAt string `json:"last_submitted_at"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type AdminPriceInquiryFilter struct {
Area string
Phone string
Status string
TimeFrom string
TimeTo string
}
type AdminPriceInquiryPage struct {
Records []AdminPriceInquiryRecord
Total int64
Page int
PageSize int
TotalPages int
HasPreviousPage bool
HasNextPage bool
PreviousPage int
NextPage int
}
func (s *Service) HomeHeroImage(ctx context.Context) (string, error) {
config, err := s.Q.GetAppConfig(ctx, HomeHeroImageConfigKey)
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {
return "", err
}
return config.ConfigValue, nil
}
func (s *Service) SetHomeHeroImage(ctx context.Context, image string) error {
_, err := s.Q.UpsertAppConfig(ctx, db.UpsertAppConfigParams{
ConfigKey: HomeHeroImageConfigKey,
ConfigValue: image,
})
return err
}
func (s *Service) AdminPhoneRecords(ctx context.Context) ([]AdminPhoneRecord, error) {
users, err := s.Q.ListUsersWithPhone(ctx)
if err != nil {
return nil, err
}
records := make([]AdminPhoneRecord, 0, len(users))
for _, user := range users {
phone := nullString(user.Phone)
if phone == "" {
continue
}
records = append(records, AdminPhoneRecord{
Phone: phone,
UserID: user.ID,
OpenID: user.Openid,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
LastLoginAt: user.LastLoginAt,
})
}
return records, nil
}
func (s *Service) AdminBookingRecords(ctx context.Context, phone string, limit, offset int64) ([]AdminBookingRecord, error) {
phone = strings.TrimSpace(phone)
var (
details []BookingDetail
err error
)
if phone != "" {
details, err = s.ListBookingDetailsByPhone(ctx, phone)
} else {
details, err = s.AdminListBookingDetails(ctx, "", limit, offset)
}
if err != nil {
return nil, err
}
records := make([]AdminBookingRecord, 0, len(details))
for _, detail := range details {
records = append(records, AdminBookingRecord{
BookingID: detail.Booking.ID,
Phone: detail.Booking.Phone,
ProjectID: detail.Project.ID,
ProjectName: detail.Project.Name,
Date: detail.Day.Date,
StartTime: detail.Slot.StartTime,
Status: detail.Booking.Status,
StatusText: adminBookingStatusText(detail.Booking.Status),
CreatedAt: detail.Booking.CreatedAt,
})
}
return records, nil
}
func (s *Service) AdminPriceInquiryRecords(ctx context.Context, filter AdminPriceInquiryFilter, limit, offset int64) ([]AdminPriceInquiryRecord, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
filter = normalizeAdminPriceInquiryFilter(filter)
inquiries, err := s.Q.ListPriceInquiries(ctx, db.ListPriceInquiriesParams{
Area: filter.Area,
Phone: filter.Phone,
Status: filter.Status,
TimeFrom: filter.TimeFrom,
TimeTo: filter.TimeTo,
Limit: limit,
Offset: offset,
})
if err != nil {
return nil, err
}
return adminPriceInquiryRecordsFromRows(inquiries), nil
}
func (s *Service) AdminPriceInquiryRecordsPage(ctx context.Context, filter AdminPriceInquiryFilter, page, pageSize int) (AdminPriceInquiryPage, error) {
filter = normalizeAdminPriceInquiryFilter(filter)
total, err := s.Q.CountPriceInquiries(ctx, db.CountPriceInquiriesParams{
Area: filter.Area,
Phone: filter.Phone,
Status: filter.Status,
TimeFrom: filter.TimeFrom,
TimeTo: filter.TimeTo,
})
if err != nil {
return AdminPriceInquiryPage{}, err
}
offset := int64((page - 1) * pageSize)
inquiries, err := s.Q.ListPriceInquiries(ctx, db.ListPriceInquiriesParams{
Area: filter.Area,
Phone: filter.Phone,
Status: filter.Status,
TimeFrom: filter.TimeFrom,
TimeTo: filter.TimeTo,
Limit: int64(pageSize),
Offset: offset,
})
if err != nil {
return AdminPriceInquiryPage{}, err
}
totalPages := 1
if total > 0 {
totalPages = int((total + int64(pageSize) - 1) / int64(pageSize))
}
result := AdminPriceInquiryPage{
Records: adminPriceInquiryRecordsFromRows(inquiries),
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
HasPreviousPage: page > 1,
HasNextPage: page < totalPages,
}
if result.HasPreviousPage {
result.PreviousPage = page - 1
}
if result.HasNextPage {
result.NextPage = page + 1
}
return result, nil
}
func adminPriceInquiryRecordsFromRows(inquiries []db.PriceInquiry) []AdminPriceInquiryRecord {
records := make([]AdminPriceInquiryRecord, 0, len(inquiries))
for _, inquiry := range inquiries {
records = append(records, AdminPriceInquiryRecord{
InquiryID: inquiry.ID,
Phone: inquiry.Phone,
Province: inquiry.Province,
City: inquiry.City,
District: inquiry.District,
Region: inquiry.Province + " " + inquiry.City + " " + inquiry.District,
ProjectName: inquiry.ProjectName,
Status: inquiry.Status,
StatusClass: adminPriceInquiryStatusClass(inquiry.Status),
StatusText: adminPriceInquiryStatusText(inquiry.Status),
Remark: inquiry.Remark,
LastSubmittedAt: inquiry.LastSubmittedAt,
CreatedAt: inquiry.CreatedAt,
UpdatedAt: inquiry.UpdatedAt,
})
}
return records
}
func normalizeAdminPriceInquiryFilter(filter AdminPriceInquiryFilter) AdminPriceInquiryFilter {
return AdminPriceInquiryFilter{
Area: strings.TrimSpace(filter.Area),
Phone: strings.TrimSpace(filter.Phone),
Status: strings.TrimSpace(filter.Status),
TimeFrom: strings.TrimSpace(filter.TimeFrom),
TimeTo: strings.TrimSpace(filter.TimeTo),
}
}
func adminBookingStatusText(status string) string {
switch status {
case "pending":
return "待确认"
case "confirmed":
return "已预约"
case "cancelled_by_user":
return "用户已取消"
case "cancelled_by_admin":
return "后台已取消"
default:
return status
}
}
func adminPriceInquiryStatusText(status string) string {
switch status {
case "pending":
return "待派单"
case "assigned":
return "已经派单"
case "completed":
return "已跟进"
default:
return status
}
}
func adminPriceInquiryStatusClass(status string) string {
switch status {
case "pending":
return "status-pending"
case "assigned":
return "status-assigned"
case "completed":
return "status-completed"
default:
return "status-unknown"
}
}
func nullString(value sql.NullString) string {
if !value.Valid {
return ""
}
return value.String
}