From 21843a7b86020f0e6ef9fba65e986ebe0b4b6e9c Mon Sep 17 00:00:00 2001 From: furu04 Date: Wed, 22 Apr 2026 18:05:41 +0900 Subject: [PATCH] =?UTF-8?q?=E7=B9=B0=E3=82=8A=E8=BF=94=E3=81=97=E8=AA=B2?= =?UTF-8?q?=E9=A1=8C=E3=81=8C2=E5=9B=9E=E7=9B=AE=E4=BB=A5=E9=99=8D?= =?UTF-8?q?=E7=94=9F=E6=88=90=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GenerationLeadDays=0のとき生成タイミングガードをスキップし、 前回分完了後すぐに次回課題を生成するよう修正。 --- .../service/recurring_assignment_service.go | 118 ++++++++++++++---- 1 file changed, 95 insertions(+), 23 deletions(-) diff --git a/internal/service/recurring_assignment_service.go b/internal/service/recurring_assignment_service.go index 19270cb..7d9d5f7 100644 --- a/internal/service/recurring_assignment_service.go +++ b/internal/service/recurring_assignment_service.go @@ -16,6 +16,7 @@ var ( ErrRecurringUnauthorized = errors.New("unauthorized") ErrInvalidRecurrenceType = errors.New("invalid recurrence type") ErrInvalidEndType = errors.New("invalid end type") + ErrLeadDaysTooLarge = errors.New("generation_lead_days must be less than the recurrence interval") ) type RecurringAssignmentService struct { @@ -47,6 +48,8 @@ type CreateRecurringAssignmentInput struct { ReminderEnabled bool ReminderOffset *int UrgentReminderEnabled bool + GenerationLeadDays int + GenerationLeadTime string FirstDueDate time.Time } @@ -62,6 +65,13 @@ func (s *RecurringAssignmentService) Create(userID uint, input CreateRecurringAs if input.RecurrenceInterval < 1 { input.RecurrenceInterval = 1 } + + if input.GenerationLeadDays > 0 { + maxLead := maxGenerationLeadDays(input.RecurrenceType, input.RecurrenceInterval) + if input.GenerationLeadDays > maxLead { + return nil, ErrLeadDaysTooLarge + } + } if input.EditBehavior == "" { input.EditBehavior = models.EditBehaviorThisOnly } @@ -84,6 +94,8 @@ func (s *RecurringAssignmentService) Create(userID uint, input CreateRecurringAs ReminderEnabled: input.ReminderEnabled, ReminderOffset: input.ReminderOffset, UrgentReminderEnabled: input.UrgentReminderEnabled, + GenerationLeadDays: input.GenerationLeadDays, + GenerationLeadTime: input.GenerationLeadTime, IsActive: true, GeneratedCount: 0, } @@ -137,6 +149,8 @@ type UpdateRecurringInput struct { ReminderEnabled *bool ReminderOffset *int UrgentReminderEnabled *bool + GenerationLeadDays *int + GenerationLeadTime *string } func (s *RecurringAssignmentService) Update(userID, recurringID uint, input UpdateRecurringInput) (*models.RecurringAssignment, error) { @@ -195,6 +209,18 @@ func (s *RecurringAssignmentService) Update(userID, recurringID uint, input Upda if input.EndDate != nil { recurring.EndDate = input.EndDate } + if input.GenerationLeadDays != nil && *input.GenerationLeadDays >= 0 { + if *input.GenerationLeadDays > 0 { + maxLead := maxGenerationLeadDays(recurring.RecurrenceType, recurring.RecurrenceInterval) + if *input.GenerationLeadDays > maxLead { + return nil, ErrLeadDaysTooLarge + } + } + recurring.GenerationLeadDays = *input.GenerationLeadDays + } + if input.GenerationLeadTime != nil { + recurring.GenerationLeadTime = *input.GenerationLeadTime + } if err := s.recurringRepo.Update(recurring); err != nil { return nil, err @@ -366,33 +392,62 @@ func (s *RecurringAssignmentService) GenerateNextAssignments() error { } for _, recurring := range recurrings { - pendingCount, err := s.recurringRepo.CountPendingByRecurringID(recurring.ID) - if err != nil { + if err := s.generateNextIfPending(&recurring); err != nil { continue } - - if pendingCount == 0 { - latest, err := s.recurringRepo.GetLatestAssignmentByRecurringID(recurring.ID) - if err != nil { - continue - } - - var nextDueDate time.Time - if latest != nil { - nextDueDate = recurring.CalculateNextDueDate(latest.DueDate) - } else { - nextDueDate = time.Now() - } - - if nextDueDate.After(time.Now()) { - s.generateAssignment(&recurring, nextDueDate) - } - } } return nil } +func (s *RecurringAssignmentService) TriggerForRecurring(recurringID uint) error { + recurring, err := s.recurringRepo.FindByID(recurringID) + if err != nil { + return nil + } + return s.generateNextIfPending(recurring) +} + +func (s *RecurringAssignmentService) generateNextIfPending(recurring *models.RecurringAssignment) error { + if !recurring.ShouldGenerateNext() { + return nil + } + + pendingCount, err := s.recurringRepo.CountPendingByRecurringID(recurring.ID) + if err != nil || pendingCount > 0 { + return err + } + + latest, err := s.recurringRepo.GetLatestAssignmentByRecurringID(recurring.ID) + if err != nil { + return err + } + if latest == nil { + return nil + } + + nextDueDate := recurring.CalculateNextDueDate(latest.DueDate) + + if recurring.GenerationLeadDays > 0 { + generationAt := nextDueDate.AddDate(0, 0, -recurring.GenerationLeadDays) + if recurring.GenerationLeadTime != "" { + parts := strings.Split(recurring.GenerationLeadTime, ":") + if len(parts) == 2 { + hour, _ := strconv.Atoi(parts[0]) + min, _ := strconv.Atoi(parts[1]) + generationAt = time.Date(generationAt.Year(), generationAt.Month(), generationAt.Day(), hour, min, 0, 0, generationAt.Location()) + } + } else { + generationAt = time.Date(generationAt.Year(), generationAt.Month(), generationAt.Day(), 0, 0, 0, 0, generationAt.Location()) + } + if time.Now().Before(generationAt) { + return nil + } + } + + return s.generateAssignment(recurring, nextDueDate) +} + func (s *RecurringAssignmentService) generateAssignment(recurring *models.RecurringAssignment, dueDate time.Time) error { if recurring.DueTime != "" { parts := strings.Split(recurring.DueTime, ":") @@ -403,6 +458,14 @@ func (s *RecurringAssignmentService) generateAssignment(recurring *models.Recurr } } + existing, err := s.assignmentRepo.FindByRecurringAndDue(recurring.ID, dueDate) + if err != nil { + return err + } + if existing != nil { + return nil + } + var reminderAt *time.Time if recurring.ReminderEnabled && recurring.ReminderOffset != nil { t := dueDate.Add(-time.Duration(*recurring.ReminderOffset) * time.Minute) @@ -410,7 +473,7 @@ func (s *RecurringAssignmentService) generateAssignment(recurring *models.Recurr } assignment := &models.Assignment{ - UserID: userID(recurring.UserID), + UserID: recurring.UserID, Title: recurring.Title, Description: recurring.Description, Subject: recurring.Subject, @@ -430,8 +493,17 @@ func (s *RecurringAssignmentService) generateAssignment(recurring *models.Recurr return s.recurringRepo.Update(recurring) } -func userID(id uint) uint { - return id + +func maxGenerationLeadDays(recurrenceType string, interval int) int { + switch recurrenceType { + case models.RecurrenceDaily: + return interval + case models.RecurrenceWeekly: + return interval * 7 + case models.RecurrenceMonthly: + return interval * 28 + } + return 0 } func isValidRecurrenceType(t string) bool {