feat: set timezone user level
This commit is contained in:
parent
ad0989510a
commit
945069052f
11 changed files with 261 additions and 18 deletions
|
|
@ -1,10 +1,15 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||
"git.juancwu.dev/juancwu/budgit/internal/repository"
|
||||
)
|
||||
|
||||
var ErrInvalidTimezone = errors.New("invalid timezone")
|
||||
|
||||
type ProfileService struct {
|
||||
profileRepository repository.ProfileRepository
|
||||
}
|
||||
|
|
@ -18,3 +23,10 @@ func NewProfileService(profileRepository repository.ProfileRepository) *ProfileS
|
|||
func (s *ProfileService) ByUserID(userID string) (*model.Profile, error) {
|
||||
return s.profileRepository.ByUserID(userID)
|
||||
}
|
||||
|
||||
func (s *ProfileService) UpdateTimezone(userID, timezone string) error {
|
||||
if _, err := time.LoadLocation(timezone); err != nil {
|
||||
return ErrInvalidTimezone
|
||||
}
|
||||
return s.profileRepository.UpdateTimezone(userID, timezone)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,13 +36,15 @@ type RecurringDepositService struct {
|
|||
recurringRepo repository.RecurringDepositRepository
|
||||
accountRepo repository.MoneyAccountRepository
|
||||
expenseService *ExpenseService
|
||||
profileRepo repository.ProfileRepository
|
||||
}
|
||||
|
||||
func NewRecurringDepositService(recurringRepo repository.RecurringDepositRepository, accountRepo repository.MoneyAccountRepository, expenseService *ExpenseService) *RecurringDepositService {
|
||||
func NewRecurringDepositService(recurringRepo repository.RecurringDepositRepository, accountRepo repository.MoneyAccountRepository, expenseService *ExpenseService, profileRepo repository.ProfileRepository) *RecurringDepositService {
|
||||
return &RecurringDepositService{
|
||||
recurringRepo: recurringRepo,
|
||||
accountRepo: accountRepo,
|
||||
expenseService: expenseService,
|
||||
profileRepo: profileRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,8 +163,10 @@ func (s *RecurringDepositService) ProcessDueRecurrences(now time.Time) error {
|
|||
return fmt.Errorf("failed to get due recurring deposits: %w", err)
|
||||
}
|
||||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, rd := range dues {
|
||||
if err := s.processRecurrence(rd, now); err != nil {
|
||||
userNow := s.getUserNow(rd.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(rd, userNow); err != nil {
|
||||
slog.Error("failed to process recurring deposit", "id", rd.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -175,14 +179,30 @@ func (s *RecurringDepositService) ProcessDueRecurrencesForSpace(spaceID string,
|
|||
return fmt.Errorf("failed to get due recurring deposits for space: %w", err)
|
||||
}
|
||||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, rd := range dues {
|
||||
if err := s.processRecurrence(rd, now); err != nil {
|
||||
userNow := s.getUserNow(rd.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(rd, userNow); err != nil {
|
||||
slog.Error("failed to process recurring deposit", "id", rd.ID, "error", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RecurringDepositService) getUserNow(userID string, now time.Time, cache map[string]*time.Location) time.Time {
|
||||
if loc, ok := cache[userID]; ok {
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
loc := time.UTC
|
||||
profile, err := s.profileRepo.ByUserID(userID)
|
||||
if err == nil && profile != nil {
|
||||
loc = profile.Location()
|
||||
}
|
||||
cache[userID] = loc
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
func (s *RecurringDepositService) getAvailableBalance(spaceID string) (int, error) {
|
||||
totalBalance, err := s.expenseService.GetBalanceForSpace(spaceID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,14 @@ type UpdateRecurringExpenseDTO struct {
|
|||
type RecurringExpenseService struct {
|
||||
recurringRepo repository.RecurringExpenseRepository
|
||||
expenseRepo repository.ExpenseRepository
|
||||
profileRepo repository.ProfileRepository
|
||||
}
|
||||
|
||||
func NewRecurringExpenseService(recurringRepo repository.RecurringExpenseRepository, expenseRepo repository.ExpenseRepository) *RecurringExpenseService {
|
||||
func NewRecurringExpenseService(recurringRepo repository.RecurringExpenseRepository, expenseRepo repository.ExpenseRepository, profileRepo repository.ProfileRepository) *RecurringExpenseService {
|
||||
return &RecurringExpenseService{
|
||||
recurringRepo: recurringRepo,
|
||||
expenseRepo: expenseRepo,
|
||||
profileRepo: profileRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,8 +178,10 @@ func (s *RecurringExpenseService) ProcessDueRecurrences(now time.Time) error {
|
|||
return fmt.Errorf("failed to get due recurrences: %w", err)
|
||||
}
|
||||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, re := range dues {
|
||||
if err := s.processRecurrence(re, now); err != nil {
|
||||
userNow := s.getUserNow(re.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(re, userNow); err != nil {
|
||||
slog.Error("failed to process recurring expense", "id", re.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -190,8 +194,10 @@ func (s *RecurringExpenseService) ProcessDueRecurrencesForSpace(spaceID string,
|
|||
return fmt.Errorf("failed to get due recurrences for space: %w", err)
|
||||
}
|
||||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, re := range dues {
|
||||
if err := s.processRecurrence(re, now); err != nil {
|
||||
userNow := s.getUserNow(re.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(re, userNow); err != nil {
|
||||
slog.Error("failed to process recurring expense", "id", re.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -247,6 +253,22 @@ func (s *RecurringExpenseService) processRecurrence(re *model.RecurringExpense,
|
|||
return s.recurringRepo.UpdateNextOccurrence(re.ID, re.NextOccurrence)
|
||||
}
|
||||
|
||||
// getUserNow converts the server time to the user's local time based on their
|
||||
// profile timezone setting. Falls back to UTC if no timezone is set.
|
||||
func (s *RecurringExpenseService) getUserNow(userID string, now time.Time, cache map[string]*time.Location) time.Time {
|
||||
if loc, ok := cache[userID]; ok {
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
loc := time.UTC
|
||||
profile, err := s.profileRepo.ByUserID(userID)
|
||||
if err == nil && profile != nil {
|
||||
loc = profile.Location()
|
||||
}
|
||||
cache[userID] = loc
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
func AdvanceDate(date time.Time, freq model.Frequency) time.Time {
|
||||
switch freq {
|
||||
case model.FrequencyDaily:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue