crm/internal/storage/followup_storage.go
2026-01-13 18:02:43 +08:00

243 lines
5.2 KiB
Go

package storage
import (
"crm-go/models"
"crypto/rand"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
)
type FollowUpStorage interface {
GetAllFollowUps() ([]models.FollowUp, error)
GetFollowUpByID(id string) (*models.FollowUp, error)
CreateFollowUp(followUp models.FollowUp) error
UpdateFollowUp(id string, updates models.UpdateFollowUpRequest) error
DeleteFollowUp(id string) error
SaveFollowUps(followUps []models.FollowUp) error
LoadFollowUps() ([]models.FollowUp, error)
GetPendingNotifications() ([]models.FollowUp, error)
MarkNotificationSent(id string) error
}
type followUpStorage struct {
filePath string
mutex sync.RWMutex
}
func NewFollowUpStorage(filePath string) FollowUpStorage {
storage := &followUpStorage{
filePath: filePath,
}
return storage
}
func (fs *followUpStorage) GetAllFollowUps() ([]models.FollowUp, error) {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
followUps, err := fs.LoadFollowUps()
if err != nil {
return nil, err
}
// Sort by FollowUpTime in descending order (newest first)
for i := 0; i < len(followUps)-1; i++ {
for j := i + 1; j < len(followUps); j++ {
if followUps[i].FollowUpTime.Before(followUps[j].FollowUpTime) {
followUps[i], followUps[j] = followUps[j], followUps[i]
}
}
}
return followUps, nil
}
func (fs *followUpStorage) GetFollowUpByID(id string) (*models.FollowUp, error) {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
followUps, err := fs.LoadFollowUps()
if err != nil {
return nil, err
}
for _, followUp := range followUps {
if followUp.ID == id {
return &followUp, nil
}
}
return nil, nil
}
func (fs *followUpStorage) CreateFollowUp(followUp models.FollowUp) error {
fs.mutex.Lock()
defer fs.mutex.Unlock()
if followUp.ID == "" {
followUp.ID = generateFollowUpUUID()
}
if followUp.CreatedAt.IsZero() {
followUp.CreatedAt = time.Now()
}
followUps, err := fs.LoadFollowUps()
if err != nil {
return err
}
followUps = append(followUps, followUp)
return fs.SaveFollowUps(followUps)
}
func generateFollowUpUUID() 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)
}
func (fs *followUpStorage) UpdateFollowUp(id string, updates models.UpdateFollowUpRequest) error {
fs.mutex.Lock()
defer fs.mutex.Unlock()
followUps, err := fs.LoadFollowUps()
if err != nil {
return err
}
for i, followUp := range followUps {
if followUp.ID == id {
if updates.CustomerName != nil {
followUps[i].CustomerName = *updates.CustomerName
}
if updates.DealStatus != nil {
followUps[i].DealStatus = *updates.DealStatus
}
if updates.CustomerLevel != nil {
followUps[i].CustomerLevel = *updates.CustomerLevel
}
if updates.Industry != nil {
followUps[i].Industry = *updates.Industry
}
if updates.FollowUpTime != nil {
// Parse the time string
t, err := time.Parse(time.RFC3339, *updates.FollowUpTime)
if err == nil {
followUps[i].FollowUpTime = t
}
}
if updates.NotificationSent != nil {
followUps[i].NotificationSent = *updates.NotificationSent
}
return fs.SaveFollowUps(followUps)
}
}
return nil // FollowUp not found, but not an error
}
func (fs *followUpStorage) DeleteFollowUp(id string) error {
fs.mutex.Lock()
defer fs.mutex.Unlock()
followUps, err := fs.LoadFollowUps()
if err != nil {
return err
}
for i, followUp := range followUps {
if followUp.ID == id {
followUps = append(followUps[:i], followUps[i+1:]...)
return fs.SaveFollowUps(followUps)
}
}
return nil // FollowUp not found, but not an error
}
func (fs *followUpStorage) SaveFollowUps(followUps []models.FollowUp) error {
// Ensure the directory exists
dir := filepath.Dir(fs.filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
data, err := json.MarshalIndent(followUps, "", " ")
if err != nil {
return err
}
return os.WriteFile(fs.filePath, data, 0644)
}
func (fs *followUpStorage) LoadFollowUps() ([]models.FollowUp, error) {
// Check if file exists
if _, err := os.Stat(fs.filePath); os.IsNotExist(err) {
// Return empty slice if file doesn't exist
return []models.FollowUp{}, nil
}
data, err := os.ReadFile(fs.filePath)
if err != nil {
return nil, err
}
var followUps []models.FollowUp
if err := json.Unmarshal(data, &followUps); err != nil {
return nil, err
}
return followUps, nil
}
func (fs *followUpStorage) GetPendingNotifications() ([]models.FollowUp, error) {
fs.mutex.RLock()
defer fs.mutex.RUnlock()
followUps, err := fs.LoadFollowUps()
if err != nil {
return nil, err
}
now := time.Now()
var pending []models.FollowUp
for _, followUp := range followUps {
// Check if follow-up time has passed and notification hasn't been sent
if !followUp.NotificationSent && followUp.FollowUpTime.Before(now) {
pending = append(pending, followUp)
}
}
return pending, nil
}
func (fs *followUpStorage) MarkNotificationSent(id string) error {
fs.mutex.Lock()
defer fs.mutex.Unlock()
followUps, err := fs.LoadFollowUps()
if err != nil {
return err
}
for i, followUp := range followUps {
if followUp.ID == id {
followUps[i].NotificationSent = true
return fs.SaveFollowUps(followUps)
}
}
return nil
}