Enhance tender approval and submission handling
- Added a new error for submission sync failures, improving error handling in the tender approval process. - Updated the `ToggleTenderApproval` method to handle the new error, providing clearer responses for sync issues. - Introduced a method to reopen terminal submissions when tender approvals are resubmitted, enhancing submission state management. - Implemented optimistic locking in the repository to prevent concurrent updates, improving data integrity. - Added unit tests for new submission handling logic, ensuring robust functionality and error management. This update strengthens the tender approval workflow and submission handling, ensuring better error reporting and state management.
This commit is contained in:
@@ -19,6 +19,7 @@ type TenderApprovalReader interface {
|
||||
type Service interface {
|
||||
EnsureForApprovedTender(ctx context.Context, tenderID, companyID, customerID string) (*TenderSubmissionWithTenderResponse, error)
|
||||
GetByIDWithTender(ctx context.Context, id string) (*TenderSubmissionWithTenderResponse, error)
|
||||
GetByIDForCompanyWithTender(ctx context.Context, id, companyID string) (*TenderSubmissionWithTenderResponse, error)
|
||||
GetByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderSubmissionWithTenderResponse, error)
|
||||
ListByCompanyWithTender(ctx context.Context, companyID string, form *ListTenderSubmissionsForm) (*TenderSubmissionListResponse, error)
|
||||
UpdateStatus(ctx context.Context, id string, form *UpdateSubmissionStatusForm, companyID, changedBy string) (*TenderSubmissionWithTenderResponse, error)
|
||||
@@ -69,7 +70,13 @@ func (s *tenderSubmissionService) EnsureForApprovedTender(ctx context.Context, t
|
||||
}
|
||||
submission = NewTenderSubmission(resolvedTenderID, companyID, customerID)
|
||||
if err := s.repository.Create(ctx, submission); err != nil {
|
||||
return nil, err
|
||||
if !errors.Is(err, ErrTenderSubmissionExists) {
|
||||
return nil, err
|
||||
}
|
||||
submission, err = s.repository.GetByTenderAndCompany(ctx, resolvedTenderID, companyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +91,17 @@ func (s *tenderSubmissionService) GetByIDWithTender(ctx context.Context, id stri
|
||||
return s.responseWithTender(ctx, submission, true)
|
||||
}
|
||||
|
||||
func (s *tenderSubmissionService) GetByIDForCompanyWithTender(ctx context.Context, id, companyID string) (*TenderSubmissionWithTenderResponse, error) {
|
||||
submission, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if submission.CompanyID != companyID {
|
||||
return nil, ErrTenderSubmissionNotFound
|
||||
}
|
||||
return s.responseWithTender(ctx, submission, true)
|
||||
}
|
||||
|
||||
func (s *tenderSubmissionService) GetByTenderAndCompanyWithTender(ctx context.Context, tenderID, companyID string) (*TenderSubmissionWithTenderResponse, error) {
|
||||
resolvedTenderID, err := s.tenderService.ResolveMongoID(ctx, tenderID)
|
||||
if err != nil {
|
||||
@@ -153,6 +171,7 @@ func (s *tenderSubmissionService) UpdateStatus(ctx context.Context, id string, f
|
||||
return nil, ErrTenderSubmissionNotFound
|
||||
}
|
||||
|
||||
expectedUpdatedAt := submission.UpdatedAt
|
||||
if err := s.validateStatusTransition(submission.Status, form.Status); err != nil {
|
||||
s.logger.Error("Invalid tender submission status transition", map[string]interface{}{
|
||||
"submission_id": id,
|
||||
@@ -164,7 +183,10 @@ func (s *tenderSubmissionService) UpdateStatus(ctx context.Context, id string, f
|
||||
}
|
||||
|
||||
submission.AddStatusChange(form.Status, form.Reason, changedBy, form.Description)
|
||||
if err := s.repository.Update(ctx, submission); err != nil {
|
||||
if err := s.repository.UpdateIfUpdatedAt(ctx, submission, expectedUpdatedAt); err != nil {
|
||||
if errors.Is(err, ErrConcurrentUpdate) {
|
||||
return nil, ErrConcurrentUpdate
|
||||
}
|
||||
return nil, errors.New("failed to update tender submission status")
|
||||
}
|
||||
|
||||
@@ -206,11 +228,26 @@ func (s *tenderSubmissionService) OnApprovalSubmitted(ctx context.Context, tende
|
||||
return err
|
||||
}
|
||||
if existing != nil {
|
||||
if IsTerminalStatus(existing.Status) {
|
||||
existing.ReopenForApproval(customerID, "Tender re-approved")
|
||||
return s.repository.Update(ctx, existing)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
submission := NewTenderSubmission(tenderID, companyID, customerID)
|
||||
if err := s.repository.Create(ctx, submission); err != nil {
|
||||
if errors.Is(err, ErrTenderSubmissionExists) {
|
||||
existing, getErr := s.repository.GetByTenderAndCompany(ctx, tenderID, companyID)
|
||||
if getErr != nil {
|
||||
return getErr
|
||||
}
|
||||
if IsTerminalStatus(existing.Status) {
|
||||
existing.ReopenForApproval(customerID, "Tender re-approved")
|
||||
return s.repository.Update(ctx, existing)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
s.logger.Error("Failed to create submission on approval", map[string]interface{}{
|
||||
"tender_id": tenderID,
|
||||
"company_id": companyID,
|
||||
@@ -262,10 +299,30 @@ func (s *tenderSubmissionService) OnApprovalRejected(ctx context.Context, tender
|
||||
return nil
|
||||
}
|
||||
|
||||
submission.AddStatusChange(StatusNotApplied, "approval_rejected", changedBy, "Tender approval was rejected")
|
||||
if err := s.transitionSubmission(submission, StatusNotApplied, "approval_rejected", changedBy, "Tender approval was rejected"); err != nil {
|
||||
var transitionErr *InvalidStatusTransitionError
|
||||
if errors.Is(err, ErrTerminalStatus) || errors.As(err, &transitionErr) {
|
||||
s.logger.Warn("Skipping approval rejection sync due to invalid transition", map[string]interface{}{
|
||||
"tender_id": tenderID,
|
||||
"company_id": companyID,
|
||||
"current_status": submission.Status,
|
||||
"target_status": StatusNotApplied,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return s.repository.Update(ctx, submission)
|
||||
}
|
||||
|
||||
func (s *tenderSubmissionService) transitionSubmission(submission *TenderSubmission, next SubmissionStatus, reason, changedBy, description string) error {
|
||||
if err := s.validateStatusTransition(submission.Status, next); err != nil {
|
||||
return err
|
||||
}
|
||||
submission.AddStatusChange(next, reason, changedBy, description)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tenderSubmissionService) requireSubmittedApproval(ctx context.Context, tenderID, companyID string) error {
|
||||
approval, err := s.approvalReader.GetByTenderAndCompany(ctx, tenderID, companyID)
|
||||
if err != nil {
|
||||
@@ -330,6 +387,9 @@ func listParams(form *ListTenderSubmissionsForm) (limit, offset int, sortBy, sor
|
||||
if form.Limit != nil && *form.Limit > 0 {
|
||||
limit = *form.Limit
|
||||
}
|
||||
if limit > maxListLimit {
|
||||
limit = maxListLimit
|
||||
}
|
||||
if form.Offset != nil && *form.Offset >= 0 {
|
||||
offset = *form.Offset
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user