package storage import ( "crm-go/models" "crypto/rand" "database/sql" "encoding/hex" "fmt" "strings" "time" ) type mysqlFollowUpStorage struct { db *sql.DB } // NewMySQLFollowUpStorage 创建MySQL跟进存储 func NewMySQLFollowUpStorage() FollowUpStorage { return &mysqlFollowUpStorage{ db: GetDB(), } } func (fs *mysqlFollowUpStorage) GetAllFollowUps() ([]models.FollowUp, error) { query := ` SELECT id, created_at, customer_name, deal_status, customer_level, industry, follow_up_time, notification_sent FROM followups ORDER BY follow_up_time DESC ` rows, err := fs.db.Query(query) if err != nil { return nil, err } defer rows.Close() var followUps []models.FollowUp for rows.Next() { var f models.FollowUp var dealStatus, customerLevel, industry sql.NullString var notificationSent int err := rows.Scan( &f.ID, &f.CreatedAt, &f.CustomerName, &dealStatus, &customerLevel, &industry, &f.FollowUpTime, ¬ificationSent, ) if err != nil { return nil, err } f.DealStatus = dealStatus.String f.CustomerLevel = customerLevel.String f.Industry = industry.String f.NotificationSent = notificationSent == 1 followUps = append(followUps, f) } return followUps, rows.Err() } func (fs *mysqlFollowUpStorage) GetFollowUpByID(id string) (*models.FollowUp, error) { query := ` SELECT id, created_at, customer_name, deal_status, customer_level, industry, follow_up_time, notification_sent FROM followups WHERE id = ? ` var f models.FollowUp var dealStatus, customerLevel, industry sql.NullString var notificationSent int err := fs.db.QueryRow(query, id).Scan( &f.ID, &f.CreatedAt, &f.CustomerName, &dealStatus, &customerLevel, &industry, &f.FollowUpTime, ¬ificationSent, ) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } f.DealStatus = dealStatus.String f.CustomerLevel = customerLevel.String f.Industry = industry.String f.NotificationSent = notificationSent == 1 return &f, nil } func (fs *mysqlFollowUpStorage) CreateFollowUp(followUp models.FollowUp) error { if followUp.ID == "" { followUp.ID = generateFollowUpMySQLUUID() } if followUp.CreatedAt.IsZero() { followUp.CreatedAt = time.Now() } query := ` INSERT INTO followups (id, created_at, customer_name, deal_status, customer_level, industry, follow_up_time, notification_sent) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ` notificationSent := 0 if followUp.NotificationSent { notificationSent = 1 } _, err := fs.db.Exec(query, followUp.ID, followUp.CreatedAt, followUp.CustomerName, followUp.DealStatus, followUp.CustomerLevel, followUp.Industry, followUp.FollowUpTime, notificationSent, ) return err } func (fs *mysqlFollowUpStorage) UpdateFollowUp(id string, updates models.UpdateFollowUpRequest) error { // 首先获取现有记录 existing, err := fs.GetFollowUpByID(id) if err != nil || existing == nil { return err } // 应用更新 if updates.CustomerName != nil { existing.CustomerName = *updates.CustomerName } if updates.DealStatus != nil { existing.DealStatus = *updates.DealStatus } if updates.CustomerLevel != nil { existing.CustomerLevel = *updates.CustomerLevel } if updates.Industry != nil { existing.Industry = *updates.Industry } if updates.FollowUpTime != nil { t, err := time.Parse(time.RFC3339, *updates.FollowUpTime) if err == nil { existing.FollowUpTime = t } } if updates.NotificationSent != nil { existing.NotificationSent = *updates.NotificationSent } query := ` UPDATE followups SET customer_name = ?, deal_status = ?, customer_level = ?, industry = ?, follow_up_time = ?, notification_sent = ? WHERE id = ? ` notificationSent := 0 if existing.NotificationSent { notificationSent = 1 } _, err = fs.db.Exec(query, existing.CustomerName, existing.DealStatus, existing.CustomerLevel, existing.Industry, existing.FollowUpTime, notificationSent, id, ) return err } func (fs *mysqlFollowUpStorage) DeleteFollowUp(id string) error { query := `DELETE FROM followups WHERE id = ?` _, err := fs.db.Exec(query, id) if err != nil { if strings.Contains(err.Error(), "command denied") { return fmt.Errorf("数据库权限不足:无法执行删除操作,请联系管理员") } return err } return nil } func (fs *mysqlFollowUpStorage) SaveFollowUps(followUps []models.FollowUp) error { // MySQL版本不需要使用此方法,保留接口兼容 return nil } func (fs *mysqlFollowUpStorage) LoadFollowUps() ([]models.FollowUp, error) { return fs.GetAllFollowUps() } func (fs *mysqlFollowUpStorage) GetPendingNotifications() ([]models.FollowUp, error) { query := ` SELECT id, created_at, customer_name, deal_status, customer_level, industry, follow_up_time, notification_sent FROM followups WHERE notification_sent = 0 AND follow_up_time < NOW() ` rows, err := fs.db.Query(query) if err != nil { return nil, err } defer rows.Close() var pending []models.FollowUp for rows.Next() { var f models.FollowUp var dealStatus, customerLevel, industry sql.NullString var notificationSent int err := rows.Scan( &f.ID, &f.CreatedAt, &f.CustomerName, &dealStatus, &customerLevel, &industry, &f.FollowUpTime, ¬ificationSent, ) if err != nil { return nil, err } f.DealStatus = dealStatus.String f.CustomerLevel = customerLevel.String f.Industry = industry.String f.NotificationSent = notificationSent == 1 pending = append(pending, f) } return pending, rows.Err() } func (fs *mysqlFollowUpStorage) MarkNotificationSent(id string) error { query := `UPDATE followups SET notification_sent = 1 WHERE id = ?` _, err := fs.db.Exec(query, id) return err } func generateFollowUpMySQLUUID() string { bytes := make([]byte, 16) rand.Read(bytes) bytes[6] = (bytes[6] & 0x0f) | 0x40 // Version 4 bytes[8] = (bytes[8] & 0x3f) | 0x80 // Variant return hex.EncodeToString(bytes) }