feat: set timezone space level
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m25s
All checks were successful
Deploy / build-and-deploy (push) Successful in 2m25s
This commit is contained in:
parent
945069052f
commit
08f6b034f5
12 changed files with 196 additions and 23 deletions
|
|
@ -37,14 +37,16 @@ type RecurringDepositService struct {
|
|||
accountRepo repository.MoneyAccountRepository
|
||||
expenseService *ExpenseService
|
||||
profileRepo repository.ProfileRepository
|
||||
spaceRepo repository.SpaceRepository
|
||||
}
|
||||
|
||||
func NewRecurringDepositService(recurringRepo repository.RecurringDepositRepository, accountRepo repository.MoneyAccountRepository, expenseService *ExpenseService, profileRepo repository.ProfileRepository) *RecurringDepositService {
|
||||
func NewRecurringDepositService(recurringRepo repository.RecurringDepositRepository, accountRepo repository.MoneyAccountRepository, expenseService *ExpenseService, profileRepo repository.ProfileRepository, spaceRepo repository.SpaceRepository) *RecurringDepositService {
|
||||
return &RecurringDepositService{
|
||||
recurringRepo: recurringRepo,
|
||||
accountRepo: accountRepo,
|
||||
expenseService: expenseService,
|
||||
profileRepo: profileRepo,
|
||||
spaceRepo: spaceRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,8 +167,8 @@ func (s *RecurringDepositService) ProcessDueRecurrences(now time.Time) error {
|
|||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, rd := range dues {
|
||||
userNow := s.getUserNow(rd.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(rd, userNow); err != nil {
|
||||
localNow := s.getLocalNow(rd.SpaceID, rd.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(rd, localNow); err != nil {
|
||||
slog.Error("failed to process recurring deposit", "id", rd.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -181,16 +183,32 @@ func (s *RecurringDepositService) ProcessDueRecurrencesForSpace(spaceID string,
|
|||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, rd := range dues {
|
||||
userNow := s.getUserNow(rd.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(rd, userNow); err != nil {
|
||||
localNow := s.getLocalNow(rd.SpaceID, rd.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(rd, localNow); 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 {
|
||||
// getLocalNow resolves the effective timezone for a recurring deposit.
|
||||
// Resolution order: space timezone → user profile timezone → UTC.
|
||||
func (s *RecurringDepositService) getLocalNow(spaceID, userID string, now time.Time, cache map[string]*time.Location) time.Time {
|
||||
spaceKey := "space:" + spaceID
|
||||
if loc, ok := cache[spaceKey]; ok {
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
space, err := s.spaceRepo.ByID(spaceID)
|
||||
if err == nil && space != nil {
|
||||
if loc := space.Location(); loc != nil {
|
||||
cache[spaceKey] = loc
|
||||
return now.In(loc)
|
||||
}
|
||||
}
|
||||
|
||||
userKey := "user:" + userID
|
||||
if loc, ok := cache[userKey]; ok {
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +217,7 @@ func (s *RecurringDepositService) getUserNow(userID string, now time.Time, cache
|
|||
if err == nil && profile != nil {
|
||||
loc = profile.Location()
|
||||
}
|
||||
cache[userID] = loc
|
||||
cache[userKey] = loc
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,15 @@ type RecurringExpenseService struct {
|
|||
recurringRepo repository.RecurringExpenseRepository
|
||||
expenseRepo repository.ExpenseRepository
|
||||
profileRepo repository.ProfileRepository
|
||||
spaceRepo repository.SpaceRepository
|
||||
}
|
||||
|
||||
func NewRecurringExpenseService(recurringRepo repository.RecurringExpenseRepository, expenseRepo repository.ExpenseRepository, profileRepo repository.ProfileRepository) *RecurringExpenseService {
|
||||
func NewRecurringExpenseService(recurringRepo repository.RecurringExpenseRepository, expenseRepo repository.ExpenseRepository, profileRepo repository.ProfileRepository, spaceRepo repository.SpaceRepository) *RecurringExpenseService {
|
||||
return &RecurringExpenseService{
|
||||
recurringRepo: recurringRepo,
|
||||
expenseRepo: expenseRepo,
|
||||
profileRepo: profileRepo,
|
||||
spaceRepo: spaceRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +182,8 @@ func (s *RecurringExpenseService) ProcessDueRecurrences(now time.Time) error {
|
|||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, re := range dues {
|
||||
userNow := s.getUserNow(re.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(re, userNow); err != nil {
|
||||
localNow := s.getLocalNow(re.SpaceID, re.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(re, localNow); err != nil {
|
||||
slog.Error("failed to process recurring expense", "id", re.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -196,8 +198,8 @@ func (s *RecurringExpenseService) ProcessDueRecurrencesForSpace(spaceID string,
|
|||
|
||||
tzCache := make(map[string]*time.Location)
|
||||
for _, re := range dues {
|
||||
userNow := s.getUserNow(re.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(re, userNow); err != nil {
|
||||
localNow := s.getLocalNow(re.SpaceID, re.CreatedBy, now, tzCache)
|
||||
if err := s.processRecurrence(re, localNow); err != nil {
|
||||
slog.Error("failed to process recurring expense", "id", re.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -253,10 +255,24 @@ 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 {
|
||||
// getLocalNow resolves the effective timezone for a recurring expense.
|
||||
// Resolution order: space timezone → user profile timezone → UTC.
|
||||
func (s *RecurringExpenseService) getLocalNow(spaceID, userID string, now time.Time, cache map[string]*time.Location) time.Time {
|
||||
spaceKey := "space:" + spaceID
|
||||
if loc, ok := cache[spaceKey]; ok {
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
space, err := s.spaceRepo.ByID(spaceID)
|
||||
if err == nil && space != nil {
|
||||
if loc := space.Location(); loc != nil {
|
||||
cache[spaceKey] = loc
|
||||
return now.In(loc)
|
||||
}
|
||||
}
|
||||
|
||||
userKey := "user:" + userID
|
||||
if loc, ok := cache[userKey]; ok {
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +281,7 @@ func (s *RecurringExpenseService) getUserNow(userID string, now time.Time, cache
|
|||
if err == nil && profile != nil {
|
||||
loc = profile.Location()
|
||||
}
|
||||
cache[userID] = loc
|
||||
cache[userKey] = loc
|
||||
return now.In(loc)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,3 +110,11 @@ func (s *SpaceService) UpdateSpaceName(spaceID, name string) error {
|
|||
}
|
||||
return s.spaceRepo.UpdateName(spaceID, name)
|
||||
}
|
||||
|
||||
// UpdateSpaceTimezone updates the timezone of a space.
|
||||
func (s *SpaceService) UpdateSpaceTimezone(spaceID, timezone string) error {
|
||||
if _, err := time.LoadLocation(timezone); err != nil {
|
||||
return ErrInvalidTimezone
|
||||
}
|
||||
return s.spaceRepo.UpdateTimezone(spaceID, timezone)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue