Refactor TED Calendar and EForm Structure for Enhanced Functionality

- Updated the GetOJS function to accept a base URL parameter, improving flexibility in constructing the calendar URL.
- Removed the deprecated eform.go file, streamlining the codebase by eliminating unused components.
- Introduced new eform structures for BusinessRegistrationInformationNotice, ContractNotice, ContractAwardNotice, and PriorInformationNotice, enhancing the handling of TED XML data.
- Implemented mapping functions to convert eform notices to the Tender entity, improving integration with the tender management system.
- Added a service layer to encapsulate business logic related to eform processing, adhering to Clean Architecture principles.
This commit is contained in:
n.nakhostin
2025-09-30 16:51:40 +03:30
parent 05c7eae8a2
commit 44f266189c
9 changed files with 574 additions and 336 deletions
@@ -0,0 +1,44 @@
package business_registration_information
import (
"encoding/xml"
"tm/internal/tender"
"tm/ted/eform"
)
// BusinessRegistrationInformationNotice represents the UBL BusinessRegistrationInformationNotice structure
type BusinessRegistrationInformationNotice struct {
XMLName xml.Name `xml:"BusinessRegistrationInformationNotice"`
UBLVersionID string `json:"ubl_version_id" xml:"UBLVersionID"`
CustomizationID string `json:"customization_id" xml:"CustomizationID"`
ID string `json:"id" xml:"ID"`
IssueDate string `json:"issue_date" xml:"IssueDate"`
IssueTime string `json:"issue_time,omitempty" xml:"IssueTime,omitempty"`
VersionID string `json:"version_id,omitempty" xml:"VersionID,omitempty"`
RegulatoryDomain string `json:"regulatory_domain,omitempty" xml:"RegulatoryDomain,omitempty"`
NoticeTypeCode string `json:"notice_type_code" xml:"NoticeTypeCode"`
NoticeLanguageCode string `json:"notice_language_code" xml:"NoticeLanguageCode"`
UBLExtensions eform.UBLExtensions `json:"ubl_extensions,omitempty" xml:"UBLExtensions,omitempty"`
AdditionalNoticeLanguage []eform.AdditionalNoticeLanguage `json:"additional_notice_language,omitempty" xml:"AdditionalNoticeLanguage,omitempty"`
SenderParty eform.SenderParty `json:"sender_party,omitempty" xml:"SenderParty,omitempty"`
BusinessParty eform.BusinessParty `json:"business_party" xml:"BusinessParty"`
AdditionalDocumentReference []eform.AdditionalDocumentReference `json:"additional_document_reference,omitempty" xml:"AdditionalDocumentReference,omitempty"`
BusinessCapability []eform.BusinessCapability `json:"business_capability,omitempty" xml:"BusinessCapability,omitempty"`
NoticePurpose eform.NoticePurpose `json:"notice_purpose,omitempty" xml:"NoticePurpose,omitempty"`
}
// GetID returns the business registration information notice ID
func (brin BusinessRegistrationInformationNotice) GetID() string {
return brin.ID
}
// Validate validates the parsed model
func (brin BusinessRegistrationInformationNotice) Validate() error {
return nil
}
func (notice BusinessRegistrationInformationNotice) MapToTender() *tender.Tender {
t := new(tender.Tender)
t.ContractNoticeID = notice.GetID()
return t
}
+213
View File
@@ -0,0 +1,213 @@
package contract
import (
"encoding/xml"
"time"
"tm/internal/tender"
"tm/ted/eform"
)
// Notice represents the UBL ContractNotice structure used by TED
type ContractNotice struct {
XMLName xml.Name `xml:"ContractNotice"`
UBLVersionID string `xml:"UBLVersionID"`
CustomizationID string `xml:"CustomizationID"`
ID string `xml:"ID"`
ContractFolderID string `xml:"ContractFolderID"`
IssueDate string `xml:"IssueDate"`
IssueTime string `xml:"IssueTime"`
VersionID string `xml:"VersionID"`
RegulatoryDomain string `xml:"RegulatoryDomain"`
NoticeTypeCode string `xml:"NoticeTypeCode"`
NoticeLanguageCode string `xml:"NoticeLanguageCode"`
UBLExtensions eform.UBLExtensions `xml:"UBLExtensions,omitempty"`
ContractingParty eform.ContractingParty `xml:"ContractingParty"`
TenderingTerms eform.TenderingTerms `xml:"TenderingTerms,omitempty"`
TenderingProcess eform.TenderingProcess `xml:"TenderingProcess,omitempty"`
ProcurementProject eform.ProcurementProject `xml:"ProcurementProject"`
ProcurementProjectLot []eform.ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"`
}
func (notice ContractNotice) MapToTender() *tender.Tender {
now := time.Now().Unix()
t := &tender.Tender{
ContractNoticeID: notice.ID,
ContractFolderID: notice.ContractFolderID,
NoticeTypeCode: notice.NoticeTypeCode,
// NoticeSubTypeCode: notice.NoticeSubTypeCode,
NoticeLanguageCode: notice.NoticeLanguageCode,
// IssueDate: parseDateToUnixMilli(notice.IssueDate),
// IssueTime: parseTimeToUnixMilli(notice.IssueTime),
// GazetteID: notice.GetOJSID(),
// Title: notice.GetProcurementTitle(),
// Description: notice.GetProcurementDescription(),
// ProcurementTypeCode: notice.GetContractNature(),
// ProcedureCode: notice.GetProcedureCode(),
// MainClassification: notice.GetMainClassification(),
// PlaceOfPerformance: notice.GetPlaceOfPerformance(),
// DocumentURI: notice.GetDocumentURI(),
Status: tender.TenderStatusActive,
Source: tender.TenderSourceTEDScraper,
ProcessingMetadata: tender.ProcessingMetadata{
ScrapedAt: now,
ProcessedAt: now,
ProcessingVersion: "1.0",
},
}
// Set publication information
// if pubInfo := notice.GetPublicationInfo(); pubInfo != nil {
// t.NoticePublicationID = pubInfo.NoticePublicationID
// t.PublicationDate = s.parseDateToUnixMilli(pubInfo.PublicationDate)
// t.TenderURL = s.generateTenderURL(pubInfo.NoticePublicationID)
// }
// Set additional classifications
// if notice.ProcurementProject != nil && notice.ProcurementProject.AdditionalCommodityClassification != nil {
// for _, additionalClass := range notice.ProcurementProject.AdditionalCommodityClassification {
// if additionalClass.ItemClassificationCode != "" {
// t.AdditionalClassifications = append(t.AdditionalClassifications, additionalClass.ItemClassificationCode)
// }
// }
// }
// Set estimated value and currency
// if amount, currency := notice.GetBudget(); amount != "" {
// if value, err := strconv.ParseFloat(amount, 64); err == nil {
// t.EstimatedValue = value
// t.Currency = currency
// }
// }
// Set duration information
// if duration, unit := notice.GetDuration(); duration != "" {
// t.Duration = duration
// t.DurationUnit = unit
// }
// Set tender deadline
// if deadline := notice.GetTenderDeadline(); deadline != "" {
// t.TenderDeadline = parseDateToUnixMilli(deadline)
// if deadlineTime, err := parseDate(deadline); err == nil {
// applicationDeadline := calculateApplicationDeadline(deadlineTime)
// t.ApplicationDeadline = applicationDeadline.UnixMilli()
// }
// }
// if pubDate, err := parseDate(notice.GetPublicationDate()); err == nil {
// submissionDeadline := calculateSubmissionDeadline(pubDate)
// t.SubmissionDeadline = submissionDeadline.UnixMilli()
// }
// Set SubmissionURL from TenderingTerms
// if notice.ProcurementProjectLot != nil && notice.ProcurementProjectLot.TenderingTerms != nil {
// if tenderingTerms := notice.ProcurementProjectLot.TenderingTerms; tenderingTerms.TenderRecipientParty != nil {
// if endpointID := tenderingTerms.TenderRecipientParty.EndpointID; endpointID != "" {
// if strings.HasPrefix(endpointID, "http") {
// t.SubmissionURL = endpointID
// } else {
// t.SubmissionURL = "https://" + endpointID
// }
// }
// }
// }
// Set location information
// if notice.ProcurementProject != nil && notice.ProcurementProject.RealizedLocation != nil {
// if addr := notice.ProcurementProject.RealizedLocation.Address; addr != nil {
// t.CityName = addr.CityName
// t.PostalCode = addr.PostalZone
// t.RegionCode = addr.CountrySubentityCode
// if addr.Country != nil {
// t.CountryCode = addr.Country.IdentificationCode
// }
// }
// }
// Set buyer organization
// buyerID := notice.GetBuyerID()
// if buyerID != "" {
// if buyerOrg := notice.GetOrganizationByID(buyerID); buyerOrg != nil {
// t.BuyerOrganization = s.mapOrganization(buyerOrg, "buyer")
// }
// }
// Set review organization
// reviewID := notice.GetReviewOrganizationID()
// if reviewID != "" {
// if reviewOrg := notice.GetOrganizationByID(reviewID); reviewOrg != nil {
// t.ReviewOrganization = s.mapOrganization(reviewOrg, "review")
// }
// }
// Set all organizations
// if orgs := notice.GetOrganizations(); orgs != nil {
// for _, org := range orgs.Organization {
// mappedOrg := s.mapOrganization(&org, "")
// if mappedOrg != nil {
// t.Organizations = append(t.Organizations, *mappedOrg)
// }
// }
// }
// Set selection criteria
// if criteria := notice.GetSelectionCriteria(); criteria != nil {
// for _, criterion := range criteria {
// t.SelectionCriteria = append(t.SelectionCriteria, tender.SelectionCriterion{
// TypeCode: criterion.CriterionTypeCode,
// Description: criterion.Description,
// LanguageID: criterion.LanguageID,
// })
// }
// }
// Set official languages
// if languages := notice.GetOfficialLanguages(); languages != nil {
// t.OfficialLanguages = languages
// }
// Generate unique tender ID
// t.TenderID = generateTenderID(t)
return t
}
// GetID returns the contract notice ID
func (cn ContractNotice) GetID() string {
return cn.ID
}
// Validate validates the parsed model
func (cn ContractNotice) Validate() error {
return nil
}
// GetMainBuyer returns the lead/main buyer organization
func (n *ContractNotice) GetMainBuyer() *eform.Organization {
if len(n.UBLExtensions.UBLExtension) > 0 {
orgs := n.UBLExtensions.UBLExtension[0].ExtensionContent.EformsExtension.Organizations.Organization
for i := range orgs {
for _, role := range orgs[i].Roles {
if role == eform.RoleBuyer || role == eform.RoleLeadBuyer {
return &orgs[i]
}
}
}
}
return nil
}
// GetOrganizationByID retrieves organization by ID
func (n *ContractNotice) GetOrganizationByID(id string) *eform.Organization {
if len(n.UBLExtensions.UBLExtension) > 0 {
orgs := n.UBLExtensions.UBLExtension[0].ExtensionContent.EformsExtension.Organizations.Organization
for i := range orgs {
if orgs[i].ID == id {
return &orgs[i]
}
}
}
return nil
}
@@ -0,0 +1,44 @@
package contract_award
import (
"encoding/xml"
"tm/internal/tender"
"tm/ted/eform"
)
// ContractAwardNotice represents the UBL ContractAwardNotice structure
type ContractAwardNotice struct {
XMLName xml.Name `xml:"ContractAwardNotice"`
UBLVersionID string `json:"ubl_version_id" xml:"UBLVersionID"`
CustomizationID string `json:"customization_id" xml:"CustomizationID"`
ID string `json:"id" xml:"ID"`
ContractFolderID string `json:"contract_folder_id" xml:"ContractFolderID"`
IssueDate string `json:"issue_date" xml:"IssueDate"`
IssueTime string `json:"issue_time,omitempty" xml:"IssueTime,omitempty"`
VersionID string `json:"version_id,omitempty" xml:"VersionID,omitempty"`
RegulatoryDomain string `json:"regulatory_domain,omitempty" xml:"RegulatoryDomain,omitempty"`
NoticeTypeCode string `json:"notice_type_code" xml:"NoticeTypeCode"`
NoticeLanguageCode string `json:"notice_language_code" xml:"NoticeLanguageCode"`
ContractingParty eform.ContractingParty `json:"contracting_party" xml:"ContractingParty"`
TenderingTerms eform.TenderingTerms `json:"tendering_terms,omitempty" xml:"TenderingTerms,omitempty"`
TenderingProcess eform.TenderingProcess `json:"tendering_process,omitempty" xml:"TenderingProcess,omitempty"`
ProcurementProject eform.ProcurementProject `json:"procurement_project" xml:"ProcurementProject"`
ProcurementProjectLot []eform.ProcurementProjectLot `json:"procurement_project_lots,omitempty" xml:"ProcurementProjectLot,omitempty"`
UBLExtensions eform.UBLExtensions `json:"ubl_extensions,omitempty" xml:"UBLExtensions,omitempty"`
}
// GetID returns the contract award notice ID
func (can ContractAwardNotice) GetID() string {
return can.ID
}
// Validate validates the parsed model
func (can ContractAwardNotice) Validate() error {
return nil
}
func (notice ContractAwardNotice) MapToTender() *tender.Tender {
t := new(tender.Tender)
t.ContractNoticeID = notice.GetID()
return t
}
@@ -0,0 +1,44 @@
package prior_information
import (
"encoding/xml"
"tm/internal/tender"
"tm/ted/eform"
)
// PriorInformationNotice represents the UBL PriorInformationNotice structure
type PriorInformationNotice struct {
XMLName xml.Name `xml:"PriorInformationNotice"`
UBLVersionID string `json:"ubl_version_id" xml:"UBLVersionID"`
CustomizationID string `json:"customization_id" xml:"CustomizationID"`
ID string `json:"id" xml:"ID"`
ContractFolderID string `json:"contract_folder_id" xml:"ContractFolderID"`
IssueDate string `json:"issue_date" xml:"IssueDate"`
IssueTime string `json:"issue_time,omitempty" xml:"IssueTime,omitempty"`
VersionID string `json:"version_id,omitempty" xml:"VersionID,omitempty"`
RegulatoryDomain string `json:"regulatory_domain,omitempty" xml:"RegulatoryDomain,omitempty"`
NoticeTypeCode string `json:"notice_type_code" xml:"NoticeTypeCode"`
NoticeLanguageCode string `json:"notice_language_code" xml:"NoticeLanguageCode"`
ContractingParty eform.ContractingParty `json:"contracting_party" xml:"ContractingParty"`
TenderingTerms eform.TenderingTerms `json:"tendering_terms,omitempty" xml:"TenderingTerms,omitempty"`
TenderingProcess eform.TenderingProcess `json:"tendering_process,omitempty" xml:"TenderingProcess,omitempty"`
ProcurementProject eform.ProcurementProject `json:"procurement_project" xml:"ProcurementProject"`
ProcurementProjectLot []eform.ProcurementProjectLot `json:"procurement_project_lots,omitempty" xml:"ProcurementProjectLot,omitempty"`
UBLExtensions eform.UBLExtensions `json:"ubl_extensions,omitempty" xml:"UBLExtensions,omitempty"`
}
// GetID returns the prior information notice ID
func (pin PriorInformationNotice) GetID() string {
return pin.ID
}
// Validate validates the parsed model
func (pin PriorInformationNotice) Validate() error {
return nil
}
func (pin PriorInformationNotice) MapToTender() *tender.Tender {
t := new(tender.Tender)
t.ContractNoticeID = pin.GetID()
return t
}