Enhance Tender entity with PublicationSubmissionDeadline method and corresponding tests
continuous-integration/drone/push Build is passing

- Added the PublicationSubmissionDeadline method to the Tender entity, which calculates the submission deadline based on the publication date and stored submission deadline.
- Updated the IsRecommendable method to utilize the new PublicationSubmissionDeadline logic for determining recommendation eligibility.
- Introduced unit tests for the PublicationSubmissionDeadline method, ensuring accurate calculation and validation of submission deadlines.
- Enhanced existing tests for the IsRecommendable method to cover new scenarios related to submission deadlines.

This update improves the accuracy of submission deadline handling and enhances the recommendation logic for tenders based on publication dates.
This commit is contained in:
Mazyar
2026-06-30 18:12:55 +03:30
parent ff6bdfcb09
commit 20ce9c53ff
3 changed files with 61 additions and 15 deletions
+18 -2
View File
@@ -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
+42 -12
View File
@@ -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,
},
}
+1 -1
View File
@@ -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)"