diff --git a/internal/tender/entity.go b/internal/tender/entity.go index 37b34aa..a344bc4 100644 --- a/internal/tender/entity.go +++ b/internal/tender/entity.go @@ -5,7 +5,9 @@ import ( "strconv" "strings" "time" + "tm/pkg/mongo" + "tm/ted" ) // DocumentSummary represents a summary of a tender document @@ -266,13 +268,27 @@ func (t *Tender) IsExpired() bool { return t.TenderDeadline < now } -// IsRecommendable reports whether a tender should appear in AI recommendation results. +// PublicationSubmissionDeadline returns the submission window end derived from publication_date. +// Stored submission_deadline is preferred; otherwise it is calculated as 48 working hours after publication. +func (t *Tender) PublicationSubmissionDeadline() int64 { + if t.SubmissionDeadline > 0 { + return t.SubmissionDeadline + } + if t.PublicationDate <= 0 { + return 0 + } + return ted.CalculateSubmissionDeadline(time.Unix(t.PublicationDate, 0)).Unix() +} + +// IsRecommendable reports whether a tender should appear in customer AI recommendation results. +// Recommendations use the publication-based submission window, not tender_deadline. func (t *Tender) IsRecommendable(now int64) bool { switch t.Status { case TenderStatusExpired, TenderStatusCancelled, TenderStatusAwarded, TenderStatusClosed: return false } - if t.TenderDeadline <= now { + deadline := t.PublicationSubmissionDeadline() + if deadline <= 0 || deadline <= now { return false } return true diff --git a/internal/tender/entity_test.go b/internal/tender/entity_test.go index 6e8e571..a9996f1 100644 --- a/internal/tender/entity_test.go +++ b/internal/tender/entity_test.go @@ -73,6 +73,21 @@ func TestResolvedEstimatedValueAndCurrencyPrefersTopLevel(t *testing.T) { } } +func TestTenderPublicationSubmissionDeadline(t *testing.T) { + pubDate := int64(1_700_000_000) + tender := Tender{PublicationDate: pubDate} + + got := tender.PublicationSubmissionDeadline() + if got <= pubDate { + t.Fatalf("expected submission deadline after publication_date, got %d (publication_date=%d)", got, pubDate) + } + + withStored := Tender{PublicationDate: pubDate, SubmissionDeadline: pubDate + 999} + if deadline := withStored.PublicationSubmissionDeadline(); deadline != pubDate+999 { + t.Fatalf("expected stored submission_deadline, got %d", deadline) + } +} + func TestTenderIsRecommendable(t *testing.T) { now := int64(1_700_000_000) @@ -82,33 +97,48 @@ func TestTenderIsRecommendable(t *testing.T) { want bool }{ { - name: "active with future deadline", - tender: Tender{Status: TenderStatusActive, TenderDeadline: now + 3600}, + name: "active with future submission deadline", + tender: Tender{Status: TenderStatusActive, SubmissionDeadline: now + 3600}, want: true, }, { - name: "active with past deadline", - tender: Tender{Status: TenderStatusActive, TenderDeadline: now - 3600}, + name: "active with past submission deadline", + tender: Tender{Status: TenderStatusActive, SubmissionDeadline: now - 3600}, want: false, }, { - name: "active with deadline at now", - tender: Tender{Status: TenderStatusActive, TenderDeadline: now}, + name: "active with future tender deadline but past submission deadline", + tender: Tender{Status: TenderStatusActive, TenderDeadline: now + 86400, SubmissionDeadline: now - 3600}, want: false, }, { - name: "active without deadline", - tender: Tender{Status: TenderStatusActive, TenderDeadline: 0}, + name: "active with future tender deadline only", + tender: Tender{Status: TenderStatusActive, TenderDeadline: now + 86400}, want: false, }, { - name: "expired status with future deadline", - tender: Tender{Status: TenderStatusExpired, TenderDeadline: now + 3600}, + name: "active with submission deadline at now", + tender: Tender{Status: TenderStatusActive, SubmissionDeadline: now}, want: false, }, { - name: "awarded with future deadline", - tender: Tender{Status: TenderStatusAwarded, TenderDeadline: now + 3600}, + name: "active without publication or submission deadline", + tender: Tender{Status: TenderStatusActive}, + want: false, + }, + { + name: "active with publication date only and open submission window", + tender: Tender{Status: TenderStatusActive, PublicationDate: now - 3600}, + want: true, + }, + { + name: "expired status with future submission deadline", + tender: Tender{Status: TenderStatusExpired, SubmissionDeadline: now + 3600}, + want: false, + }, + { + name: "awarded with future submission deadline", + tender: Tender{Status: TenderStatusAwarded, SubmissionDeadline: now + 3600}, want: false, }, } diff --git a/internal/tender/handler.go b/internal/tender/handler.go index b4212ae..6c5dbeb 100644 --- a/internal/tender/handler.go +++ b/internal/tender/handler.go @@ -326,7 +326,7 @@ func (h *TenderHandler) GetPublicTenders(c echo.Context) error { // RecommendTenders returns AI-ranked tender recommendations for the authenticated customer's company // @Summary Get recommended tenders -// @Description Retrieve AI-ranked tenders with full tender details. AI tender_id values use PROC_{contract_folder_id}/{notice_publication_id}; each item includes rank, analysis, procedure_ref, and the standard tender fields. +// @Description Retrieve AI-ranked tenders with full tender details. Only active tenders whose publication-based submission window has not passed are returned (submission_deadline, or 48 working hours after publication_date). AI tender_id values use PROC_{contract_folder_id}/{notice_publication_id}; each item includes rank, analysis, procedure_ref, and the standard tender fields. // @Tags Tenders // @Produce json // @Param limit query int false "Number of items per page (default: 10, max: 100)"