295 lines
7.8 KiB
Go
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
|
|
}
|