Implement AI recommendations for multiple companies and enhance company context handling
continuous-integration/drone/push Build is passing

- Added functionality to retrieve merged AI recommendations for multiple companies, improving the relevance of tender suggestions based on company-specific data.
- Introduced normalization functions to clean and deduplicate company IDs, ensuring accurate processing of recommendations.
- Enhanced the company context resolution in customer middleware to support multiple assigned companies, improving the handling of company-specific requests.
- Updated the tender recommendation logic to utilize the new merged recommendations and handle exclusions for rejected tenders accordingly.
- Added unit tests to verify the new recommendation merging logic and company ID normalization, ensuring robust functionality.

This update significantly enhances the tender recommendation process by allowing for more comprehensive and relevant suggestions based on multiple company contexts, improving user experience and satisfaction.
This commit is contained in:
Mazyar
2026-07-04 13:00:33 +03:30
parent f8c98da132
commit 1edf42187d
12 changed files with 280 additions and 22 deletions
+34 -4
View File
@@ -8,10 +8,28 @@ import (
var errCompanyNotAssigned = errors.New("company is not assigned to customer")
func normalizeAssignedCompanyIDs(assigned []string) []string {
normalized := make([]string, 0, len(assigned))
seen := make(map[string]struct{}, len(assigned))
for _, id := range assigned {
clean := strings.TrimSpace(id)
if clean == "" {
continue
}
if _, ok := seen[clean]; ok {
continue
}
seen[clean] = struct{}{}
normalized = append(normalized, clean)
}
return normalized
}
// pickActiveCompanyID chooses the company context for a customer request.
// When requestedCompanyID is set it must be in assigned; otherwise a still-valid
// token company is kept; if the token company was removed, the first assignment is used.
func pickActiveCompanyID(assigned []string, tokenCompanyID, requestedCompanyID string) (string, error) {
assigned = normalizeAssignedCompanyIDs(assigned)
if len(assigned) == 0 {
return "", nil
}
@@ -38,12 +56,24 @@ func pickActiveCompanyID(assigned []string, tokenCompanyID, requestedCompanyID s
return assigned[0], nil
}
// ResolveActiveCompanyID resolves the active company for an authenticated customer.
func (s *customerService) ResolveActiveCompanyID(ctx context.Context, customerID, tokenCompanyID, requestedCompanyID string) (string, error) {
// ResolveCompanyContext resolves the active company and assigned companies for an authenticated customer.
func (s *customerService) ResolveCompanyContext(ctx context.Context, customerID, tokenCompanyID, requestedCompanyID string) (string, []string, error) {
customer, err := s.repository.GetByID(ctx, customerID)
if err != nil {
return "", errors.New("customer not found")
return "", nil, errors.New("customer not found")
}
return pickActiveCompanyID(customer.Companies, tokenCompanyID, requestedCompanyID)
assigned := normalizeAssignedCompanyIDs(customer.Companies)
companyID, err := pickActiveCompanyID(assigned, tokenCompanyID, requestedCompanyID)
if err != nil {
return "", nil, err
}
return companyID, assigned, nil
}
// ResolveActiveCompanyID resolves the active company for an authenticated customer.
func (s *customerService) ResolveActiveCompanyID(ctx context.Context, customerID, tokenCompanyID, requestedCompanyID string) (string, error) {
companyID, _, err := s.ResolveCompanyContext(ctx, customerID, tokenCompanyID, requestedCompanyID)
return companyID, err
}
+2 -1
View File
@@ -76,7 +76,7 @@ func (h *Handler) CompanyContextMiddleware() echo.MiddlewareFunc {
tokenCompanyID, _ := c.Get("company_id").(string)
requestedCompanyID := strings.TrimSpace(c.QueryParam("company_id"))
companyID, err := h.service.ResolveActiveCompanyID(
companyID, companyIDs, err := h.service.ResolveCompanyContext(
c.Request().Context(),
customerID,
tokenCompanyID,
@@ -90,6 +90,7 @@ func (h *Handler) CompanyContextMiddleware() echo.MiddlewareFunc {
}
c.Set("company_id", companyID)
c.Set("company_ids", companyIDs)
return next(c)
}
}
+4
View File
@@ -54,6 +54,10 @@ type Service interface {
AssignRole(ctx context.Context, customerID string, form *AssignRoleForm) (*AssignRoleResponse, error)
AssignCompanies(ctx context.Context, customerID string, Companies []string) error
// ResolveCompanyContext returns the active company and all assigned companies
// for an authenticated customer request.
ResolveCompanyContext(ctx context.Context, customerID, tokenCompanyID, requestedCompanyID string) (string, []string, error)
// ResolveActiveCompanyID returns the company context for an authenticated customer.
ResolveActiveCompanyID(ctx context.Context, customerID, tokenCompanyID, requestedCompanyID string) (string, error)
}