Advanced search
This commit is contained in:
@@ -82,6 +82,8 @@ type Tender struct {
|
|||||||
SourceFileName string `bson:"source_file_name" json:"source_file_name"`
|
SourceFileName string `bson:"source_file_name" json:"source_file_name"`
|
||||||
ContentXML string `bson:"content_xml,omitempty" json:"content_xml,omitempty"`
|
ContentXML string `bson:"content_xml,omitempty" json:"content_xml,omitempty"`
|
||||||
ProcessingMetadata ProcessingMetadata `bson:"processing_metadata" json:"processing_metadata"`
|
ProcessingMetadata ProcessingMetadata `bson:"processing_metadata" json:"processing_metadata"`
|
||||||
|
// AIOverallSummary optional denormalized overall summary for search/list (e.g. synced from AI pipeline storage).
|
||||||
|
AIOverallSummary string `bson:"ai_overall_summary,omitempty" json:"ai_overall_summary,omitempty"`
|
||||||
TenderID string `bson:"tender_id" json:"tender_id"` // PBL Project Number (SCDYYNNN format)
|
TenderID string `bson:"tender_id" json:"tender_id"` // PBL Project Number (SCDYYNNN format)
|
||||||
ProjectName string `bson:"project_name" json:"project_name"` // PBL Project Name (<InternalProjectNumber>_<Client-OpportunityName>_<PartnerCode>)
|
ProjectName string `bson:"project_name" json:"project_name"` // PBL Project Name (<InternalProjectNumber>_<Client-OpportunityName>_<PartnerCode>)
|
||||||
|
|
||||||
|
|||||||
@@ -16,21 +16,41 @@ type UpdateTenderRequest struct {
|
|||||||
// SearchForm represents a request to list tenders with pagination and filtering
|
// SearchForm represents a request to list tenders with pagination and filtering
|
||||||
type SearchForm struct {
|
type SearchForm struct {
|
||||||
Search *string `query:"q" valid:"optional"`
|
Search *string `query:"q" valid:"optional"`
|
||||||
|
Title *string `query:"title" valid:"optional"`
|
||||||
|
Description *string `query:"description" valid:"optional"`
|
||||||
NoticeType *string `query:"notice_type" valid:"optional"`
|
NoticeType *string `query:"notice_type" valid:"optional"`
|
||||||
|
NoticeTypes []string `query:"notice_types" valid:"optional"`
|
||||||
|
FormTypes []string `query:"form_types" valid:"optional"`
|
||||||
ProcurementType *string `query:"procurement_type" valid:"optional"`
|
ProcurementType *string `query:"procurement_type" valid:"optional"`
|
||||||
|
Country *string `query:"country" valid:"optional"`
|
||||||
|
CountryCode *string `query:"country_code" valid:"optional"`
|
||||||
CountryCodes []string `query:"country_codes" valid:"optional"`
|
CountryCodes []string `query:"country_codes" valid:"optional"`
|
||||||
RegionCodes []string `query:"region_codes" valid:"optional"`
|
RegionCodes []string `query:"region_codes" valid:"optional"`
|
||||||
CompanyID *string `query:"company_id" valid:"optional"`
|
CompanyID *string `query:"company_id" valid:"optional"`
|
||||||
CustomerID *string `query:"customer_id" valid:"optional"`
|
CustomerID *string `query:"customer_id" valid:"optional"`
|
||||||
Status []string `query:"status" valid:"optional"`
|
Status []string `query:"status" valid:"optional"`
|
||||||
|
MainClassification *string `query:"main_classification" valid:"optional"`
|
||||||
Classifications []string `query:"classifications" valid:"optional"`
|
Classifications []string `query:"classifications" valid:"optional"`
|
||||||
|
CpvCodes []string `query:"cpv_codes" valid:"optional"`
|
||||||
MinEstimatedValue *float64 `query:"min_estimated_value" valid:"optional"`
|
MinEstimatedValue *float64 `query:"min_estimated_value" valid:"optional"`
|
||||||
MaxEstimatedValue *float64 `query:"max_estimated_value" valid:"optional"`
|
MaxEstimatedValue *float64 `query:"max_estimated_value" valid:"optional"`
|
||||||
Currency string `query:"currency" valid:"optional"`
|
Currency string `query:"currency" valid:"optional"`
|
||||||
|
CreatedAt *int64 `query:"created_at" valid:"optional"`
|
||||||
|
CreatedAtFrom *int64 `query:"created_at_from" valid:"optional"`
|
||||||
|
CreatedAtTo *int64 `query:"created_at_to" valid:"optional"`
|
||||||
|
TenderDeadline *int64 `query:"tender_deadline" valid:"optional"`
|
||||||
|
TenderDeadlineFrom *int64 `query:"tender_deadline_from" valid:"optional"`
|
||||||
|
TenderDeadlineTo *int64 `query:"tender_deadline_to" valid:"optional"`
|
||||||
DeadlineFrom *int64 `query:"deadline_from" valid:"optional"`
|
DeadlineFrom *int64 `query:"deadline_from" valid:"optional"`
|
||||||
DeadlineTo *int64 `query:"deadline_to" valid:"optional"`
|
DeadlineTo *int64 `query:"deadline_to" valid:"optional"`
|
||||||
|
PublicationDate *int64 `query:"publication_date" valid:"optional"`
|
||||||
PublicationDateFrom *int64 `query:"publication_date_from" valid:"optional"`
|
PublicationDateFrom *int64 `query:"publication_date_from" valid:"optional"`
|
||||||
PublicationDateTo *int64 `query:"publication_date_to" valid:"optional"`
|
PublicationDateTo *int64 `query:"publication_date_to" valid:"optional"`
|
||||||
|
SubmissionDeadline *int64 `query:"submission_deadline" valid:"optional"`
|
||||||
|
SubmissionDateAliasFrom *int64 `query:"submission_deadline_from" valid:"optional"`
|
||||||
|
SubmissionDateAliasTo *int64 `query:"submission_deadline_to" valid:"optional"`
|
||||||
|
SubmissionDateFrom *int64 `query:"submission_date_from" valid:"optional"`
|
||||||
|
SubmissionDateTo *int64 `query:"submission_date_to" valid:"optional"`
|
||||||
Source []string `query:"source" valid:"optional"`
|
Source []string `query:"source" valid:"optional"`
|
||||||
BuyerOrganizationID *string `query:"buyer_organization_id" valid:"optional"`
|
BuyerOrganizationID *string `query:"buyer_organization_id" valid:"optional"`
|
||||||
Languages []string `query:"languages" valid:"optional"`
|
Languages []string `query:"languages" valid:"optional"`
|
||||||
@@ -48,10 +68,12 @@ type SearchResponse struct {
|
|||||||
type TenderResponse struct {
|
type TenderResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
NoticePublicationID string `json:"notice_publication_id"`
|
NoticePublicationID string `json:"notice_publication_id"`
|
||||||
|
NoticeTypeCode string `json:"notice_type_code"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
ProcurementTypeCode string `json:"procurement_type_code"`
|
ProcurementTypeCode string `json:"procurement_type_code"`
|
||||||
ProcedureCode string `json:"procedure_code"`
|
ProcedureCode string `json:"procedure_code"`
|
||||||
|
MainClassification string `json:"main_classification"`
|
||||||
EstimatedValue float64 `json:"estimated_value"`
|
EstimatedValue float64 `json:"estimated_value"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Duration string `json:"duration"`
|
Duration string `json:"duration"`
|
||||||
@@ -115,10 +137,12 @@ func (t *Tender) ToResponseWithLanguage(language string) *TenderResponse {
|
|||||||
response := &TenderResponse{
|
response := &TenderResponse{
|
||||||
ID: t.ID.Hex(),
|
ID: t.ID.Hex(),
|
||||||
NoticePublicationID: t.NoticePublicationID,
|
NoticePublicationID: t.NoticePublicationID,
|
||||||
|
NoticeTypeCode: t.NoticeTypeCode,
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: description,
|
Description: description,
|
||||||
ProcurementTypeCode: t.ProcurementTypeCode,
|
ProcurementTypeCode: t.ProcurementTypeCode,
|
||||||
ProcedureCode: t.ProcedureCode,
|
ProcedureCode: t.ProcedureCode,
|
||||||
|
MainClassification: t.MainClassification,
|
||||||
EstimatedValue: t.EstimatedValue,
|
EstimatedValue: t.EstimatedValue,
|
||||||
Currency: t.Currency,
|
Currency: t.Currency,
|
||||||
Duration: t.Duration,
|
Duration: t.Duration,
|
||||||
|
|||||||
+48
-21
@@ -131,19 +131,28 @@ func (h *TenderHandler) Delete(c echo.Context) error {
|
|||||||
// @Tags Admin-Tenders
|
// @Tags Admin-Tenders
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param company_id query string false "Company ID for match percentage calculation and sorting"
|
// @Param company_id query string false "Company ID for match percentage calculation and sorting"
|
||||||
// @Param q query string false "Search query for title and description"
|
// @Param q query string false "Keyword search across title, description, per-document AI summaries (document_summaries), and optional stored overall summary (ai_overall_summary)"
|
||||||
// @Param notice_type query string false "Filter by notice type code"
|
// @Param notice_type query string false "Filter by TED notice type code (BT-02, notice_type_code)"
|
||||||
|
// @Param notice_types query []string false "Filter by multiple notice type codes (notice_type_code)"
|
||||||
|
// @Param form_types query []string false "Filter by TED form type / listName (e.g. competition, result, planning, contract-award, prior-information)"
|
||||||
// @Param procurement_type query string false "Filter by procurement type code"
|
// @Param procurement_type query string false "Filter by procurement type code"
|
||||||
// @Param country_codes query []string false "Filter by country codes (comma-separated)"
|
// @Param country query string false "Filter by a single ISO country code"
|
||||||
|
// @Param country_codes query []string false "Filter by country codes (repeat param or comma-separated)"
|
||||||
// @Param status query []string false "Filter by status (comma-separated)"
|
// @Param status query []string false "Filter by status (comma-separated)"
|
||||||
// @Param source query []string false "Filter by source (comma-separated)"
|
// @Param source query []string false "Filter by source (comma-separated)"
|
||||||
|
// @Param classifications query []string false "Filter by CPV / classification codes (main, additional, or procurement lot CPVs)"
|
||||||
|
// @Param cpv_codes query []string false "Alias for classifications: one or more CPV codes"
|
||||||
// @Param min_estimated_value query number false "Minimum estimated value"
|
// @Param min_estimated_value query number false "Minimum estimated value"
|
||||||
// @Param max_estimated_value query number false "Maximum estimated value"
|
// @Param max_estimated_value query number false "Maximum estimated value"
|
||||||
// @Param currency query string false "Filter by currency"
|
// @Param currency query string false "Filter by currency"
|
||||||
// @Param deadline_from query number false "Deadline from"
|
// @Param created_at_from query number false "Created at lower bound (Unix timestamp, seconds)"
|
||||||
// @Param deadline_to query number false "Deadline to"
|
// @Param created_at_to query number false "Created at upper bound (Unix timestamp, seconds)"
|
||||||
// @Param publication_date_from query number false "Publication date from"
|
// @Param deadline_from query number false "Tender deadline from (Unix timestamp, seconds)"
|
||||||
// @Param publication_date_to query number false "Publication date to"
|
// @Param deadline_to query number false "Tender deadline to (Unix timestamp, seconds)"
|
||||||
|
// @Param publication_date_from query number false "Publication date from (Unix timestamp, seconds)"
|
||||||
|
// @Param publication_date_to query number false "Publication date to (Unix timestamp, seconds)"
|
||||||
|
// @Param submission_date_from query number false "Submission deadline from (Unix timestamp, seconds)"
|
||||||
|
// @Param submission_date_to query number false "Submission deadline to (Unix timestamp, seconds)"
|
||||||
// @Param languages query []string false "Filter by languages (comma-separated)"
|
// @Param languages query []string false "Filter by languages (comma-separated)"
|
||||||
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
||||||
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
||||||
@@ -184,19 +193,28 @@ func (h *TenderHandler) Search(c echo.Context) error {
|
|||||||
// @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile
|
// @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile
|
||||||
// @Tags Tenders
|
// @Tags Tenders
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param q query string false "Search query for title and description"
|
// @Param q query string false "Keyword search across title, description, AI document summaries, optional stored overall summary"
|
||||||
// @Param notice_type query string false "Filter by notice type code"
|
// @Param notice_type query string false "Filter by TED notice type code (notice_type_code)"
|
||||||
|
// @Param notice_types query []string false "Multiple notice type codes"
|
||||||
|
// @Param form_types query []string false "TED form types: competition, result, planning, contract-award, prior-information, etc."
|
||||||
// @Param procurement_type query string false "Filter by procurement type code"
|
// @Param procurement_type query string false "Filter by procurement type code"
|
||||||
// @Param country_codes query []string false "Filter by country codes (comma-separated)"
|
// @Param country query string false "Single ISO country code"
|
||||||
|
// @Param country_codes query []string false "Filter by country codes"
|
||||||
// @Param status query []string false "Filter by status (comma-separated)"
|
// @Param status query []string false "Filter by status (comma-separated)"
|
||||||
// @Param source query []string false "Filter by source (comma-separated)"
|
// @Param source query []string false "Filter by source (comma-separated)"
|
||||||
|
// @Param classifications query []string false "CPV / classification codes"
|
||||||
|
// @Param cpv_codes query []string false "One or more CPV codes (same as classifications)"
|
||||||
// @Param min_estimated_value query number false "Minimum estimated value"
|
// @Param min_estimated_value query number false "Minimum estimated value"
|
||||||
// @Param max_estimated_value query number false "Maximum estimated value"
|
// @Param max_estimated_value query number false "Maximum estimated value"
|
||||||
// @Param currency query string false "Filter by currency"
|
// @Param currency query string false "Filter by currency"
|
||||||
// @Param deadline_from query number false "Deadline from"
|
// @Param created_at_from query number false "Created at from (Unix seconds)"
|
||||||
// @Param deadline_to query number false "Deadline to"
|
// @Param created_at_to query number false "Created at to (Unix seconds)"
|
||||||
// @Param publication_date_from query number false "Publication date from"
|
// @Param deadline_from query number false "Tender deadline from (Unix seconds)"
|
||||||
// @Param publication_date_to query number false "Publication date to"
|
// @Param deadline_to query number false "Tender deadline to (Unix seconds)"
|
||||||
|
// @Param publication_date_from query number false "Publication date from (Unix seconds)"
|
||||||
|
// @Param publication_date_to query number false "Publication date to (Unix seconds)"
|
||||||
|
// @Param submission_date_from query number false "Submission deadline from (Unix seconds)"
|
||||||
|
// @Param submission_date_to query number false "Submission deadline to (Unix seconds)"
|
||||||
// @Param languages query []string false "Filter by languages (comma-separated)"
|
// @Param languages query []string false "Filter by languages (comma-separated)"
|
||||||
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
||||||
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
||||||
@@ -248,19 +266,28 @@ func (h *TenderHandler) GetPublicTenders(c echo.Context) error {
|
|||||||
// @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile
|
// @Description Retrieve active tenders for mobile application users with automatic company-based matching from customer profile
|
||||||
// @Tags Tenders
|
// @Tags Tenders
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param q query string false "Search query for title and description"
|
// @Param q query string false "Keyword search across title, description, AI document summaries, optional stored overall summary"
|
||||||
// @Param notice_type query string false "Filter by notice type code"
|
// @Param notice_type query string false "Filter by TED notice type code (notice_type_code)"
|
||||||
|
// @Param notice_types query []string false "Multiple notice type codes"
|
||||||
|
// @Param form_types query []string false "TED form types: competition, result, planning, contract-award, prior-information, etc."
|
||||||
// @Param procurement_type query string false "Filter by procurement type code"
|
// @Param procurement_type query string false "Filter by procurement type code"
|
||||||
// @Param country_codes query []string false "Filter by country codes (comma-separated)"
|
// @Param country query string false "Single ISO country code"
|
||||||
|
// @Param country_codes query []string false "Filter by country codes"
|
||||||
// @Param status query []string false "Filter by status (comma-separated)"
|
// @Param status query []string false "Filter by status (comma-separated)"
|
||||||
// @Param source query []string false "Filter by source (comma-separated)"
|
// @Param source query []string false "Filter by source (comma-separated)"
|
||||||
|
// @Param classifications query []string false "CPV / classification codes"
|
||||||
|
// @Param cpv_codes query []string false "One or more CPV codes"
|
||||||
// @Param min_estimated_value query number false "Minimum estimated value"
|
// @Param min_estimated_value query number false "Minimum estimated value"
|
||||||
// @Param max_estimated_value query number false "Maximum estimated value"
|
// @Param max_estimated_value query number false "Maximum estimated value"
|
||||||
// @Param currency query string false "Filter by currency"
|
// @Param currency query string false "Filter by currency"
|
||||||
// @Param deadline_from query number false "Deadline from"
|
// @Param created_at_from query number false "Created at from (Unix seconds)"
|
||||||
// @Param deadline_to query number false "Deadline to"
|
// @Param created_at_to query number false "Created at to (Unix seconds)"
|
||||||
// @Param publication_date_from query number false "Publication date from"
|
// @Param deadline_from query number false "Tender deadline from (Unix seconds)"
|
||||||
// @Param publication_date_to query number false "Publication date to"
|
// @Param deadline_to query number false "Tender deadline to (Unix seconds)"
|
||||||
|
// @Param publication_date_from query number false "Publication date from (Unix seconds)"
|
||||||
|
// @Param publication_date_to query number false "Publication date to (Unix seconds)"
|
||||||
|
// @Param submission_date_from query number false "Submission deadline from (Unix seconds)"
|
||||||
|
// @Param submission_date_to query number false "Submission deadline to (Unix seconds)"
|
||||||
// @Param languages query []string false "Filter by languages (comma-separated)"
|
// @Param languages query []string false "Filter by languages (comma-separated)"
|
||||||
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
// @Param buyer_organization_id query string false "Filter by buyer organization ID"
|
||||||
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
// @Param limit query int false "Number of items per page (default: 20, max: 100)"
|
||||||
|
|||||||
+207
-23
@@ -2,6 +2,8 @@ package tender
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
orm "tm/pkg/mongo"
|
orm "tm/pkg/mongo"
|
||||||
@@ -54,6 +56,7 @@ func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Te
|
|||||||
*orm.NewIndex("source_idx", bson.D{{Key: "source", Value: 1}}),
|
*orm.NewIndex("source_idx", bson.D{{Key: "source", Value: 1}}),
|
||||||
*orm.NewIndex("country_code_idx", bson.D{{Key: "country_code", Value: 1}}),
|
*orm.NewIndex("country_code_idx", bson.D{{Key: "country_code", Value: 1}}),
|
||||||
*orm.NewIndex("notice_type_code_idx", bson.D{{Key: "notice_type_code", Value: 1}}),
|
*orm.NewIndex("notice_type_code_idx", bson.D{{Key: "notice_type_code", Value: 1}}),
|
||||||
|
*orm.NewIndex("form_type_idx", bson.D{{Key: "form_type", Value: 1}}),
|
||||||
*orm.NewIndex("procurement_type_code_idx", bson.D{{Key: "procurement_type_code", Value: 1}}),
|
*orm.NewIndex("procurement_type_code_idx", bson.D{{Key: "procurement_type_code", Value: 1}}),
|
||||||
*orm.NewIndex("main_classification_idx", bson.D{{Key: "main_classification", Value: 1}}),
|
*orm.NewIndex("main_classification_idx", bson.D{{Key: "main_classification", Value: 1}}),
|
||||||
*orm.NewIndex("publication_date_idx", bson.D{{Key: "publication_date", Value: -1}}),
|
*orm.NewIndex("publication_date_idx", bson.D{{Key: "publication_date", Value: -1}}),
|
||||||
@@ -77,7 +80,7 @@ func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Te
|
|||||||
{Key: "additional_classifications", Value: 1},
|
{Key: "additional_classifications", Value: 1},
|
||||||
{Key: "publication_date", Value: -1},
|
{Key: "publication_date", Value: -1},
|
||||||
}),
|
}),
|
||||||
// Text index for search
|
// Text index (legacy; list search uses $regex across title, description, and AI summary fields for unified matching)
|
||||||
*orm.CreateTextIndex("search_idx", "title", "description"),
|
*orm.CreateTextIndex("search_idx", "title", "description"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,37 +552,176 @@ func (r *tenderRepository) GetTenderCountByClassification(ctx context.Context) (
|
|||||||
return counts, nil
|
return counts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dedupeNonEmptyStrings(values []string) []string {
|
||||||
|
seen := make(map[string]struct{}, len(values))
|
||||||
|
out := make([]string, 0, len(values))
|
||||||
|
for _, v := range values {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[v]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[v] = struct{}{}
|
||||||
|
out = append(out, v)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeUnixTimestamp accepts seconds or milliseconds and always returns seconds.
|
||||||
|
func normalizeUnixTimestamp(value int64) int64 {
|
||||||
|
// 1e12 is safely above current Unix seconds and in millisecond range for modern dates.
|
||||||
|
if value > 1_000_000_000_000 {
|
||||||
|
return value / 1000
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func exactOrRange(exact, from, to *int64) (*int64, *int64) {
|
||||||
|
if exact != nil {
|
||||||
|
v := normalizeUnixTimestamp(*exact)
|
||||||
|
return &v, &v
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedFrom *int64
|
||||||
|
if from != nil {
|
||||||
|
v := normalizeUnixTimestamp(*from)
|
||||||
|
normalizedFrom = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedTo *int64
|
||||||
|
if to != nil {
|
||||||
|
v := normalizeUnixTimestamp(*to)
|
||||||
|
normalizedTo = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedFrom, normalizedTo
|
||||||
|
}
|
||||||
|
|
||||||
// buildSearchFilter builds MongoDB filter from search form
|
// buildSearchFilter builds MongoDB filter from search form
|
||||||
func (r *tenderRepository) buildSearchFilter(form *SearchForm) bson.M {
|
func (r *tenderRepository) buildSearchFilter(form *SearchForm) bson.M {
|
||||||
filter := bson.M{}
|
filter := bson.M{}
|
||||||
|
|
||||||
|
var keywordOr []bson.M
|
||||||
if form.Search != nil {
|
if form.Search != nil {
|
||||||
filter["$text"] = bson.M{"$search": *form.Search}
|
q := strings.TrimSpace(*form.Search)
|
||||||
|
if q != "" {
|
||||||
|
pattern := regexp.QuoteMeta(q)
|
||||||
|
keywordOr = []bson.M{
|
||||||
|
{"title": bson.M{"$regex": pattern, "$options": "i"}},
|
||||||
|
{"description": bson.M{"$regex": pattern, "$options": "i"}},
|
||||||
|
{"document_summaries.summary": bson.M{"$regex": pattern, "$options": "i"}},
|
||||||
|
{"ai_overall_summary": bson.M{"$regex": pattern, "$options": "i"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if form.Title != nil {
|
||||||
|
title := strings.TrimSpace(*form.Title)
|
||||||
|
if title != "" {
|
||||||
|
filter["title"] = bson.M{
|
||||||
|
"$regex": regexp.QuoteMeta(title),
|
||||||
|
"$options": "i",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if form.Description != nil {
|
||||||
|
description := strings.TrimSpace(*form.Description)
|
||||||
|
if description != "" {
|
||||||
|
filter["description"] = bson.M{
|
||||||
|
"$regex": regexp.QuoteMeta(description),
|
||||||
|
"$options": "i",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noticeCodes []string
|
||||||
if form.NoticeType != nil {
|
if form.NoticeType != nil {
|
||||||
filter["notice_type_code"] = form.NoticeType
|
if t := strings.TrimSpace(*form.NoticeType); t != "" {
|
||||||
|
noticeCodes = append(noticeCodes, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noticeCodes = append(noticeCodes, form.NoticeTypes...)
|
||||||
|
noticeCodes = dedupeNonEmptyStrings(noticeCodes)
|
||||||
|
if len(noticeCodes) == 1 {
|
||||||
|
filter["notice_type_code"] = noticeCodes[0]
|
||||||
|
} else if len(noticeCodes) > 1 {
|
||||||
|
filter["notice_type_code"] = bson.M{"$in": noticeCodes}
|
||||||
|
}
|
||||||
|
|
||||||
|
formTypes := dedupeNonEmptyStrings(form.FormTypes)
|
||||||
|
if len(formTypes) == 1 {
|
||||||
|
filter["form_type"] = formTypes[0]
|
||||||
|
} else if len(formTypes) > 1 {
|
||||||
|
filter["form_type"] = bson.M{"$in": formTypes}
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.ProcurementType != nil {
|
if form.ProcurementType != nil {
|
||||||
filter["procurement_type_code"] = form.ProcurementType
|
filter["procurement_type_code"] = form.ProcurementType
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.CountryCodes) > 0 {
|
countryCodes := dedupeNonEmptyStrings(form.CountryCodes)
|
||||||
filter["country_code"] = bson.M{"$in": form.CountryCodes}
|
if form.CountryCode != nil {
|
||||||
|
if c := strings.TrimSpace(*form.CountryCode); c != "" {
|
||||||
|
countryCodes = append(countryCodes, c)
|
||||||
|
countryCodes = dedupeNonEmptyStrings(countryCodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if form.Country != nil {
|
||||||
|
if c := strings.TrimSpace(*form.Country); c != "" {
|
||||||
|
countryCodes = append(countryCodes, c)
|
||||||
|
countryCodes = dedupeNonEmptyStrings(countryCodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(countryCodes) == 1 {
|
||||||
|
filter["country_code"] = countryCodes[0]
|
||||||
|
} else if len(countryCodes) > 1 {
|
||||||
|
filter["country_code"] = bson.M{"$in": countryCodes}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.RegionCodes) > 0 {
|
if len(form.RegionCodes) > 0 {
|
||||||
filter["region_code"] = bson.M{"$in": form.RegionCodes}
|
filter["region_code"] = bson.M{"$in": dedupeNonEmptyStrings(form.RegionCodes)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Classifications) > 0 {
|
if form.MainClassification != nil {
|
||||||
filter["$or"] = []bson.M{
|
if mainClassification := strings.TrimSpace(*form.MainClassification); mainClassification != "" {
|
||||||
{"main_classification": bson.M{"$in": form.Classifications}},
|
filter["main_classification"] = mainClassification
|
||||||
{"additional_classifications": bson.M{"$in": form.Classifications}},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpvCodes := dedupeNonEmptyStrings(form.Classifications)
|
||||||
|
cpvCodes = append(cpvCodes, dedupeNonEmptyStrings(form.CpvCodes)...)
|
||||||
|
cpvCodes = dedupeNonEmptyStrings(cpvCodes)
|
||||||
|
var cpvOr []bson.M
|
||||||
|
if len(cpvCodes) > 0 {
|
||||||
|
cpvOr = []bson.M{
|
||||||
|
{"main_classification": bson.M{"$in": cpvCodes}},
|
||||||
|
{"additional_classifications": bson.M{"$in": cpvCodes}},
|
||||||
|
{
|
||||||
|
"procurement_lots": bson.M{
|
||||||
|
"$elemMatch": bson.M{
|
||||||
|
"$or": []bson.M{
|
||||||
|
{"main_classification": bson.M{"$in": cpvCodes}},
|
||||||
|
{"additional_classifications": bson.M{"$in": cpvCodes}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(keywordOr) > 0 && len(cpvOr) > 0:
|
||||||
|
filter["$and"] = []bson.M{
|
||||||
|
{"$or": keywordOr},
|
||||||
|
{"$or": cpvOr},
|
||||||
|
}
|
||||||
|
case len(keywordOr) > 0:
|
||||||
|
filter["$or"] = keywordOr
|
||||||
|
case len(cpvOr) > 0:
|
||||||
|
filter["$or"] = cpvOr
|
||||||
|
}
|
||||||
|
|
||||||
if form.MinEstimatedValue != nil || form.MaxEstimatedValue != nil {
|
if form.MinEstimatedValue != nil || form.MaxEstimatedValue != nil {
|
||||||
valueFilter := bson.M{}
|
valueFilter := bson.M{}
|
||||||
if form.MinEstimatedValue != nil {
|
if form.MinEstimatedValue != nil {
|
||||||
@@ -595,37 +737,79 @@ func (r *tenderRepository) buildSearchFilter(form *SearchForm) bson.M {
|
|||||||
filter["currency"] = form.Currency
|
filter["currency"] = form.Currency
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.DeadlineFrom != nil || form.DeadlineTo != nil || form.OnlyActiveDeadlines {
|
createdAtFrom, createdAtTo := exactOrRange(form.CreatedAt, form.CreatedAtFrom, form.CreatedAtTo)
|
||||||
|
if createdAtFrom != nil || createdAtTo != nil {
|
||||||
|
createdFilter := bson.M{}
|
||||||
|
if createdAtFrom != nil {
|
||||||
|
createdFilter["$gte"] = *createdAtFrom
|
||||||
|
}
|
||||||
|
if createdAtTo != nil {
|
||||||
|
createdFilter["$lte"] = *createdAtTo
|
||||||
|
}
|
||||||
|
filter["created_at"] = createdFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
deadlineFrom := form.DeadlineFrom
|
||||||
|
if form.TenderDeadlineFrom != nil {
|
||||||
|
deadlineFrom = form.TenderDeadlineFrom
|
||||||
|
}
|
||||||
|
deadlineTo := form.DeadlineTo
|
||||||
|
if form.TenderDeadlineTo != nil {
|
||||||
|
deadlineTo = form.TenderDeadlineTo
|
||||||
|
}
|
||||||
|
tenderDeadlineFrom, tenderDeadlineTo := exactOrRange(form.TenderDeadline, deadlineFrom, deadlineTo)
|
||||||
|
if tenderDeadlineFrom != nil || tenderDeadlineTo != nil || form.OnlyActiveDeadlines {
|
||||||
deadlineFilter := bson.M{}
|
deadlineFilter := bson.M{}
|
||||||
if form.OnlyActiveDeadlines {
|
if form.OnlyActiveDeadlines {
|
||||||
deadlineFilter["$gt"] = time.Now().Unix()
|
deadlineFilter["$gt"] = time.Now().Unix()
|
||||||
}
|
}
|
||||||
if form.DeadlineFrom != nil {
|
if tenderDeadlineFrom != nil {
|
||||||
deadlineFilter["$gte"] = *form.DeadlineFrom
|
deadlineFilter["$gte"] = *tenderDeadlineFrom
|
||||||
}
|
}
|
||||||
if form.DeadlineTo != nil {
|
if tenderDeadlineTo != nil {
|
||||||
deadlineFilter["$lte"] = *form.DeadlineTo
|
deadlineFilter["$lte"] = *tenderDeadlineTo
|
||||||
}
|
}
|
||||||
filter["tender_deadline"] = deadlineFilter
|
filter["tender_deadline"] = deadlineFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.PublicationDateFrom != nil || form.PublicationDateTo != nil {
|
publicationDateFrom, publicationDateTo := exactOrRange(form.PublicationDate, form.PublicationDateFrom, form.PublicationDateTo)
|
||||||
|
if publicationDateFrom != nil || publicationDateTo != nil {
|
||||||
pubDateFilter := bson.M{}
|
pubDateFilter := bson.M{}
|
||||||
if form.PublicationDateFrom != nil {
|
if publicationDateFrom != nil {
|
||||||
pubDateFilter["$gte"] = *form.PublicationDateFrom
|
pubDateFilter["$gte"] = *publicationDateFrom
|
||||||
}
|
}
|
||||||
if form.PublicationDateTo != nil {
|
if publicationDateTo != nil {
|
||||||
pubDateFilter["$lte"] = *form.PublicationDateTo
|
pubDateFilter["$lte"] = *publicationDateTo
|
||||||
}
|
}
|
||||||
filter["publication_date"] = pubDateFilter
|
filter["publication_date"] = pubDateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submissionDateFrom := form.SubmissionDateFrom
|
||||||
|
if form.SubmissionDateAliasFrom != nil {
|
||||||
|
submissionDateFrom = form.SubmissionDateAliasFrom
|
||||||
|
}
|
||||||
|
submissionDateTo := form.SubmissionDateTo
|
||||||
|
if form.SubmissionDateAliasTo != nil {
|
||||||
|
submissionDateTo = form.SubmissionDateAliasTo
|
||||||
|
}
|
||||||
|
submissionDeadlineFrom, submissionDeadlineTo := exactOrRange(form.SubmissionDeadline, submissionDateFrom, submissionDateTo)
|
||||||
|
if submissionDeadlineFrom != nil || submissionDeadlineTo != nil {
|
||||||
|
subFilter := bson.M{}
|
||||||
|
if submissionDeadlineFrom != nil {
|
||||||
|
subFilter["$gte"] = *submissionDeadlineFrom
|
||||||
|
}
|
||||||
|
if submissionDeadlineTo != nil {
|
||||||
|
subFilter["$lte"] = *submissionDeadlineTo
|
||||||
|
}
|
||||||
|
filter["submission_deadline"] = subFilter
|
||||||
|
}
|
||||||
|
|
||||||
if len(form.Status) > 0 {
|
if len(form.Status) > 0 {
|
||||||
filter["status"] = bson.M{"$in": form.Status}
|
filter["status"] = bson.M{"$in": dedupeNonEmptyStrings(form.Status)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Source) > 0 {
|
if len(form.Source) > 0 {
|
||||||
filter["source"] = bson.M{"$in": form.Source}
|
filter["source"] = bson.M{"$in": dedupeNonEmptyStrings(form.Source)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.BuyerOrganizationID != nil {
|
if form.BuyerOrganizationID != nil {
|
||||||
@@ -633,7 +817,7 @@ func (r *tenderRepository) buildSearchFilter(form *SearchForm) bson.M {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Languages) > 0 {
|
if len(form.Languages) > 0 {
|
||||||
filter["notice_language_code"] = bson.M{"$in": form.Languages}
|
filter["notice_language_code"] = bson.M{"$in": dedupeNonEmptyStrings(form.Languages)}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter
|
return filter
|
||||||
|
|||||||
Reference in New Issue
Block a user