veza/veza-backend-api/internal/services/gear_warranty_notifier.go
senke 7692c4b8b9 feat(v0.802): frontend Cloud/Gear, MSW, docs, scope v0.803, archive
- Cloud: CloudFileVersions, CloudShareModal, versions/share in CloudView
- Gear: GearDocumentsTab, GearRepairsTab, warranty badge, initialTab
- MSW: cloud versions/share, gear documents/repairs, tags suggest
- Stories: CloudFileVersions, CloudShareModal, GearDetailModal variants
- gearService: listDocuments, uploadDocument, deleteDocument, listRepairs, createRepair, deleteRepair
- cloudService: listVersions, restoreVersion, shareFile, getSharedFile
- gear_warranty_notifier: 24h ticker, notifications for expiring warranty
- tag_handler_test: unit tests
- docs: API_REFERENCE, CHANGELOG, PROJECT_STATE, FEATURE_STATUS v0.802
- SCOPE_CONTROL, .cursorrules: scope v0.803
- archive: V0_802_RELEASE_SCOPE, RETROSPECTIVE_V0802
2026-02-25 14:00:58 +01:00

86 lines
2.3 KiB
Go

package services
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"veza-backend-api/internal/models"
)
// GearWarrantyNotifier sends notifications when gear warranty is expiring
type GearWarrantyNotifier struct {
db *gorm.DB
notificationService *NotificationService
logger *zap.Logger
interval time.Duration
}
// NewGearWarrantyNotifier creates a new warranty notifier
func NewGearWarrantyNotifier(db *gorm.DB, notificationService *NotificationService, logger *zap.Logger) *GearWarrantyNotifier {
return &GearWarrantyNotifier{
db: db,
notificationService: notificationService,
logger: logger,
interval: 24 * time.Hour,
}
}
// Start runs the notifier loop
func (n *GearWarrantyNotifier) Start(ctx context.Context) {
if n.notificationService == nil {
n.logger.Info("Gear warranty notifier: notification service not configured")
return
}
ticker := time.NewTicker(n.interval)
defer ticker.Stop()
n.logger.Info("Gear warranty notifier started", zap.Duration("interval", n.interval))
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := n.checkAndNotify(ctx); err != nil {
n.logger.Error("Gear warranty notifier failed", zap.Error(err))
}
}
}
}
func (n *GearWarrantyNotifier) checkAndNotify(ctx context.Context) error {
// Gear with warranty_expire between now and now+30 days
now := time.Now()
expiryLimit := now.Add(30 * 24 * time.Hour)
var items []models.GearItem
err := n.db.WithContext(ctx).Where(
"warranty_expire IS NOT NULL AND warranty_expire > ? AND warranty_expire <= ?",
now, expiryLimit,
).Find(&items).Error
if err != nil {
return nil
}
for _, item := range items {
if item.WarrantyExpire == nil {
continue
}
daysLeft := int(item.WarrantyExpire.Sub(now).Hours() / 24)
msg := fmt.Sprintf("%s warranty expires in %d days", item.Name, daysLeft)
link := fmt.Sprintf("/inventory?gear=%s", item.ID)
if err := n.notificationService.CreateNotification(item.UserID, "gear_warranty", "Warranty expiring soon", msg, link); err != nil {
n.logger.Warn("failed to send warranty notification", zap.String("gear_id", item.ID.String()), zap.Error(err))
}
}
if len(items) > 0 {
n.logger.Info("Gear warranty notifications sent", zap.Int("count", len(items)))
}
return nil
}