Enhance tender and company context handling with new features and error management
continuous-integration/drone/push Build is passing

- Introduced `PickAssignedCompanyID` function to determine the appropriate company ID for company-scoped operations, improving company assignment logic.
- Added `ResolveMongoID` method in the tender service to map MongoDB IDs and AI procedure references to canonical tender IDs, enhancing ID resolution.
- Updated `ToggleTenderApproval` to handle company ID validation and improve error handling, ensuring robust processing of tender approvals.
- Enhanced logging for error scenarios in the tender approval service, providing better insights into failures during operations.
- Refactored the `GetByID` and `GetByTenderAndCompany` methods in the tender approval repository to utilize custom error types for improved clarity.

This update significantly improves the handling of company and tender operations, enhancing error management and overall service reliability.
This commit is contained in:
Mazyar
2026-07-08 02:55:55 +03:30
parent 11e0b44ebe
commit 81a94b4879
7 changed files with 137 additions and 28 deletions
+1
View File
@@ -81,6 +81,7 @@ type CompanyTenderApprovalStatsResponse struct {
// ToggleTenderApprovalForm represents the form for toggling a tender approval
type ToggleTenderApprovalForm struct {
TenderID string `json:"tender_id" valid:"required"`
CompanyID *string `json:"company_id,omitempty" valid:"optional"`
Status ApprovalStatus `json:"status" valid:"required,in(submitted|rejected)"`
SubmissionMode SubmissionMode `json:"submission_mode" valid:"required,in(self-apply|partnership)"`
}
+19 -4
View File
@@ -1,7 +1,10 @@
package tender_approval
import (
"errors"
"strconv"
"strings"
"tm/internal/customer"
"tm/pkg/logger"
"tm/pkg/response"
@@ -42,14 +45,26 @@ func (h *Handler) ToggleTenderApproval(c echo.Context) error {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get company ID from context
companyID := c.Get("company_id").(string)
if companyID == "" {
return response.Unauthorized(c, "Unauthorized")
requestedCompanyID := strings.TrimSpace(c.QueryParam("company_id"))
if form.CompanyID != nil && strings.TrimSpace(*form.CompanyID) != "" {
requestedCompanyID = strings.TrimSpace(*form.CompanyID)
}
activeCompanyID, _ := c.Get("company_id").(string)
assignedCompanyIDs, _ := c.Get("company_ids").([]string)
companyID, err := customer.PickAssignedCompanyID(activeCompanyID, assignedCompanyIDs, requestedCompanyID)
if err != nil {
if errors.Is(err, customer.ErrCompanyNotAssigned) {
return response.Forbidden(c, "Company is not assigned to customer")
}
return response.BadRequest(c, "Company ID is required", "")
}
tenderApproval, err := h.service.ToggleTenderApproval(c.Request().Context(), form, companyID)
if err != nil {
if errors.Is(err, ErrTenderNotFound) {
return response.NotFound(c, "Tender not found")
}
if err.Error() == "tender approval already exists for this tender and company" {
return response.Conflict(c, err.Error())
}
+2 -2
View File
@@ -104,7 +104,7 @@ func (r *tenderApprovalRepository) GetByID(ctx context.Context, id string) (*Ten
tenderApproval, err := r.ormRepo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
return nil, errors.New("tender approval not found")
return nil, ErrTenderApprovalNotFound
}
r.logger.Error("Failed to get tender approval by ID", map[string]interface{}{
"error": err.Error(),
@@ -126,7 +126,7 @@ func (r *tenderApprovalRepository) GetByTenderAndCompany(ctx context.Context, te
tenderApproval, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
return nil, errors.New("tender approval not found")
return nil, ErrTenderApprovalNotFound
}
r.logger.Error("Failed to get tender approval by tender and company", map[string]interface{}{
"error": err.Error(),
+50 -19
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"math"
"strings"
"time"
"tm/internal/tender"
"tm/pkg/logger"
@@ -853,10 +854,34 @@ func (s *tenderApprovalService) ListTenderApprovalsWithTender(ctx context.Contex
}
func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *ToggleTenderApprovalForm, companyID string) (*TenderApprovalWithTenderResponse, error) {
existing, err := s.repository.GetByTenderAndCompany(ctx, req.TenderID, companyID)
companyID = strings.TrimSpace(companyID)
if companyID == "" {
return nil, ErrInvalidCompanyID
}
tenderID, err := s.tenderService.ResolveMongoID(ctx, req.TenderID)
if err != nil {
s.logger.Warn("Failed to resolve tender for approval toggle", map[string]interface{}{
"tender_ref": req.TenderID,
"company_id": companyID,
"error": err.Error(),
})
return nil, ErrTenderNotFound
}
existing, err := s.repository.GetByTenderAndCompany(ctx, tenderID, companyID)
if err != nil {
if !errors.Is(err, ErrTenderApprovalNotFound) {
s.logger.Error("Failed to load tender approval for toggle", map[string]interface{}{
"tender_id": tenderID,
"company_id": companyID,
"error": err.Error(),
})
return nil, err
}
approval := &TenderApproval{
TenderID: req.TenderID,
TenderID: tenderID,
CompanyID: companyID,
SubmissionMode: req.SubmissionMode,
Status: req.Status,
@@ -865,7 +890,7 @@ func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *T
err = s.repository.Create(ctx, approval)
if err != nil {
s.logger.Error("Failed to create new tender approval", map[string]interface{}{
"tender_id": req.TenderID,
"tender_id": tenderID,
"company_id": companyID,
"status": req.Status,
"submission_mode": req.SubmissionMode,
@@ -874,13 +899,13 @@ func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *T
return nil, err
}
return approval.ToResponseWithTender(nil), nil
return s.toggleResponseWithTender(ctx, approval)
}
if existing.Status == req.Status {
err = s.repository.Delete(ctx, existing.GetID())
if err != nil {
s.logger.Error("Failed to update tender approval", map[string]interface{}{
s.logger.Error("Failed to delete tender approval during toggle", map[string]interface{}{
"error": err.Error(),
})
return nil, err
@@ -892,24 +917,17 @@ func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *T
existing.Status = "unrejected"
}
return existing.ToResponseWithTender(nil), nil
} else if existing.Status == req.Status && existing.SubmissionMode != req.SubmissionMode {
existing.SubmissionMode = req.SubmissionMode
err = s.repository.Update(ctx, existing)
if err != nil {
s.logger.Error("Failed to update tender approval", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
return existing.ToResponseWithTender(nil), nil
}
if existing.Status == ApprovalStatusSubmitted {
if existing.Status == ApprovalStatusRejected && req.Status == ApprovalStatusSubmitted {
existing.Submit()
existing.SubmissionMode = req.SubmissionMode
} else if existing.Status == ApprovalStatusSubmitted && req.Status == ApprovalStatusRejected {
existing.Reject()
} else {
existing.Submit()
existing.Status = req.Status
existing.SubmissionMode = req.SubmissionMode
}
err = s.repository.Update(ctx, existing)
@@ -920,7 +938,20 @@ func (s *tenderApprovalService) ToggleTenderApproval(ctx context.Context, req *T
return nil, err
}
return existing.ToResponseWithTender(nil), nil
return s.toggleResponseWithTender(ctx, existing)
}
func (s *tenderApprovalService) toggleResponseWithTender(ctx context.Context, approval *TenderApproval) (*TenderApprovalWithTenderResponse, error) {
if approval == nil {
return nil, ErrInvalidData
}
tenderResponse, err := s.tenderService.GetByID(ctx, approval.TenderID, "")
if err != nil {
return approval.ToResponseWithTender(nil), nil
}
return approval.ToResponseWithTender(listTenderDetailsFromResponse(tenderResponse)), nil
}
// GetCompanyTenderApprovalStats returns company-specific tender approval statistics