From 0dd877a46d93d86570a065ab90b92529edf27deb Mon Sep 17 00:00:00 2001 From: "n.nakhostin" Date: Sat, 4 Oct 2025 12:34:37 +0330 Subject: [PATCH] Refactor TED Repository and Introduce TED Mapping Functionality - Updated the tender repository to create a unique index on tender_id, enhancing database query performance. - Introduced new TED mapping functions to convert parsed TED documents into tender entities, improving integration with the tender management system. - Added error handling for contract notice processing in the TED scraper, ensuring robust logging and error management. - Removed deprecated eform structures to streamline the codebase and focus on essential components. --- internal/tender/repository.go | 2 +- ted/eform/award_criteria.go | 54 - ted/eform/business_registration.go | 77 - .../business_registration_information.go | 44 - ted/eform/constant.go | 96 - ted/eform/contract/contract.go | 213 -- ted/eform/contract_award/contract_award.go | 44 - ted/eform/contracting_party.go | 82 - ted/eform/document.go | 31 - ted/eform/lot.go | 62 - ted/eform/lot_result.go | 54 - ted/eform/modification.go | 53 - ted/eform/notice_change.go | 16 - ted/eform/notice_metadata.go | 144 -- ted/eform/organization.go | 97 - .../prior_information/prior_information.go | 44 - ted/eform/procedure.go | 244 --- ted/eform/procurement_project.go | 49 - ted/eform/procurement_project_lot.go | 13 - ted/eform/review_info.go | 17 - ted/eform/settled_contract.go | 26 - ted/eform/technical_specifications.go | 43 - ted/eform/tender.go | 24 - ted/eform/tendering_process.go | 28 - ted/eform/tendering_terms.go | 85 - ted/eform/ubl.go | 71 - ted/eform/validation.go | 19 - ted/mapper.go | 367 ++++ ted/model.go | 1884 +++++++++++++++++ ted/parser.go | 85 +- ted/scraper.go | 76 +- ted/service.go | 59 +- 32 files changed, 2386 insertions(+), 1817 deletions(-) delete mode 100644 ted/eform/award_criteria.go delete mode 100644 ted/eform/business_registration.go delete mode 100644 ted/eform/business_registration_information/business_registration_information.go delete mode 100644 ted/eform/constant.go delete mode 100644 ted/eform/contract/contract.go delete mode 100644 ted/eform/contract_award/contract_award.go delete mode 100644 ted/eform/contracting_party.go delete mode 100644 ted/eform/document.go delete mode 100644 ted/eform/lot.go delete mode 100644 ted/eform/lot_result.go delete mode 100644 ted/eform/modification.go delete mode 100644 ted/eform/notice_change.go delete mode 100644 ted/eform/notice_metadata.go delete mode 100644 ted/eform/organization.go delete mode 100644 ted/eform/prior_information/prior_information.go delete mode 100644 ted/eform/procedure.go delete mode 100644 ted/eform/procurement_project.go delete mode 100644 ted/eform/procurement_project_lot.go delete mode 100644 ted/eform/review_info.go delete mode 100644 ted/eform/settled_contract.go delete mode 100644 ted/eform/technical_specifications.go delete mode 100644 ted/eform/tender.go delete mode 100644 ted/eform/tendering_process.go delete mode 100644 ted/eform/tendering_terms.go delete mode 100644 ted/eform/ubl.go delete mode 100644 ted/eform/validation.go create mode 100644 ted/mapper.go create mode 100644 ted/model.go diff --git a/internal/tender/repository.go b/internal/tender/repository.go index e8a238d..74d8ee7 100644 --- a/internal/tender/repository.go +++ b/internal/tender/repository.go @@ -43,7 +43,7 @@ type tenderRepository struct { func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) TenderRepository { // Create indexes for tenders collection tenderIndexes := []orm.Index{ - *orm.CreateUniqueIndex("notice_publication_id_idx", bson.D{{Key: "notice_publication_id", Value: 1}}), + *orm.CreateUniqueIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}), *orm.NewIndex("contract_notice_id_idx", bson.D{{Key: "contract_notice_id", Value: 1}}), *orm.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), *orm.NewIndex("source_idx", bson.D{{Key: "source", Value: 1}}), diff --git a/ted/eform/award_criteria.go b/ted/eform/award_criteria.go deleted file mode 100644 index 3b426f1..0000000 --- a/ted/eform/award_criteria.go +++ /dev/null @@ -1,54 +0,0 @@ -package eform - -// ===================================================== -// AWARD CRITERIA -// ===================================================== - -// AwardCriteria defines how tenders will be evaluated -type AwardCriteria struct { - Type AwardCriteriaType `json:"type" xml:"type" validate:"required"` - PriceWeight *float64 `json:"price_weight,omitempty" xml:"price_weight,omitempty" validate:"omitempty,min=0,max=100"` - CriteriaStatement string `json:"criteria_statement,omitempty" xml:"criteria_statement,omitempty" validate:"max=10000"` - Criteria []AwardCriterion `json:"criteria,omitempty" xml:"criteria,omitempty" validate:"dive"` - CostCalculation string `json:"cost_calculation,omitempty" xml:"cost_calculation,omitempty" validate:"max=10000"` -} - -// AwardCriteriaType represents the evaluation approach -type AwardCriteriaType string - -const ( - AwardTypePrice AwardCriteriaType = "price" - AwardTypeCost AwardCriteriaType = "cost" - AwardTypeQualPrice AwardCriteriaType = "qual-price" -) - -// AwardCriterion represents an individual evaluation criterion -type AwardCriterion struct { - ID string `json:"id" xml:"id" validate:"required,acIDFormat"` - Name string `json:"name" xml:"name" validate:"required,max=200"` - Description string `json:"description,omitempty" xml:"description,omitempty" validate:"max=2000"` - Weight float64 `json:"weight" xml:"weight" validate:"required,min=0,max=100"` - Type AwardCriterionType `json:"type" xml:"type" validate:"required"` - SubCriteria []AwardCriterion `json:"sub_criteria,omitempty" xml:"sub_criteria,omitempty" validate:"dive"` -} - -// AwardCriterionType represents the type of criterion -type AwardCriterionType string - -const ( - CriterionQuality AwardCriterionType = "quality" - CriterionCost AwardCriterionType = "cost" - CriterionPrice AwardCriterionType = "price" - CriterionSocial AwardCriterionType = "social" - CriterionEnvironmental AwardCriterionType = "environmental" - CriterionInnovative AwardCriterionType = "innovative" -) - -// CalculateTotalAwardCriteriaWeight validates criteria weights sum to 100 -func (ac *AwardCriteria) CalculateTotalAwardCriteriaWeight() float64 { - var total float64 - for _, criterion := range ac.Criteria { - total += criterion.Weight - } - return total -} diff --git a/ted/eform/business_registration.go b/ted/eform/business_registration.go deleted file mode 100644 index da5318c..0000000 --- a/ted/eform/business_registration.go +++ /dev/null @@ -1,77 +0,0 @@ -package eform - -import "encoding/xml" - -// ===================================================== -// BUSINESS REGISTRATION INFORMATION NOTICE -// ===================================================== - -// AdditionalNoticeLanguage represents additional notice language -type AdditionalNoticeLanguage struct { - XMLName xml.Name `xml:"AdditionalNoticeLanguage"` - ID string `json:"id" xml:"ID"` -} - -// SenderParty represents the sender party -type SenderParty struct { - XMLName xml.Name `xml:"SenderParty"` - Contact Contact `json:"contact,omitempty" xml:"Contact,omitempty"` -} - -// BusinessParty represents the business party -type BusinessParty struct { - XMLName xml.Name `xml:"BusinessParty"` - PostalAddress PostalAddress `json:"postal_address,omitempty" xml:"PostalAddress,omitempty"` - PartyLegalEntity []PartyLegalEntityEnhanced `json:"party_legal_entity,omitempty" xml:"PartyLegalEntity,omitempty"` - Contact Contact `json:"contact,omitempty" xml:"Contact,omitempty"` - WebsiteURI string `json:"website_uri,omitempty" xml:"WebsiteURI,omitempty"` -} - -// AdditionalDocumentReference represents additional document reference -type AdditionalDocumentReference struct { - XMLName xml.Name `xml:"AdditionalDocumentReference"` - ID string `json:"id" xml:"ID"` - IssueDate string `json:"issue_date,omitempty" xml:"IssueDate,omitempty"` - ReferencedDocumentInternalAddress string `json:"referenced_document_internal_address,omitempty" xml:"ReferencedDocumentInternalAddress,omitempty"` - DocumentDescription string `json:"document_description,omitempty" xml:"DocumentDescription,omitempty"` - Attachment Attachment `json:"attachment,omitempty" xml:"Attachment,omitempty"` -} - -// BusinessCapability represents business capability -type BusinessCapability struct { - XMLName xml.Name `xml:"BusinessCapability"` - CapabilityTypeCode string `json:"capability_type_code" xml:"CapabilityTypeCode"` -} - -// NoticePurpose represents notice purpose -type NoticePurpose struct { - XMLName xml.Name `xml:"NoticePurpose"` - PurposeCode string `json:"purpose_code" xml:"PurposeCode"` -} - -// ===================================================== -// ENHANCED PARTY LEGAL ENTITY FOR BUSINESS REGISTRATION -// ===================================================== - -// PartyLegalEntityEnhanced represents enhanced party legal entity for business registration -type PartyLegalEntityEnhanced struct { - XMLName xml.Name `xml:"PartyLegalEntity"` - RegistrationName string `json:"registration_name,omitempty" xml:"RegistrationName,omitempty"` - CompanyID string `json:"company_id,omitempty" xml:"CompanyID,omitempty"` - RegistrationDate string `json:"registration_date,omitempty" xml:"RegistrationDate,omitempty"` - CorporateRegistrationScheme CorporateRegistrationScheme `json:"corporate_registration_scheme,omitempty" xml:"CorporateRegistrationScheme,omitempty"` -} - -// CorporateRegistrationScheme represents corporate registration scheme -type CorporateRegistrationScheme struct { - XMLName xml.Name `xml:"CorporateRegistrationScheme"` - JurisdictionRegionAddress JurisdictionRegionAddress `json:"jurisdiction_region_address,omitempty" xml:"JurisdictionRegionAddress,omitempty"` -} - -// JurisdictionRegionAddress represents jurisdiction region address -type JurisdictionRegionAddress struct { - XMLName xml.Name `xml:"JurisdictionRegionAddress"` - CityName string `json:"city_name,omitempty" xml:"CityName,omitempty"` - PostalZone string `json:"postal_zone,omitempty" xml:"PostalZone,omitempty"` - Country Country `json:"country,omitempty" xml:"Country,omitempty"` -} diff --git a/ted/eform/business_registration_information/business_registration_information.go b/ted/eform/business_registration_information/business_registration_information.go deleted file mode 100644 index 65ffc3e..0000000 --- a/ted/eform/business_registration_information/business_registration_information.go +++ /dev/null @@ -1,44 +0,0 @@ -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 -} diff --git a/ted/eform/constant.go b/ted/eform/constant.go deleted file mode 100644 index b830bad..0000000 --- a/ted/eform/constant.go +++ /dev/null @@ -1,96 +0,0 @@ -package eform - -// ===================================================== -// ENUMERATIONS AND CONSTANTS -// ===================================================== - -// Common CPV Code Prefixes (for validation) -const ( - CPVWorks = "45" // Construction works - CPVServices = "50" // Services (general range 50-99 with exceptions) -) - -// Common NUTS Code Structure -// NUTS codes are hierarchical: CC1234 -// CC = Country code (2 chars) -// 1 = NUTS 1 level -// 12 = NUTS 2 level -// 123 = NUTS 3 level - -// ISO 4217 Currency Codes (common ones) -const ( - CurrencyEUR = "EUR" // Euro - CurrencyUSD = "USD" // US Dollar - CurrencyGBP = "GBP" // British Pound - CurrencySEK = "SEK" // Swedish Krona - CurrencyDKK = "DKK" // Danish Krone - CurrencyPLN = "PLN" // Polish Zloty - CurrencyCZK = "CZK" // Czech Koruna - CurrencyHUF = "HUF" // Hungarian Forint - CurrencyRON = "RON" // Romanian Leu - CurrencyBGN = "BGN" // Bulgarian Lev - CurrencyHRK = "HRK" // Croatian Kuna -) - -// ISO 3166-1 Alpha-2 Country Codes (EU/EEA) -const ( - CountryAT = "AT" // Austria - CountryBE = "BE" // Belgium - CountryBG = "BG" // Bulgaria - CountryCY = "CY" // Cyprus - CountryCZ = "CZ" // Czech Republic - CountryDE = "DE" // Germany - CountryDK = "DK" // Denmark - CountryEE = "EE" // Estonia - CountryES = "ES" // Spain - CountryFI = "FI" // Finland - CountryFR = "FR" // France - CountryGR = "GR" // Greece - CountryHR = "HR" // Croatia - CountryHU = "HU" // Hungary - CountryIE = "IE" // Ireland - CountryIT = "IT" // Italy - CountryLT = "LT" // Lithuania - CountryLU = "LU" // Luxembourg - CountryLV = "LV" // Latvia - CountryMT = "MT" // Malta - CountryNL = "NL" // Netherlands - CountryPL = "PL" // Poland - CountryPT = "PT" // Portugal - CountryRO = "RO" // Romania - CountrySE = "SE" // Sweden - CountrySI = "SI" // Slovenia - CountrySK = "SK" // Slovakia - CountryIS = "IS" // Iceland (EEA) - CountryLI = "LI" // Liechtenstein (EEA) - CountryNO = "NO" // Norway (EEA) - CountryGB = "GB" // United Kingdom -) - -// ISO 639-1 Language Codes (EU official) -const ( - LangBG = "BG" // Bulgarian - LangCS = "CS" // Czech - LangDA = "DA" // Danish - LangDE = "DE" // German - LangEL = "EL" // Greek - LangEN = "EN" // English - LangES = "ES" // Spanish - LangET = "ET" // Estonian - LangFI = "FI" // Finnish - LangFR = "FR" // French - LangGA = "GA" // Irish - LangHR = "HR" // Croatian - LangHU = "HU" // Hungarian - LangIT = "IT" // Italian - LangLT = "LT" // Lithuanian - LangLV = "LV" // Latvian - LangMT = "MT" // Maltese - LangNL = "NL" // Dutch - LangPL = "PL" // Polish - LangPT = "PT" // Portuguese - LangRO = "RO" // Romanian - LangSK = "SK" // Slovak - LangSL = "SL" // Slovenian - LangSV = "SV" // Swedish -) diff --git a/ted/eform/contract/contract.go b/ted/eform/contract/contract.go deleted file mode 100644 index 47d830f..0000000 --- a/ted/eform/contract/contract.go +++ /dev/null @@ -1,213 +0,0 @@ -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 -} diff --git a/ted/eform/contract_award/contract_award.go b/ted/eform/contract_award/contract_award.go deleted file mode 100644 index 24625d9..0000000 --- a/ted/eform/contract_award/contract_award.go +++ /dev/null @@ -1,44 +0,0 @@ -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 -} diff --git a/ted/eform/contracting_party.go b/ted/eform/contracting_party.go deleted file mode 100644 index a074147..0000000 --- a/ted/eform/contracting_party.go +++ /dev/null @@ -1,82 +0,0 @@ -package eform - -import "encoding/xml" - -// ===================================================== -// CONTRACTING PARTY -// ===================================================== - -// ContractingParty represents the contracting party -type ContractingParty struct { - XMLName xml.Name `xml:"ContractingParty"` - BuyerProfileURI string `xml:"BuyerProfileURI,omitempty"` - ContractingPartyType ContractingPartyType `xml:"ContractingPartyType,omitempty"` - ContractingActivity ContractingActivity `xml:"ContractingActivity,omitempty"` - Party Party `xml:"Party,omitempty"` -} - -// ContractingPartyType contains party type information -type ContractingPartyType struct { - XMLName xml.Name `xml:"ContractingPartyType"` - PartyTypeCode string `xml:"PartyTypeCode"` -} - -// ContractingActivity represents the contracting activity -type ContractingActivity struct { - XMLName xml.Name `xml:"ContractingActivity"` - ActivityTypeCode string `xml:"ActivityTypeCode"` -} - -// Party represents a party -type Party struct { - XMLName xml.Name `xml:"Party"` - PartyIdentification []PartyIdentification `xml:"PartyIdentification,omitempty"` - PartyName []PartyName `xml:"PartyName,omitempty"` - PostalAddress PostalAddress `xml:"PostalAddress,omitempty"` - PartyLegalEntity PartyLegalEntity `xml:"PartyLegalEntity,omitempty"` - Contact Contact `xml:"Contact,omitempty"` - WebsiteURI string `xml:"WebsiteURI,omitempty"` -} - -// PartyIdentification represents party identification -type PartyIdentification struct { - XMLName xml.Name `xml:"PartyIdentification"` - ID string `xml:"ID"` -} - -// PartyName represents party name -type PartyName struct { - XMLName xml.Name `xml:"PartyName"` - Name string `xml:"Name"` -} - -// PostalAddress represents postal address -type PostalAddress struct { - XMLName xml.Name `xml:"PostalAddress"` - StreetName string `xml:"StreetName,omitempty"` - CityName string `xml:"CityName,omitempty"` - PostalZone string `xml:"PostalZone,omitempty"` - CountrySubentityCode string `xml:"CountrySubentityCode,omitempty"` - Country Country `xml:"Country,omitempty"` -} - -// Country represents country information -type Country struct { - XMLName xml.Name `xml:"Country"` - IdentificationCode string `xml:"IdentificationCode"` -} - -// PartyLegalEntity represents party legal entity -type PartyLegalEntity struct { - XMLName xml.Name `xml:"PartyLegalEntity"` - CompanyID string `xml:"CompanyID"` -} - -// Contact represents contact information -type Contact struct { - XMLName xml.Name `xml:"Contact"` - Name string `xml:"Name,omitempty"` - Telephone string `xml:"Telephone,omitempty"` - Telefax string `xml:"Telefax,omitempty"` - ElectronicMail string `xml:"ElectronicMail,omitempty"` -} diff --git a/ted/eform/document.go b/ted/eform/document.go deleted file mode 100644 index e04b30b..0000000 --- a/ted/eform/document.go +++ /dev/null @@ -1,31 +0,0 @@ -package eform - -// ===================================================== -// DOCUMENT STRUCTURE -// ===================================================== - -// Document represents attached documentation -type Document struct { - ID string `json:"id" xml:"id" validate:"required"` - Type DocumentType `json:"type" xml:"type" validate:"required"` - Title string `json:"title" xml:"title" validate:"required,max=500"` - Description string `json:"description,omitempty" xml:"description,omitempty" validate:"max=2000"` - URL string `json:"url,omitempty" xml:"url,omitempty" validate:"omitempty,url"` - RestrictedAccess bool `json:"restricted_access" xml:"restricted_access"` - Language LanguageCode `json:"language" xml:"language" validate:"required,iso639_1"` -} - -// DocumentType represents the type of document -type DocumentType string - -const ( - DocTypeProcurementDocs DocumentType = "procurement-docs" - DocTypeAdditionalInfo DocumentType = "additional-info" - DocTypeTenderSpecs DocumentType = "tender-specs" - DocTypeTenderForm DocumentType = "tender-form" - DocTypeContractDraft DocumentType = "contract-draft" - DocTypeEvaluationCriteria DocumentType = "evaluation-criteria" - DocTypeBillsQuantities DocumentType = "bills-quantities" - DocTypeIllustrations DocumentType = "illustrations" - DocTypeOther DocumentType = "other" -) diff --git a/ted/eform/lot.go b/ted/eform/lot.go deleted file mode 100644 index 0815989..0000000 --- a/ted/eform/lot.go +++ /dev/null @@ -1,62 +0,0 @@ -package eform - -// ===================================================== -// LOT STRUCTURE -// ===================================================== - -// Lot represents an individual procurement lot -type Lot struct { - ID string `json:"id" xml:"id" validate:"required,lotIDFormat"` - InternalID string `json:"internal_id,omitempty" xml:"internal_id,omitempty" validate:"max=100"` - Title string `json:"title" xml:"title" validate:"required,max=500"` - Description string `json:"description" xml:"description" validate:"required,max=10000"` - MainCPVCode string `json:"main_cpv_code" xml:"main_cpv_code" validate:"required,cpvCode"` - AdditionalCPVCodes []string `json:"additional_cpv_codes,omitempty" xml:"additional_cpv_codes,omitempty" validate:"dive,cpvCode"` - ContractNature ContractNature `json:"contract_nature" xml:"contract_nature" validate:"required"` - EstimatedValue *Money `json:"estimated_value,omitempty" xml:"estimated_value,omitempty"` - PlacesOfPerformance []Place `json:"places_of_performance" xml:"places_of_performance" validate:"required,min=1,dive"` - Duration *Duration `json:"duration,omitempty" xml:"duration,omitempty"` - Renewable bool `json:"renewable" xml:"renewable"` - RenewalDescription string `json:"renewal_description,omitempty" xml:"renewal_description,omitempty" validate:"required_if=Renewable true"` - OptionsDescription string `json:"options_description,omitempty" xml:"options_description,omitempty" validate:"max=10000"` - VariantsAccepted bool `json:"variants_accepted" xml:"variants_accepted"` - MaxLotsPerTenderer *int `json:"max_lots_per_tenderer,omitempty" xml:"max_lots_per_tenderer,omitempty" validate:"omitempty,min=1"` - MaxLotsAwarded *int `json:"max_lots_awarded,omitempty" xml:"max_lots_awarded,omitempty" validate:"omitempty,min=1"` - LotCombinations []string `json:"lot_combinations,omitempty" xml:"lot_combinations,omitempty"` - SubcontractingObligation SubcontractingObligation `json:"subcontracting_obligation" xml:"subcontracting_obligation"` - SubcontractingPercentage *float64 `json:"subcontracting_percentage,omitempty" xml:"subcontracting_percentage,omitempty" validate:"omitempty,min=0,max=100"` - TechnicalSpecifications TechnicalSpecifications `json:"technical_specifications" xml:"technical_specifications" validate:"required"` - AwardCriteria AwardCriteria `json:"award_criteria" xml:"award_criteria" validate:"required"` - TenderingTerms TenderingTerms `json:"tendering_terms" xml:"tendering_terms" validate:"required"` - Result *LotResult `json:"result,omitempty" xml:"result,omitempty"` -} - -// SubcontractingObligation represents subcontracting requirements -type SubcontractingObligation string - -const ( - SubcontractingNone SubcontractingObligation = "none" - SubcontractingMinimum SubcontractingObligation = "minimum" - SubcontractingRequired SubcontractingObligation = "required" -) - -// Place represents a place of performance -type Place struct { - StreetAddress string `json:"street_address,omitempty" xml:"street_address,omitempty" validate:"max=200"` - Locality string `json:"locality,omitempty" xml:"locality,omitempty" validate:"max=100"` - PostalCode string `json:"postal_code,omitempty" xml:"postal_code,omitempty" validate:"max=20"` - CountrySubdivision string `json:"country_subdivision,omitempty" xml:"country_subdivision,omitempty" validate:"max=100"` - CountryCode string `json:"country_code" xml:"country_code" validate:"required,iso3166_1_alpha2"` - NUTSCodes []string `json:"nuts_codes" xml:"nuts_codes" validate:"required,min=1,dive,nutsCode"` - Description string `json:"description,omitempty" xml:"description,omitempty" validate:"max=1000"` -} - -// HasSubcontractingObligation checks if subcontracting is required -func (l *Lot) HasSubcontractingObligation() bool { - return l.SubcontractingObligation != SubcontractingNone -} - -// IsReservedContract checks if lot is reserved for specific participants -func (l *Lot) IsReservedContract() bool { - return l.TechnicalSpecifications.ReservedExecution != ReservedNone -} diff --git a/ted/eform/lot_result.go b/ted/eform/lot_result.go deleted file mode 100644 index 4717671..0000000 --- a/ted/eform/lot_result.go +++ /dev/null @@ -1,54 +0,0 @@ -package eform - -import "time" - -// ===================================================== -// LOT RESULT STRUCTURE -// ===================================================== - -// LotResult represents the outcome of lot procurement -type LotResult struct { - ID string `json:"id" xml:"id" validate:"required,resIDFormat"` - Status ResultStatus `json:"status" xml:"status" validate:"required"` - StatusDescription string `json:"status_description,omitempty" xml:"status_description,omitempty" validate:"max=10000"` - DecisionDate *time.Time `json:"decision_date,omitempty" xml:"decision_date,omitempty"` - TendersReceived int `json:"tenders_received" xml:"tenders_received" validate:"min=0"` - TendersSME int `json:"tenders_sme" xml:"tenders_sme" validate:"min=0"` - TendersElectronic int `json:"tenders_electronic" xml:"tenders_electronic" validate:"min=0"` - TendersOtherEU int `json:"tenders_other_eu" xml:"tenders_other_eu" validate:"min=0"` - TendersNonEU int `json:"tenders_non_eu" xml:"tenders_non_eu" validate:"min=0"` - AwardedValue *Money `json:"awarded_value,omitempty" xml:"awarded_value,omitempty"` - SubcontractingShare *float64 `json:"subcontracting_share,omitempty" xml:"subcontracting_share,omitempty" validate:"omitempty,min=0,max=100"` - SubcontractingValue *Money `json:"subcontracting_value,omitempty" xml:"subcontracting_value,omitempty"` - ConcessionRevenue *Money `json:"concession_revenue,omitempty" xml:"concession_revenue,omitempty"` - Tenders []Tender `json:"tenders,omitempty" xml:"tenders,omitempty" validate:"dive"` - SettledContract *SettledContract `json:"settled_contract,omitempty" xml:"settled_contract,omitempty"` - NonAwardJustification string `json:"non_award_justification,omitempty" xml:"non_award_justification,omitempty" validate:"max=10000"` -} - -// ResultStatus represents the lot result status -type ResultStatus string - -const ( - ResultOpen ResultStatus = "open" - ResultClose ResultStatus = "close" - ResultWinner ResultStatus = "winn" - ResultNoWinner ResultStatus = "no-winn" - ResultSelected ResultStatus = "selec" - ResultClosedNoWin ResultStatus = "clos-nw" -) - -// HasWinner checks if lot has a winning tender -func (lr *LotResult) HasWinner() bool { - return lr.Status == ResultWinner -} - -// GetWinningTender returns the winning tender if exists -func (lr *LotResult) GetWinningTender() *Tender { - for i := range lr.Tenders { - if lr.Tenders[i].IsWinner { - return &lr.Tenders[i] - } - } - return nil -} diff --git a/ted/eform/modification.go b/ted/eform/modification.go deleted file mode 100644 index 7dd6513..0000000 --- a/ted/eform/modification.go +++ /dev/null @@ -1,53 +0,0 @@ -package eform - -import "time" - -// ===================================================== -// MODIFICATION STRUCTURE -// ===================================================== - -// Modification represents changes to existing contracts -type Modification struct { - ID string `json:"id" xml:"id" validate:"required,modIDFormat"` - OriginalContractID string `json:"original_contract_id" xml:"original_contract_id" validate:"required,conIDFormat"` - Reason ModificationReason `json:"reason" xml:"reason" validate:"required"` - ReasonDescription string `json:"reason_description" xml:"reason_description" validate:"required,max=10000"` - ValueBefore Money `json:"value_before" xml:"value_before" validate:"required"` - ValueAfter Money `json:"value_after" xml:"value_after" validate:"required"` - ValueChange Money `json:"value_change" xml:"value_change"` - PercentageChange float64 `json:"percentage_change" xml:"percentage_change"` - DurationBefore *Duration `json:"duration_before,omitempty" xml:"duration_before,omitempty"` - DurationAfter *Duration `json:"duration_after,omitempty" xml:"duration_after,omitempty"` - ModificationDescription string `json:"modification_description" xml:"modification_description" validate:"required,max=10000"` - ModificationDate time.Time `json:"modification_date" xml:"modification_date" validate:"required"` - ContractorChange bool `json:"contractor_change" xml:"contractor_change"` - NewContractorIDs []string `json:"new_contractor_ids,omitempty" xml:"new_contractor_ids,omitempty"` -} - -// ModificationReason represents the reason for contract modification -type ModificationReason string - -const ( - ModReasonAdditionalWorks ModificationReason = "add-wks" - ModReasonUnforeseen ModificationReason = "unforeseen" - ModReasonDesignDefect ModificationReason = "design-defect" - ModReasonAdditionalNeed ModificationReason = "additional-need" - ModReasonRepeatSimilar ModificationReason = "repeat-similar" - ModReasonRatePriceUpdate ModificationReason = "rate-price-update" - ModReasonSuspendCall ModificationReason = "suspend-call" - ModReasonOther ModificationReason = "other" -) - -// CalculateModificationChange calculates value change for modifications -func (m *Modification) CalculateModificationChange() { - if m.ValueBefore.Currency == m.ValueAfter.Currency { - change := m.ValueAfter.Amount - m.ValueBefore.Amount - m.ValueChange = Money{ - Amount: change, - Currency: m.ValueBefore.Currency, - } - if m.ValueBefore.Amount != 0 { - m.PercentageChange = (change / m.ValueBefore.Amount) * 100 - } - } -} diff --git a/ted/eform/notice_change.go b/ted/eform/notice_change.go deleted file mode 100644 index f8d2277..0000000 --- a/ted/eform/notice_change.go +++ /dev/null @@ -1,16 +0,0 @@ -package eform - -// ===================================================== -// CHANGE/CORRECTION STRUCTURE -// ===================================================== - -// NoticeChange represents changes or corrections to notices -type NoticeChange struct { - ChangeID string `json:"change_id" xml:"change_id" validate:"required"` - SectionReference string `json:"section_reference" xml:"section_reference" validate:"required"` - LotReference string `json:"lot_reference,omitempty" xml:"lot_reference,omitempty"` - Label string `json:"label" xml:"label" validate:"required,max=200"` - OldValue string `json:"old_value,omitempty" xml:"old_value,omitempty"` - NewValue string `json:"new_value" xml:"new_value" validate:"required"` - ChangeDescription string `json:"change_description,omitempty" xml:"change_description,omitempty" validate:"max=2000"` -} diff --git a/ted/eform/notice_metadata.go b/ted/eform/notice_metadata.go deleted file mode 100644 index 34107c4..0000000 --- a/ted/eform/notice_metadata.go +++ /dev/null @@ -1,144 +0,0 @@ -package eform - -import "time" - -// ===================================================== -// NOTICE METADATA (Legacy - keeping for compatibility) -// ===================================================== - -// NoticeMetadata contains identification and publication information -type NoticeMetadata struct { - NoticeID string `json:"notice_id" xml:"notice_id" validate:"required,noticeIDFormat"` - NoticeType NoticeType `json:"notice_type" xml:"notice_type" validate:"required"` - NoticeVersion string `json:"notice_version" xml:"notice_version" validate:"required,semver"` - PublicationDate *time.Time `json:"publication_date,omitempty" xml:"publication_date,omitempty"` - DispatchDate time.Time `json:"dispatch_date" xml:"dispatch_date" validate:"required"` - Language LanguageCode `json:"language" xml:"language" validate:"required,iso639_1"` - TranslationLanguages []LanguageCode `json:"translation_languages,omitempty" xml:"translation_languages,omitempty" validate:"dive,iso639_1"` - LegalBasis LegalBasis `json:"legal_basis" xml:"legal_basis" validate:"required"` - PreviousNoticeID string `json:"previous_notice_id,omitempty" xml:"previous_notice_id,omitempty" validate:"omitempty,noticeIDFormat"` - ChangedNoticeID string `json:"changed_notice_id,omitempty" xml:"changed_notice_id,omitempty" validate:"omitempty,noticeIDFormat"` - CorrectionPrevious bool `json:"correction_previous" xml:"correction_previous"` -} - -// NoticeType represents the eForms subtype identifier (1-40, E1-E5) -type NoticeType string - -const ( - // Planning notices (1-6) - NoticeTypePIN1 NoticeType = "1" // Prior Information Notice - standard - NoticeTypePIN2 NoticeType = "2" // Prior Information Notice - utilities - NoticeTypePIN3 NoticeType = "3" // Prior Information Notice - social - NoticeTypePINCallComp4 NoticeType = "4" // PIN as call for competition - standard - NoticeTypePINCallComp5 NoticeType = "5" // PIN as call for competition - utilities - NoticeTypePINCallComp6 NoticeType = "6" // PIN as call for competition - social - - // Competition notices (7-14) - NoticeTypeCN7 NoticeType = "7" // Contract Notice - open/restricted - NoticeTypeCN8 NoticeType = "8" // Contract Notice - utilities - NoticeTypeCN9 NoticeType = "9" // Contract Notice - negotiated - NoticeTypeCN10 NoticeType = "10" // Contract Notice - competitive dialogue - NoticeTypeCN11 NoticeType = "11" // Contract Notice - innovation partnership - NoticeTypeCN12 NoticeType = "12" // Contract Notice - social services - NoticeTypeCN13 NoticeType = "13" // Contract Notice - concessions - NoticeTypeCN14 NoticeType = "14" // Contract Notice - design contest - - // Direct award notices (15-24) - NoticeTypeCANDirect15 NoticeType = "15" // CAN without prior - direct award - NoticeTypeCANDirect16 NoticeType = "16" // CAN without prior - utilities - NoticeTypeCANDirect17 NoticeType = "17" // CAN without prior - social - NoticeTypeCANDirect18 NoticeType = "18" // CAN without prior - negotiated - NoticeTypeCANDirect19 NoticeType = "19" // CAN without prior - concessions - NoticeTypeCANDirect20 NoticeType = "20" // Voluntary ex-ante transparency - direct award - NoticeTypeCANDirect21 NoticeType = "21" // Voluntary ex-ante transparency - utilities - NoticeTypeCANDirect22 NoticeType = "22" // Voluntary ex-ante transparency - social - NoticeTypeCANDirect23 NoticeType = "23" // Voluntary ex-ante transparency - concessions - NoticeTypeCANDirect24 NoticeType = "24" // Voluntary ex-ante transparency - defence - - // Result notices (25-35) - NoticeTypeCAN25 NoticeType = "25" // Contract Award Notice - standard - NoticeTypeCAN26 NoticeType = "26" // Contract Award Notice - utilities - NoticeTypeCAN27 NoticeType = "27" // Contract Award Notice - social - NoticeTypeCAN28 NoticeType = "28" // Contract Award Notice - concessions - NoticeTypeCAN29 NoticeType = "29" // Contract Award Notice - design contest results - NoticeTypeCAN30 NoticeType = "30" // Contract Award Notice - defence - NoticeTypeCAN31 NoticeType = "31" // Contract Award Notice - framework single - NoticeTypeCAN32 NoticeType = "32" // Contract Award Notice - DPS award - NoticeTypeCAN33 NoticeType = "33" // Contract Award Notice - modification - NoticeTypeCAN34 NoticeType = "34" // Contract Award Notice - completion - NoticeTypeCAN35 NoticeType = "35" // Contract Award Notice - quarterly DPS - - // Modification notices (36-40) - NoticeTypeMod36 NoticeType = "36" // Contract Modification Notice - standard - NoticeTypeMod37 NoticeType = "37" // Contract Modification Notice - utilities - NoticeTypeMod38 NoticeType = "38" // Contract Modification Notice - social - NoticeTypeMod39 NoticeType = "39" // Contract Modification Notice - concessions - NoticeTypeMod40 NoticeType = "40" // Contract Modification Notice - defence - - // Voluntary notices (E1-E5) - NoticeTypeVoluntaryE1 NoticeType = "E1" // Voluntary notice - below threshold - NoticeTypeVoluntaryE2 NoticeType = "E2" // Voluntary notice - modification - NoticeTypeVoluntaryE3 NoticeType = "E3" // Voluntary notice - contract completion - NoticeTypeVoluntaryE4 NoticeType = "E4" // Voluntary notice - concession award - NoticeTypeVoluntaryE5 NoticeType = "E5" // Voluntary notice - qualification system -) - -// LegalBasis represents applicable EU directives -type LegalBasis string - -const ( - LegalBasisClassicDir LegalBasis = "32014L0024" // Directive 2014/24/EU (classic) - LegalBasisUtilitiesDir LegalBasis = "32014L0025" // Directive 2014/25/EU (utilities) - LegalBasisConcessionsDir LegalBasis = "32014L0023" // Directive 2014/23/EU (concessions) - LegalBasisDefenceDir LegalBasis = "32009L0081" // Directive 2009/81/EC (defence) - LegalBasisFinancialReg LegalBasis = "32018R1046" // Financial Regulation (EU institutions) -) - -// LanguageCode represents ISO 639-1 language codes -type LanguageCode string - -// IsPlanningNotice checks if notice is a planning/PIN notice -func (nt NoticeType) IsPlanningNotice() bool { - planningTypes := []NoticeType{ - NoticeTypePIN1, NoticeTypePIN2, NoticeTypePIN3, - NoticeTypePINCallComp4, NoticeTypePINCallComp5, NoticeTypePINCallComp6, - } - for _, t := range planningTypes { - if nt == t { - return true - } - } - return false -} - -// IsCompetitionNotice checks if notice is a competition/CN notice -func (nt NoticeType) IsCompetitionNotice() bool { - return nt >= NoticeTypeCN7 && nt <= NoticeTypeCN14 -} - -// IsResultNotice checks if notice is a result/CAN notice -func (nt NoticeType) IsResultNotice() bool { - return (nt >= NoticeTypeCANDirect15 && nt <= NoticeTypeCANDirect24) || - (nt >= NoticeTypeCAN25 && nt <= NoticeTypeCAN35) -} - -// IsModificationNotice checks if notice is a modification notice -func (nt NoticeType) IsModificationNotice() bool { - return nt >= NoticeTypeMod36 && nt <= NoticeTypeMod40 -} - -// IsVoluntaryNotice checks if notice is voluntary -func (nt NoticeType) IsVoluntaryNotice() bool { - voluntaryTypes := []NoticeType{ - NoticeTypeVoluntaryE1, NoticeTypeVoluntaryE2, NoticeTypeVoluntaryE3, - NoticeTypeVoluntaryE4, NoticeTypeVoluntaryE5, - NoticeTypeCANDirect20, NoticeTypeCANDirect21, NoticeTypeCANDirect22, - NoticeTypeCANDirect23, NoticeTypeCANDirect24, - } - for _, t := range voluntaryTypes { - if nt == t { - return true - } - } - return false -} diff --git a/ted/eform/organization.go b/ted/eform/organization.go deleted file mode 100644 index c50856e..0000000 --- a/ted/eform/organization.go +++ /dev/null @@ -1,97 +0,0 @@ -package eform - -// ===================================================== -// ORGANIZATION STRUCTURE -// ===================================================== - -// Organization represents any party involved in the procurement -type Organization struct { - ID string `json:"id" xml:"id" validate:"required,orgIDFormat"` - Roles []OrganizationRole `json:"roles" xml:"roles" validate:"required,min=1,dive"` - LegalName string `json:"legal_name" xml:"legal_name" validate:"required,max=500"` - LegalType *OrganizationType `json:"legal_type,omitempty" xml:"legal_type,omitempty"` - RegistrationID string `json:"registration_id,omitempty" xml:"registration_id,omitempty"` - VATNumber string `json:"vat_number,omitempty" xml:"vat_number,omitempty" validate:"omitempty,vatFormat"` - NationalID string `json:"national_id,omitempty" xml:"national_id,omitempty"` - Address Address `json:"address" xml:"address" validate:"required"` - ContactPoint ContactPoint `json:"contact_point" xml:"contact_point" validate:"required"` - BuyerProfile string `json:"buyer_profile,omitempty" xml:"buyer_profile,omitempty" validate:"omitempty,url"` - ProcurementActivity *ActivityType `json:"procurement_activity,omitempty" xml:"procurement_activity,omitempty"` - SME bool `json:"sme" xml:"sme"` -} - -// OrganizationRole defines the role of an organization in the procurement -type OrganizationRole string - -const ( - RoleBuyer OrganizationRole = "buyer" - RoleServiceProvider OrganizationRole = "service-provider" - RoleTEDeSender OrganizationRole = "ted-esen" - RoleWinner OrganizationRole = "winner" - RoleSubcontractor OrganizationRole = "subcontractor" - RoleReviewBody OrganizationRole = "review-body" - RoleReviewOrg OrganizationRole = "review-org" - RoleMediationBody OrganizationRole = "mediation-body" - RoleLeadBuyer OrganizationRole = "lead-buyer" - RoleCentralBuyer OrganizationRole = "central-buyer" - RoleAcquirer OrganizationRole = "acquirer" -) - -// OrganizationType represents the legal type of organization -type OrganizationType string - -const ( - OrgTypeBodyPublicLaw OrganizationType = "body-pl" - OrgTypeCentralGov OrganizationType = "cga" - OrgTypeLocalAuthority OrganizationType = "la" - OrgTypeSubsidized OrganizationType = "org-sub" - OrgTypePublicUndertaking OrganizationType = "pub-undert" - OrgTypeEUInstitution OrganizationType = "eu-ins-bod-ag" - OrgTypeOther OrganizationType = "org-other" -) - -// ActivityType represents main procurement activity -type ActivityType string - -const ( - ActivityAuthority ActivityType = "auth-oth" - ActivityDefence ActivityType = "def" - ActivityEconomicAffairs ActivityType = "econ-aff" - ActivityEducation ActivityType = "edu" - ActivityEnvironment ActivityType = "env" - ActivityGeneralPublic ActivityType = "gen-pub" - ActivityHealth ActivityType = "health" - ActivityHousingCommunity ActivityType = "hous-com" - ActivityPublicOrderSafety ActivityType = "pub-os" - ActivityRecreationCulture ActivityType = "rec-com" - ActivitySocialProtection ActivityType = "soc-pro" - ActivityWater ActivityType = "water" - ActivityAirport ActivityType = "airport" - ActivityElectricity ActivityType = "electricity" - ActivityExplorationExtract ActivityType = "exploration-extraction" - ActivityGasHeat ActivityType = "gas-heat" - ActivityPort ActivityType = "port" - ActivityPost ActivityType = "post" - ActivityRailway ActivityType = "rail" - ActivityUrbanRailway ActivityType = "urban-railway" - ActivityWaterUtility ActivityType = "water" -) - -// Address represents physical address information -type Address struct { - StreetAddress string `json:"street_address" xml:"street_address" validate:"required,max=200"` - Locality string `json:"locality" xml:"locality" validate:"required,max=100"` - PostalCode string `json:"postal_code" xml:"postal_code" validate:"required,max=20"` - CountrySubdivision string `json:"country_subdivision,omitempty" xml:"country_subdivision,omitempty" validate:"max=100"` - CountryCode string `json:"country_code" xml:"country_code" validate:"required,iso3166_1_alpha2"` - NUTSCodes []string `json:"nuts_codes,omitempty" xml:"nuts_codes,omitempty" validate:"dive,nutsCode"` -} - -// ContactPoint represents contact information -type ContactPoint struct { - Name string `json:"name,omitempty" xml:"name,omitempty" validate:"max=200"` - Email string `json:"email" xml:"email" validate:"required,email"` - Telephone string `json:"telephone,omitempty" xml:"telephone,omitempty" validate:"max=30"` - Fax string `json:"fax,omitempty" xml:"fax,omitempty" validate:"max=30"` - URL string `json:"url,omitempty" xml:"url,omitempty" validate:"omitempty,url"` -} diff --git a/ted/eform/prior_information/prior_information.go b/ted/eform/prior_information/prior_information.go deleted file mode 100644 index 52511eb..0000000 --- a/ted/eform/prior_information/prior_information.go +++ /dev/null @@ -1,44 +0,0 @@ -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 -} diff --git a/ted/eform/procedure.go b/ted/eform/procedure.go deleted file mode 100644 index b03d306..0000000 --- a/ted/eform/procedure.go +++ /dev/null @@ -1,244 +0,0 @@ -package eform - -// ===================================================== -// PROCEDURE STRUCTURE -// ===================================================== - -// Procedure represents the procurement procedure -type Procedure struct { - ID string `json:"id" xml:"id" validate:"required,procIDFormat"` - Title string `json:"title" xml:"title" validate:"required,max=500"` - Description string `json:"description" xml:"description" validate:"required,max=10000"` - ProcedureType ProcedureType `json:"procedure_type" xml:"procedure_type" validate:"required"` - AcceleratedProcedure bool `json:"accelerated_procedure" xml:"accelerated_procedure"` - AcceleratedJustification string `json:"accelerated_justification,omitempty" xml:"accelerated_justification,omitempty" validate:"required_if=AcceleratedProcedure true"` - FrameworkAgreement bool `json:"framework_agreement" xml:"framework_agreement"` - FAMaxParticipants *int `json:"fa_max_participants,omitempty" xml:"fa_max_participants,omitempty" validate:"omitempty,min=1"` - FADuration *Duration `json:"fa_duration,omitempty" xml:"fa_duration,omitempty"` - DynamicPurchasingSystem bool `json:"dynamic_purchasing_system" xml:"dynamic_purchasing_system"` - ElectronicAuction bool `json:"electronic_auction" xml:"electronic_auction"` - JointProcurement bool `json:"joint_procurement" xml:"joint_procurement"` - CentralPurchasing bool `json:"central_purchasing" xml:"central_purchasing"` - GPA bool `json:"gpa" xml:"gpa"` - InternationalAgreements []string `json:"international_agreements,omitempty" xml:"international_agreements,omitempty"` - MainCPVCode string `json:"main_cpv_code" xml:"main_cpv_code" validate:"required,cpvCode"` - AdditionalCPVCodes []string `json:"additional_cpv_codes,omitempty" xml:"additional_cpv_codes,omitempty" validate:"dive,cpvCode"` - ContractNature ContractNature `json:"contract_nature" xml:"contract_nature" validate:"required"` - EstimatedValue *Money `json:"estimated_value,omitempty" xml:"estimated_value,omitempty"` - EstimatedValueRange *ValueRange `json:"estimated_value_range,omitempty" xml:"estimated_value_range,omitempty"` - ProcurementDocumentsURL string `json:"procurement_documents_url,omitempty" xml:"procurement_documents_url,omitempty" validate:"omitempty,url"` - ProcurementDocsRestricted bool `json:"procurement_docs_restricted" xml:"procurement_docs_restricted"` - SubmissionElectronic SubmissionMethod `json:"submission_electronic" xml:"submission_electronic" validate:"required"` - SubmissionLanguages []LanguageCode `json:"submission_languages" xml:"submission_languages" validate:"required,min=1,dive,iso639_1"` - TenderValidityDuration *Duration `json:"tender_validity_duration,omitempty" xml:"tender_validity_duration,omitempty"` - Lots []Lot `json:"lots,omitempty" xml:"lots,omitempty" validate:"dive"` - SecondStage *SecondStage `json:"second_stage,omitempty" xml:"second_stage,omitempty"` - Recurrence bool `json:"recurrence" xml:"recurrence"` - RecurrenceTiming string `json:"recurrence_timing,omitempty" xml:"recurrence_timing,omitempty" validate:"max=1000"` -} - -// ProcedureType represents the type of procurement procedure -type ProcedureType string - -const ( - ProcTypeOpen ProcedureType = "open" - ProcTypeRestricted ProcedureType = "restricted" - ProcTypeCompetitiveDialogue ProcedureType = "comp-dial" - ProcTypeInnovation ProcedureType = "innovation" - ProcTypeNegotiatedWithCall ProcedureType = "neg-w-call" - ProcTypeNegotiatedWoCall ProcedureType = "neg-wo-call" - ProcTypeCompWithNeg ProcedureType = "comp-w-n" - ProcTypeAwardWoCall ProcedureType = "pt-award-wo-call" - ProcTypeOtherSingle ProcedureType = "oth-single" - ProcTypeOtherMultiple ProcedureType = "oth-mult" -) - -// ContractNature represents the type of contract -type ContractNature string - -const ( - ContractNatureWorks ContractNature = "works" - ContractNatureSupplies ContractNature = "supplies" - ContractNatureServices ContractNature = "services" - ContractNatureCombined ContractNature = "combined" -) - -// SubmissionMethod represents how tenders can be submitted -type SubmissionMethod string - -const ( - SubmissionAllowed SubmissionMethod = "allowed" - SubmissionRequired SubmissionMethod = "required" - SubmissionNotAllowed SubmissionMethod = "not-allowed" -) - -// Duration represents a time duration -type Duration struct { - Type DurationType `json:"type" xml:"type" validate:"required"` - Value *int `json:"value,omitempty" xml:"value,omitempty" validate:"omitempty,min=1"` -} - -// DurationType represents the unit of duration -type DurationType string - -const ( - DurationMonths DurationType = "months" - DurationDays DurationType = "days" - DurationYears DurationType = "years" - DurationUnlimited DurationType = "unlimited" -) - -// Money represents a monetary value -type Money struct { - Amount float64 `json:"amount" xml:"amount" validate:"required,min=0"` - Currency string `json:"currency" xml:"currency" validate:"required,iso4217"` -} - -// ValueRange represents a range of monetary values -type ValueRange struct { - MinValue Money `json:"min_value" xml:"min_value" validate:"required"` - MaxValue Money `json:"max_value" xml:"max_value" validate:"required,gtfield=MinValue.Amount"` -} - -// SecondStage represents second stage of two-stage procedures -type SecondStage struct { - MinCandidates *int `json:"min_candidates,omitempty" xml:"min_candidates,omitempty" validate:"omitempty,min=1"` - MaxCandidates *int `json:"max_candidates,omitempty" xml:"max_candidates,omitempty" validate:"omitempty,min=1"` - ObjectiveCriteria string `json:"objective_criteria,omitempty" xml:"objective_criteria,omitempty" validate:"max=10000"` -} - -// IsAboveThreshold checks if procedure value is above EU thresholds -func (p *Procedure) IsAboveThreshold() bool { - if p.EstimatedValue == nil { - return false - } - - // EU thresholds 2024-2025 (in EUR) - thresholds := map[ContractNature]float64{ - ContractNatureWorks: 5382000, - ContractNatureSupplies: 143000, // central government - ContractNatureServices: 143000, // central government - } - - threshold, exists := thresholds[p.ContractNature] - if !exists { - return false - } - - // Convert to EUR if needed - valueEUR := p.EstimatedValue.Amount - if p.EstimatedValue.Currency != CurrencyEUR { - // In real implementation, would use exchange rates - // This is simplified - return true // Assume above threshold for non-EUR - } - - return valueEUR >= threshold -} - -// GetTotalEstimatedValue calculates total value from lots -func (p *Procedure) GetTotalEstimatedValue() *Money { - if len(p.Lots) == 0 { - return p.EstimatedValue - } - - var total float64 - var currency string - - for _, lot := range p.Lots { - if lot.EstimatedValue != nil { - if currency == "" { - currency = lot.EstimatedValue.Currency - } - if lot.EstimatedValue.Currency == currency { - total += lot.EstimatedValue.Amount - } - } - } - - if currency != "" { - return &Money{ - Amount: total, - Currency: currency, - } - } - - return p.EstimatedValue -} - -// IsDividedIntoLots checks if procedure has lots -func (p *Procedure) IsDividedIntoLots() bool { - return len(p.Lots) > 0 -} - -// GetLotByID retrieves lot by ID -func (p *Procedure) GetLotByID(id string) *Lot { - for i := range p.Lots { - if p.Lots[i].ID == id { - return &p.Lots[i] - } - } - return nil -} - -// IsFrameworkAgreement checks if this is a framework agreement -func (p *Procedure) IsFrameworkAgreement() bool { - return p.FrameworkAgreement -} - -// IsDynamicPurchasingSystem checks if this is a DPS -func (p *Procedure) IsDynamicPurchasingSystem() bool { - return p.DynamicPurchasingSystem -} - -// RequiresTwoStage checks if procedure is two-stage -func (p *Procedure) RequiresTwoStage() bool { - twoStageProcs := []ProcedureType{ - ProcTypeRestricted, - ProcTypeCompetitiveDialogue, - ProcTypeInnovation, - ProcTypeNegotiatedWithCall, - } - - for _, t := range twoStageProcs { - if p.ProcedureType == t { - return true - } - } - return false -} - -// GetAwardedContracts returns all awarded contracts from procedure -func (p *Procedure) GetAwardedContracts() []SettledContract { - var contracts []SettledContract - - for _, lot := range p.Lots { - if lot.Result != nil && lot.Result.SettledContract != nil { - contracts = append(contracts, *lot.Result.SettledContract) - } - } - - return contracts -} - -// GetTotalAwardedValue calculates total value of all awarded contracts -func (p *Procedure) GetTotalAwardedValue() *Money { - contracts := p.GetAwardedContracts() - if len(contracts) == 0 { - return nil - } - - var total float64 - currency := contracts[0].ContractValue.Currency - - for _, contract := range contracts { - if contract.ContractValue.Currency == currency { - total += contract.ContractValue.Amount - } - } - - return &Money{ - Amount: total, - Currency: currency, - } -} diff --git a/ted/eform/procurement_project.go b/ted/eform/procurement_project.go deleted file mode 100644 index 0bdf7bd..0000000 --- a/ted/eform/procurement_project.go +++ /dev/null @@ -1,49 +0,0 @@ -package eform - -import "encoding/xml" - -// ===================================================== -// PROCUREMENT PROJECT -// ===================================================== - -// ProcurementProject represents the procurement project -type ProcurementProject struct { - XMLName xml.Name `xml:"ProcurementProject"` - ID string `xml:"ID"` - Name string `xml:"Name"` - Description string `xml:"Description"` - ProcurementTypeCode string `xml:"ProcurementTypeCode"` - Note string `xml:"Note,omitempty"` - RequestedTenderTotal RequestedTenderTotal `xml:"RequestedTenderTotal,omitempty"` - MainCommodityClassification MainCommodityClassification `xml:"MainCommodityClassification,omitempty"` - RealizedLocation RealizedLocation `xml:"RealizedLocation,omitempty"` - PlannedPeriod PlannedPeriod `xml:"PlannedPeriod,omitempty"` -} - -// RequestedTenderTotal represents the estimated contract amount -type RequestedTenderTotal struct { - XMLName xml.Name `xml:"RequestedTenderTotal"` - EstimatedOverallContractAmount string `xml:"EstimatedOverallContractAmount"` -} - -// MainCommodityClassification represents the CPV code -type MainCommodityClassification struct { - ItemClassificationCode string `json:"item_classification_code" xml:"ItemClassificationCode"` -} - -// RealizedLocation represents the location where work will be performed -type RealizedLocation struct { - Address PostalAddress `xml:"PostalAddress"` - Region string `xml:"Region,omitempty"` -} - -// PlannedPeriod represents the planned duration of the contract -type PlannedPeriod struct { - DurationMeasure DurationMeasure `json:"duration_measure" xml:"DurationMeasure"` -} - -// DurationMeasure represents a duration with unit -type DurationMeasure struct { - UnitCode string `json:"unit_code" xml:"unitCode,attr"` - Value string `json:"value" xml:",chardata"` -} diff --git a/ted/eform/procurement_project_lot.go b/ted/eform/procurement_project_lot.go deleted file mode 100644 index 94e58b0..0000000 --- a/ted/eform/procurement_project_lot.go +++ /dev/null @@ -1,13 +0,0 @@ -package eform - -// ===================================================== -// PROCUREMENT PROJECT LOT -// ===================================================== - -// ProcurementProjectLot represents a procurement project lot -type ProcurementProjectLot struct { - ID string `json:"id" xml:"ID"` - TenderingTerms TenderingTerms `json:"tendering_terms,omitempty" xml:"TenderingTerms,omitempty"` - TenderingProcess TenderingProcess `json:"tendering_process,omitempty" xml:"TenderingProcess,omitempty"` - ProcurementProject ProcurementProject `json:"procurement_project,omitempty" xml:"ProcurementProject,omitempty"` -} diff --git a/ted/eform/review_info.go b/ted/eform/review_info.go deleted file mode 100644 index 03d26ac..0000000 --- a/ted/eform/review_info.go +++ /dev/null @@ -1,17 +0,0 @@ -package eform - -// ===================================================== -// REVIEW/APPEAL INFORMATION -// ===================================================== - -// ReviewInfo represents information about review procedures -type ReviewInfo struct { - ReviewBody string `json:"review_body" xml:"review_body" validate:"required"` - ReviewBodyID string `json:"review_body_id" xml:"review_body_id" validate:"required,orgIDFormat"` - ReviewProcedure string `json:"review_procedure" xml:"review_procedure" validate:"required,max=10000"` - ReviewDeadline string `json:"review_deadline,omitempty" xml:"review_deadline,omitempty" validate:"max=1000"` - MediationBody string `json:"mediation_body,omitempty" xml:"mediation_body,omitempty"` - MediationBodyID string `json:"mediation_body_id,omitempty" xml:"mediation_body_id,omitempty" validate:"omitempty,orgIDFormat"` - ReviewInfoURL string `json:"review_info_url,omitempty" xml:"review_info_url,omitempty" validate:"omitempty,url"` - LodgingOfAppeals string `json:"lodging_of_appeals,omitempty" xml:"lodging_of_appeals,omitempty" validate:"max=10000"` -} diff --git a/ted/eform/settled_contract.go b/ted/eform/settled_contract.go deleted file mode 100644 index a3ce3c3..0000000 --- a/ted/eform/settled_contract.go +++ /dev/null @@ -1,26 +0,0 @@ -package eform - -import "time" - -// ===================================================== -// SETTLED CONTRACT STRUCTURE -// ===================================================== - -// SettledContract represents awarded contract details -type SettledContract struct { - ID string `json:"id" xml:"id" validate:"required,conIDFormat"` - ContractNumber string `json:"contract_number,omitempty" xml:"contract_number,omitempty" validate:"max=100"` - Title string `json:"title" xml:"title" validate:"required,max=500"` - ConclusionDate time.Time `json:"conclusion_date" xml:"conclusion_date" validate:"required"` - ContractValue Money `json:"contract_value" xml:"contract_value" validate:"required"` - InitialValue *Money `json:"initial_value,omitempty" xml:"initial_value,omitempty"` - FrameworkMaxValue *Money `json:"framework_max_value,omitempty" xml:"framework_max_value,omitempty"` - StartDate *time.Time `json:"start_date,omitempty" xml:"start_date,omitempty"` - EndDate *time.Time `json:"end_date,omitempty" xml:"end_date,omitempty"` - Duration *Duration `json:"duration,omitempty" xml:"duration,omitempty"` - ContractorIDs []string `json:"contractor_ids" xml:"contractor_ids" validate:"required,min=1"` - Modifications []Modification `json:"modifications,omitempty" xml:"modifications,omitempty" validate:"dive"` - FinalValue *Money `json:"final_value,omitempty" xml:"final_value,omitempty"` - CompletionDate *time.Time `json:"completion_date,omitempty" xml:"completion_date,omitempty"` - GroupID string `json:"group_id,omitempty" xml:"group_id,omitempty"` -} diff --git a/ted/eform/technical_specifications.go b/ted/eform/technical_specifications.go deleted file mode 100644 index 95189eb..0000000 --- a/ted/eform/technical_specifications.go +++ /dev/null @@ -1,43 +0,0 @@ -package eform - -// ===================================================== -// TECHNICAL SPECIFICATIONS -// ===================================================== - -// TechnicalSpecifications contains technical and eligibility requirements -type TechnicalSpecifications struct { - Description string `json:"description" xml:"description" validate:"required,max=10000"` - EligibilityCriteria string `json:"eligibility_criteria,omitempty" xml:"eligibility_criteria,omitempty" validate:"max=10000"` - TechnicalCapability string `json:"technical_capability,omitempty" xml:"technical_capability,omitempty" validate:"max=10000"` - EconomicCapability string `json:"economic_capability,omitempty" xml:"economic_capability,omitempty" validate:"max=10000"` - References bool `json:"references" xml:"references"` - ReferencesDescription string `json:"references_description,omitempty" xml:"references_description,omitempty" validate:"required_if=References true"` - SecurityClearance bool `json:"security_clearance" xml:"security_clearance"` - SecurityClearanceDescription string `json:"security_clearance_description,omitempty" xml:"security_clearance_description,omitempty" validate:"required_if=SecurityClearance true"` - ReservedExecution ReservedExecution `json:"reserved_execution" xml:"reserved_execution"` - StrategicProcurement []StrategicType `json:"strategic_procurement,omitempty" xml:"strategic_procurement,omitempty"` - AccessibilityCriteria bool `json:"accessibility_criteria" xml:"accessibility_criteria"` - AccessibilityDescription string `json:"accessibility_description,omitempty" xml:"accessibility_description,omitempty" validate:"max=10000"` - GreenProcurement bool `json:"green_procurement" xml:"green_procurement"` - SocialProcurement bool `json:"social_procurement" xml:"social_procurement"` - InnovativeProcurement bool `json:"innovative_procurement" xml:"innovative_procurement"` -} - -// ReservedExecution represents reserved participation types -type ReservedExecution string - -const ( - ReservedNone ReservedExecution = "none" - ReservedShelteredWS ReservedExecution = "sheltered-workshop" - ReservedPublicService ReservedExecution = "public-service-mission" -) - -// StrategicType represents strategic procurement objectives -type StrategicType string - -const ( - StrategicEnvironmental StrategicType = "environmental" - StrategicSocial StrategicType = "social" - StrategicInnovation StrategicType = "innovation" - StrategicOther StrategicType = "other" -) diff --git a/ted/eform/tender.go b/ted/eform/tender.go deleted file mode 100644 index 65ee548..0000000 --- a/ted/eform/tender.go +++ /dev/null @@ -1,24 +0,0 @@ -package eform - -// ===================================================== -// TENDER STRUCTURE -// ===================================================== - -// Tender represents information about individual bids -type Tender struct { - ID string `json:"id" xml:"id" validate:"required,tenIDFormat"` - TenderRank *int `json:"tender_rank,omitempty" xml:"tender_rank,omitempty" validate:"omitempty,min=1"` - IsWinner bool `json:"is_winner" xml:"is_winner"` - TendererIDs []string `json:"tenderer_ids" xml:"tenderer_ids" validate:"required,min=1"` - IsConsortium bool `json:"is_consortium" xml:"is_consortium"` - VariantTender bool `json:"variant_tender" xml:"variant_tender"` - ExcludedTender bool `json:"excluded_tender" xml:"excluded_tender"` - ExclusionReason string `json:"exclusion_reason,omitempty" xml:"exclusion_reason,omitempty" validate:"required_if=ExcludedTender true"` - TenderValue *Money `json:"tender_value,omitempty" xml:"tender_value,omitempty"` - SubcontractingPercentage *float64 `json:"subcontracting_percentage,omitempty" xml:"subcontracting_percentage,omitempty" validate:"omitempty,min=0,max=100"` - SMETender bool `json:"sme_tender" xml:"sme_tender"` - ConcessionValue *Money `json:"concession_value,omitempty" xml:"concession_value,omitempty"` - SubcontractorIDs []string `json:"subcontractor_ids,omitempty" xml:"subcontractor_ids,omitempty"` - AbnormallyLow bool `json:"abnormally_low" xml:"abnormally_low"` - AbnormallyLowExplanation string `json:"abnormally_low_explanation,omitempty" xml:"abnormally_low_explanation,omitempty" validate:"max=10000"` -} diff --git a/ted/eform/tendering_process.go b/ted/eform/tendering_process.go deleted file mode 100644 index 58f9264..0000000 --- a/ted/eform/tendering_process.go +++ /dev/null @@ -1,28 +0,0 @@ -package eform - -import "encoding/xml" - -// ===================================================== -// TENDERING PROCESS -// ===================================================== - -// TenderingProcess represents the tendering process -type TenderingProcess struct { - XMLName xml.Name `xml:"TenderingProcess"` - ProcedureCode string `xml:"ProcedureCode"` - ProcessJustification ProcessJustification `xml:"ProcessJustification,omitempty"` - TenderSubmissionDeadlinePeriod TenderSubmissionDeadlinePeriod `xml:"TenderSubmissionDeadlinePeriod,omitempty"` -} - -// ProcessJustification represents process justification -type ProcessJustification struct { - XMLName xml.Name `xml:"ProcessJustification"` - ProcessReasonCode string `xml:"ProcessReasonCode"` -} - -// TenderSubmissionDeadlinePeriod represents the deadline for tender submission -type TenderSubmissionDeadlinePeriod struct { - XMLName xml.Name `xml:"TenderSubmissionDeadlinePeriod"` - EndDate string `xml:"EndDate"` - EndTime string `xml:"EndTime,omitempty"` -} diff --git a/ted/eform/tendering_terms.go b/ted/eform/tendering_terms.go deleted file mode 100644 index 8cdeddb..0000000 --- a/ted/eform/tendering_terms.go +++ /dev/null @@ -1,85 +0,0 @@ -package eform - -import ( - "encoding/xml" - "time" -) - -// ===================================================== -// TENDERING TERMS -// ===================================================== - -// TenderingTerms represents tendering terms -type TenderingTerms struct { - TendererQualificationRequest []TendererQualificationRequest `json:"tenderer_qualification_request,omitempty" xml:"TendererQualificationRequest,omitempty"` - AppealTerms AppealTerms `json:"appeal_terms,omitempty" xml:"AppealTerms,omitempty"` - CallForTendersDocumentReference CallForTendersDocumentReference `json:"call_for_tenders_document_reference,omitempty" xml:"CallForTendersDocumentReference,omitempty"` - TenderRecipientParty TenderRecipientParty `json:"tender_recipient_party,omitempty" xml:"TenderRecipientParty,omitempty"` - UBLExtensions UBLExtensions `json:"ubl_extensions,omitempty" xml:"UBLExtensions,omitempty"` -} - -// TendererQualificationRequest represents qualification requirements -type TendererQualificationRequest struct { - SpecificTendererRequirement []SpecificTendererRequirement `json:"specific_tenderer_requirement,omitempty" xml:"SpecificTendererRequirement,omitempty"` -} - -// SpecificTendererRequirement represents specific requirements -type SpecificTendererRequirement struct { - XMLName xml.Name `xml:"SpecificTendererRequirement"` - TendererRequirementTypeCode string `xml:"TendererRequirementTypeCode"` - Description string `xml:"Description,omitempty"` -} - -// AppealTerms represents appeal terms for review organization -type AppealTerms struct { - XMLName xml.Name `xml:"AppealTerms"` - AppealReceiverParty AppealReceiverParty `xml:"AppealReceiverParty"` -} - -// AppealReceiverParty represents the party that receives appeals -type AppealReceiverParty struct { - XMLName xml.Name `xml:"AppealReceiverParty"` - PartyIdentification PartyIdentification `xml:"PartyIdentification"` -} - -// CallForTendersDocumentReference represents document reference -type CallForTendersDocumentReference struct { - XMLName xml.Name `xml:"CallForTendersDocumentReference"` - ID string `xml:"ID"` - DocumentType string `xml:"DocumentType,omitempty"` - Attachment Attachment `xml:"Attachment"` - UBLExtensions UBLExtensions `xml:"UBLExtensions,omitempty"` -} - -// Attachment represents document attachment -type Attachment struct { - ExternalReference ExternalReference `json:"external_reference" xml:"ExternalReference"` -} - -// ExternalReference represents external document reference -type ExternalReference struct { - XMLName xml.Name `xml:"ExternalReference"` - URI string `xml:"URI"` -} - -// TenderRecipientParty represents the party that receives tenders -type TenderRecipientParty struct { - XMLName xml.Name `xml:"TenderRecipientParty"` - EndpointID string `xml:"EndpointID"` -} - -// EOSize represents economic operator size restrictions -type EOSize string - -const ( - EOSizeNone EOSize = "none" - EOSizeSME EOSize = "sme" - EOSizeNotSME EOSize = "not-sme" -) - -// TenderOpening represents tender opening information -type TenderOpening struct { - DateTime time.Time `json:"date_time" xml:"date_time" validate:"required"` - Place string `json:"place,omitempty" xml:"place,omitempty" validate:"max=500"` - Description string `json:"description,omitempty" xml:"description,omitempty" validate:"max=2000"` -} diff --git a/ted/eform/ubl.go b/ted/eform/ubl.go deleted file mode 100644 index e3ffc63..0000000 --- a/ted/eform/ubl.go +++ /dev/null @@ -1,71 +0,0 @@ -package eform - -import "encoding/xml" - -// ===================================================== -// UBL EXTENSIONS -// ===================================================== - -// UBLExtensions represents the UBL extensions element -type UBLExtensions struct { - UBLExtension []UBLExtension `json:"ubl_extension,omitempty" xml:"UBLExtension,omitempty"` -} - -// UBLExtension represents a single UBL extension -type UBLExtension struct { - ExtensionContent ExtensionContent `json:"extension_content,omitempty" xml:"ExtensionContent,omitempty"` -} - -// ExtensionContent represents the extension content -type ExtensionContent struct { - EformsExtension EformsExtension `json:"eforms_extension,omitempty" xml:"EformsExtension,omitempty"` -} - -// EformsExtension represents the eforms extension data -type EformsExtension struct { - NoticeSubType NoticeSubType `json:"notice_sub_type,omitempty" xml:"NoticeSubType,omitempty"` - Organizations Organizations `json:"organizations,omitempty" xml:"Organizations,omitempty"` - Publication Publication `json:"publication,omitempty" xml:"Publication,omitempty"` - SelectionCriteria []SelectionCriteria `json:"selection_criteria,omitempty" xml:"SelectionCriteria,omitempty"` - OfficialLanguages OfficialLanguages `json:"official_languages,omitempty" xml:"OfficialLanguages,omitempty"` -} - -// NoticeSubType represents the notice subtype -type NoticeSubType struct { - XMLName xml.Name `xml:"NoticeSubType"` - SubTypeCode string `xml:"SubTypeCode"` -} - -// Organizations represents the organizations element -type Organizations struct { - XMLName xml.Name `xml:"Organizations"` - Organization []Organization `xml:"Organization,omitempty"` -} - -// Publication represents the publication information -type Publication struct { - XMLName xml.Name `xml:"Publication"` - NoticePublicationID string `xml:"NoticePublicationID"` - GazetteID string `xml:"GazetteID"` - PublicationDate string `xml:"PublicationDate"` -} - -// SelectionCriteria represents selection criteria for tenderers -type SelectionCriteria struct { - XMLName xml.Name `xml:"SelectionCriteria"` - TendererRequirementTypeCode string `xml:"TendererRequirementTypeCode"` - Description string `xml:"Description,omitempty"` - SecondStageIndicator bool `xml:"SecondStageIndicator,omitempty"` -} - -// OfficialLanguages represents official languages for the tender -type OfficialLanguages struct { - XMLName xml.Name `xml:"OfficialLanguages"` - Language []Language `xml:"Language,omitempty"` -} - -// Language represents a language code -type Language struct { - XMLName xml.Name `xml:"Language"` - ID string `xml:"ID"` -} diff --git a/ted/eform/validation.go b/ted/eform/validation.go deleted file mode 100644 index 421fa17..0000000 --- a/ted/eform/validation.go +++ /dev/null @@ -1,19 +0,0 @@ -package eform - -// ===================================================== -// VALIDATION HELPER TYPES -// ===================================================== - -// ValidationError represents a validation error -type ValidationError struct { - Field string `json:"field"` - Tag string `json:"tag"` - Value string `json:"value"` - Message string `json:"message"` -} - -// ValidationResult represents the result of validation -type ValidationResult struct { - Valid bool `json:"valid"` - Errors []ValidationError `json:"errors,omitempty"` -} diff --git a/ted/mapper.go b/ted/mapper.go new file mode 100644 index 0000000..2134d86 --- /dev/null +++ b/ted/mapper.go @@ -0,0 +1,367 @@ +package ted + +import ( + "strconv" + "strings" + "time" + "tm/internal/tender" +) + +// mapToTenderFromParsedDoc maps a parsed TED document to tender entity +func (s *TEDScraper) mapToTenderFromParsedDoc(parsedDoc *ParsedDocument, sourceFileURL, sourceFileName string) *tender.Tender { + switch parsedDoc.Type { + case DocumentTypeContractNotice: + if parsedDoc.ContractNotice == nil { + s.logger.Error("ContractNotice is nil", map[string]interface{}{ + "document_type": string(parsedDoc.Type), + }) + return nil + } + return s.mapToTender(parsedDoc.ContractNotice, sourceFileURL, sourceFileName) + + case DocumentTypeContractAwardNotice: + if parsedDoc.ContractAwardNotice == nil { + s.logger.Error("ContractAwardNotice is nil", map[string]interface{}{ + "document_type": string(parsedDoc.Type), + }) + return nil + } + return s.mapContractAwardNoticeToTender(parsedDoc.ContractAwardNotice, sourceFileURL, sourceFileName) + + case DocumentTypePriorInformationNotice: + if parsedDoc.PriorInformationNotice == nil { + s.logger.Error("PriorInformationNotice is nil", map[string]interface{}{ + "document_type": string(parsedDoc.Type), + }) + return nil + } + return s.mapGenericNoticeToTender(parsedDoc.PriorInformationNotice, sourceFileURL, sourceFileName) + default: + s.logger.Error("Unknown document type for mapping", map[string]interface{}{ + "document_type": string(parsedDoc.Type), + }) + return nil + } +} + +// mapGenericNoticeToTender maps any TED document type to tender entity using the common interface +func (s *TEDScraper) mapGenericNoticeToTender(doc TEDDocument, sourceFileURL, sourceFileName string) *tender.Tender { + if doc == nil { + s.logger.Error("Document is nil in mapGenericNoticeToTender", map[string]interface{}{}) + return nil + } + + now := time.Now().Unix() + + t := &tender.Tender{ + ContractNoticeID: doc.GetID(), + ContractFolderID: "", // Not available in generic interface + NoticeTypeCode: doc.GetNoticeType(), + NoticeSubTypeCode: "", // Not available in generic interface + NoticeLanguageCode: doc.GetLanguage(), + IssueDate: now, // TODO: Parse actual date from document + IssueTime: now, // TODO: Parse actual time from document + GazetteID: "", // Not available in generic interface + NoticePublicationID: "", // Not available in generic interface + PublicationDate: now, // TODO: Parse actual publication date + + // Basic tender information + Title: "Generic Notice " + doc.GetNoticeType(), + Description: "Processed from " + doc.GetNoticeType() + " document", + Status: tender.TenderStatusActive, + Source: tender.TenderSourceTEDScraper, + SourceFileURL: sourceFileURL, + SourceFileName: sourceFileName, + ProcessingMetadata: tender.ProcessingMetadata{ + ScrapedAt: now, + ProcessedAt: now, + ProcessingVersion: "1.0", + }, + } + + // Generate the tender ID after creating the tender + t.TenderID = GenerateTenderID(t) + + return t +} + +// mapToTender maps a TED ContractNotice to tender entity +func (s *TEDScraper) mapToTender(cn *ContractNotice, sourceFileURL, sourceFileName string) *tender.Tender { + now := time.Now().Unix() + + t := &tender.Tender{ + ContractNoticeID: cn.ID, + ContractFolderID: cn.ContractFolderID, + NoticeTypeCode: cn.GetNoticeSubType(), + NoticeSubTypeCode: cn.GetNoticeSubType(), + NoticeLanguageCode: cn.NoticeLanguageCode, + IssueDate: ParseDateToUnix(cn.IssueDate), + IssueTime: ParseTimeToUnix(cn.IssueTime), + GazetteID: cn.GetOJSID(), + Title: cn.GetProcurementTitle(), + Description: cn.GetProcurementDescription(), + ProcurementTypeCode: cn.GetContractNature(), + ProcedureCode: cn.GetProcedureCode(), + MainClassification: cn.GetMainClassification(), + PlaceOfPerformance: cn.GetPlaceOfPerformance(), + DocumentURI: cn.GetDocumentURI(), + Status: tender.TenderStatusActive, + Source: tender.TenderSourceTEDScraper, + SourceFileURL: sourceFileURL, + SourceFileName: sourceFileName, + ProcessingMetadata: tender.ProcessingMetadata{ + ScrapedAt: now, + ProcessedAt: now, + ProcessingVersion: "1.0", + }, + } + + // Set publication information + if pubInfo := cn.GetPublicationInfo(); pubInfo != nil { + t.NoticePublicationID = pubInfo.NoticePublicationID + t.PublicationDate = ParseDateToUnix(pubInfo.PublicationDate) + t.TenderURL = GenerateTenderURL(pubInfo.NoticePublicationID) + } + + // Set additional classifications + if cn.ProcurementProject != nil && cn.ProcurementProject.AdditionalCommodityClassification != nil { + for _, additionalClass := range cn.ProcurementProject.AdditionalCommodityClassification { + if additionalClass.ItemClassificationCode != "" { + t.AdditionalClassifications = append(t.AdditionalClassifications, additionalClass.ItemClassificationCode) + } + } + } + + // Set estimated value and currency + if amount, currency := cn.GetBudget(); amount != "" { + if value, err := strconv.ParseFloat(amount, 64); err == nil { + t.EstimatedValue = value + t.Currency = currency + } + } + + // Set duration information + if duration, unit := cn.GetDuration(); duration != "" { + t.Duration = duration + t.DurationUnit = unit + } + + // Set tender deadline + if deadline := cn.GetTenderDeadline(); deadline != "" { + t.TenderDeadline = ParseDateToUnix(deadline) + + if deadlineTime, err := ParseDate(deadline); err == nil { + applicationDeadline := CalculateApplicationDeadline(deadlineTime) + t.ApplicationDeadline = applicationDeadline.Unix() + } + } + + if pubDate, err := ParseDate(cn.GetPublicationDate()); err == nil { + submissionDeadline := CalculateSubmissionDeadline(pubDate) + t.SubmissionDeadline = submissionDeadline.Unix() + } + + // Set SubmissionURL from TenderingTerms + if cn.ProcurementProjectLot != nil && cn.ProcurementProjectLot.TenderingTerms != nil { + if tenderingTerms := cn.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 cn.ProcurementProject != nil && cn.ProcurementProject.RealizedLocation != nil { + if addr := cn.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 := cn.GetBuyerID() + if buyerID != "" { + if buyerOrg := cn.GetOrganizationByID(buyerID); buyerOrg != nil { + t.BuyerOrganization = s.mapOrganization(buyerOrg, "buyer") + } + } + + // Set review organization + reviewID := cn.GetReviewOrganizationID() + if reviewID != "" { + if reviewOrg := cn.GetOrganizationByID(reviewID); reviewOrg != nil { + t.ReviewOrganization = s.mapOrganization(reviewOrg, "review") + } + } + + // Set all organizations + if orgs := cn.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 := cn.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 := cn.GetOfficialLanguages(); languages != nil { + t.OfficialLanguages = languages + } + + // Generate unique tender ID + t.TenderID = GenerateTenderID(t) + + return t +} + +// mapContractAwardNoticeToTender maps a TED ContractAwardNotice to tender entity +func (s *TEDScraper) mapContractAwardNoticeToTender(can *ContractAwardNotice, sourceFileURL, sourceFileName string) *tender.Tender { + now := time.Now().Unix() + + t := &tender.Tender{ + ContractNoticeID: can.ID, + ContractFolderID: can.ContractFolderID, + NoticeTypeCode: can.NoticeTypeCode, + NoticeSubTypeCode: can.GetNoticeSubType(), + NoticeLanguageCode: can.NoticeLanguageCode, + IssueDate: ParseDateToUnix(can.IssueDate), + IssueTime: ParseTimeToUnix(can.IssueTime), + GazetteID: can.GetOJSID(), + Title: can.GetProcurementTitle(), + Description: can.GetProcurementDescription(), + ProcurementTypeCode: can.GetContractNature(), + ProcedureCode: can.GetProcedureCode(), + MainClassification: can.GetMainClassification(), + Status: tender.TenderStatusAwarded, // Award notices are for completed tenders + Source: tender.TenderSourceTEDScraper, + SourceFileURL: sourceFileURL, + SourceFileName: sourceFileName, + ProcessingMetadata: tender.ProcessingMetadata{ + ScrapedAt: now, + ProcessedAt: now, + ProcessingVersion: "1.0", + }, + } + + // Set publication information + if pubInfo := can.GetPublicationInfo(); pubInfo != nil { + t.NoticePublicationID = pubInfo.NoticePublicationID + t.PublicationDate = ParseDateToUnix(pubInfo.PublicationDate) + t.TenderURL = GenerateTenderURL(pubInfo.NoticePublicationID) + } + + // Set total award value + if amount, currency := can.GetTotalAwardValue(); amount != "" { + if parsedAmount, err := strconv.ParseFloat(amount, 64); err == nil { + t.EstimatedValue = parsedAmount + } + t.Currency = currency + } + + // Set location information from main procurement project + if can.ProcurementProject != nil && can.ProcurementProject.RealizedLocation != nil { + if addr := can.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 := can.GetBuyerID() + if buyerID != "" { + if buyerOrg := can.GetOrganizationByID(buyerID); buyerOrg != nil { + t.BuyerOrganization = s.mapOrganization(buyerOrg, "buyer") + } + } + + // Set all organizations + if orgs := can.GetOrganizations(); orgs != nil { + for _, org := range orgs.Organization { + mappedOrg := s.mapOrganization(&org, "") + if mappedOrg != nil { + t.Organizations = append(t.Organizations, *mappedOrg) + } + } + } + + // Generate unique tender ID + t.TenderID = GenerateTenderID(t) + + return t +} + +// mapOrganization maps a TED Organization to tender Organization +func (s *TEDScraper) mapOrganization(tedOrg *Organization, role string) *tender.Organization { + if tedOrg == nil || tedOrg.Company == nil { + return nil + } + + company := tedOrg.Company + org := &tender.Organization{ + Role: role, + } + + // Set organization ID and name + if company.PartyIdentification != nil { + org.ID = company.PartyIdentification.ID + } + if company.PartyName != nil { + org.Name = company.PartyName.Name + } + + // Set company ID + if company.PartyLegalEntity != nil { + org.CompanyID = company.PartyLegalEntity.CompanyID + } + + // Set website + org.WebsiteURI = company.WebsiteURI + + // Set contact information + if company.Contact != nil { + org.ContactName = company.Contact.Name + org.ContactTelephone = company.Contact.Telephone + org.ContactEmail = company.Contact.ElectronicMail + org.ContactFax = company.Contact.Telefax + } + + // Set address + if company.PostalAddress != nil { + org.Address = tender.Address{ + StreetName: company.PostalAddress.StreetName, + CityName: company.PostalAddress.CityName, + PostalZone: company.PostalAddress.PostalZone, + CountrySubentityCode: company.PostalAddress.CountrySubentityCode, + Department: company.PostalAddress.Department, + } + if company.PostalAddress.Country != nil { + org.Address.CountryCode = company.PostalAddress.Country.IdentificationCode + } + } + + return org +} diff --git a/ted/model.go b/ted/model.go new file mode 100644 index 0000000..9315b30 --- /dev/null +++ b/ted/model.go @@ -0,0 +1,1884 @@ +package ted + +import ( + "encoding/xml" + "fmt" + "time" +) + +// DocumentType represents the type of TED document +type DocumentType string + +// Document type constants for all supported TED XML notice types +const ( + DocumentTypeContractNotice DocumentType = "ContractNotice" + DocumentTypeContractAwardNotice DocumentType = "ContractAwardNotice" + DocumentTypePriorInformationNotice DocumentType = "PriorInformationNotice" + DocumentTypeBusinessRegistrationInformationNotice DocumentType = "BusinessRegistrationInformationNotice" +) + +// ParsedDocument wraps any TED document with type information +type ParsedDocument struct { + Type DocumentType + ContractNotice *ContractNotice `json:"contract_notice,omitempty"` + ContractAwardNotice *ContractAwardNotice `json:"contract_award_notice,omitempty"` + PriorInformationNotice *PriorInformationNotice `json:"prior_information_notice,omitempty"` + // BusinessRegistrationInformationNotice *BusinessRegistrationInformationNotice `json:"business_registration_information_notice,omitempty"` +} + +// GetDocument returns the underlying document interface +func (pd *ParsedDocument) GetDocument() TEDDocument { + switch pd.Type { + case DocumentTypeContractNotice: + return pd.ContractNotice + case DocumentTypeContractAwardNotice: + return pd.ContractAwardNotice + case DocumentTypePriorInformationNotice: + return pd.PriorInformationNotice + default: + return nil + } +} + +// TEDDocument represents the common interface for all TED document types +type TEDDocument interface { + GetID() string + GetNoticeType() string + GetLanguage() string + Validate() error +} + +// PriorInformationNotice represents a prior information notice +type PriorInformationNotice struct { + XMLName xml.Name `xml:"PriorInformationNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` + AdditionalDocumentReference []AdditionalDocumentReference `xml:"AdditionalDocumentReference,omitempty"` +} + +// Interface methods for PriorInformationNotice +func (pin PriorInformationNotice) GetID() string { + return pin.ID +} + +func (pin PriorInformationNotice) GetNoticeType() string { + return pin.NoticeTypeCode +} + +func (pin PriorInformationNotice) GetLanguage() string { + return pin.NoticeLanguageCode +} + +func (pin PriorInformationNotice) Validate() error { + if pin.ID == "" { + return fmt.Errorf("prior information notice ID is required") + } + if pin.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// DesignContest represents a design contest notice +type DesignContest struct { + XMLName xml.Name `xml:"DesignContest"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ContestDesignJury *ContestDesignJury `xml:"ContestDesignJury,omitempty"` + ContestDesignReward []ContestDesignReward `xml:"ContestDesignReward,omitempty"` +} + +// Interface methods for DesignContest +func (dc DesignContest) GetID() string { return dc.ID } +func (dc DesignContest) GetNoticeType() string { return dc.NoticeTypeCode } +func (dc DesignContest) GetLanguage() string { return dc.NoticeLanguageCode } +func (dc DesignContest) Validate() error { + if dc.ID == "" { + return fmt.Errorf("design contest ID is required") + } + if dc.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// QualificationSystemNotice represents a qualification system notice +type QualificationSystemNotice struct { + XMLName xml.Name `xml:"QualificationSystemNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + QualificationApplication []QualificationApplication `xml:"QualificationApplication,omitempty"` +} + +// Interface methods for QualificationSystemNotice +func (qsn QualificationSystemNotice) GetID() string { return qsn.ID } +func (qsn QualificationSystemNotice) GetNoticeType() string { return qsn.NoticeTypeCode } +func (qsn QualificationSystemNotice) GetLanguage() string { return qsn.NoticeLanguageCode } +func (qsn QualificationSystemNotice) Validate() error { + if qsn.ID == "" { + return fmt.Errorf("qualification system notice ID is required") + } + if qsn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// ConcessionNotice represents a concession notice +type ConcessionNotice struct { + XMLName xml.Name `xml:"ConcessionNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` + ConcessionRevenue *ConcessionRevenue `xml:"ConcessionRevenue,omitempty"` + ConcessionTerm *ConcessionTerm `xml:"ConcessionTerm,omitempty"` +} + +// Interface methods for ConcessionNotice +func (cn ConcessionNotice) GetID() string { return cn.ID } +func (cn ConcessionNotice) GetNoticeType() string { return cn.NoticeTypeCode } +func (cn ConcessionNotice) GetLanguage() string { return cn.NoticeLanguageCode } +func (cn ConcessionNotice) Validate() error { + if cn.ID == "" { + return fmt.Errorf("concession notice ID is required") + } + if cn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// PlanningNotice represents a planning notice +type PlanningNotice struct { + XMLName xml.Name `xml:"PlanningNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` + BudgetAccountLine []BudgetAccountLine `xml:"BudgetAccountLine,omitempty"` + PlannedPeriod *PlannedPeriod `xml:"PlannedPeriod,omitempty"` +} + +// Interface methods for PlanningNotice +func (pn PlanningNotice) GetID() string { return pn.ID } +func (pn PlanningNotice) GetNoticeType() string { return pn.NoticeTypeCode } +func (pn PlanningNotice) GetLanguage() string { return pn.NoticeLanguageCode } +func (pn PlanningNotice) Validate() error { + if pn.ID == "" { + return fmt.Errorf("planning notice ID is required") + } + if pn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// CompetitionNotice represents a competition notice +type CompetitionNotice struct { + XMLName xml.Name `xml:"CompetitionNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` +} + +// Interface methods for CompetitionNotice +func (cn CompetitionNotice) GetID() string { return cn.ID } +func (cn CompetitionNotice) GetNoticeType() string { return cn.NoticeTypeCode } +func (cn CompetitionNotice) GetLanguage() string { return cn.NoticeLanguageCode } +func (cn CompetitionNotice) Validate() error { + if cn.ID == "" { + return fmt.Errorf("competition notice ID is required") + } + if cn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// ResultNotice represents a result notice (eForms specific) +type ResultNotice struct { + XMLName xml.Name `xml:"ResultNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` + TenderResult []TenderResult `xml:"TenderResult,omitempty"` + SettledContract []SettledContract `xml:"SettledContract,omitempty"` +} + +// Interface methods for ResultNotice +func (rn ResultNotice) GetID() string { return rn.ID } +func (rn ResultNotice) GetNoticeType() string { return rn.NoticeTypeCode } +func (rn ResultNotice) GetLanguage() string { return rn.NoticeLanguageCode } +func (rn ResultNotice) Validate() error { + if rn.ID == "" { + return fmt.Errorf("result notice ID is required") + } + if rn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// ChangeNotice represents a change notice (eForms specific) +type ChangeNotice struct { + XMLName xml.Name `xml:"ChangeNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` + NoticeVersionIdentifier string `xml:"NoticeVersionIdentifier,omitempty"` + ChangeReason []ChangeReason `xml:"ChangeReason,omitempty"` +} + +// Interface methods for ChangeNotice +func (cn ChangeNotice) GetID() string { return cn.ID } +func (cn ChangeNotice) GetNoticeType() string { return cn.NoticeTypeCode } +func (cn ChangeNotice) GetLanguage() string { return cn.NoticeLanguageCode } +func (cn ChangeNotice) Validate() error { + if cn.ID == "" { + return fmt.Errorf("change notice ID is required") + } + if cn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// SubcontractNotice represents a subcontract notice +type SubcontractNotice struct { + XMLName xml.Name `xml:"SubcontractNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + SubcontractCall []SubcontractCall `xml:"SubcontractCall,omitempty"` + ParentContract *ParentContract `xml:"ParentContract,omitempty"` +} + +// Interface methods for SubcontractNotice +func (sn SubcontractNotice) GetID() string { return sn.ID } +func (sn SubcontractNotice) GetNoticeType() string { return sn.NoticeTypeCode } +func (sn SubcontractNotice) GetLanguage() string { return sn.NoticeLanguageCode } +func (sn SubcontractNotice) Validate() error { + if sn.ID == "" { + return fmt.Errorf("subcontract notice ID is required") + } + if sn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + return nil +} + +// Additional structures for new notice types + +// ContestDesignJury contains design contest jury information +type ContestDesignJury struct { + XMLName xml.Name `xml:"ContestDesignJury"` + JuryDecisionDate string `xml:"JuryDecisionDate,omitempty"` + JuryDecision string `xml:"JuryDecision,omitempty"` + JuryMember []JuryMember `xml:"JuryMember,omitempty"` + JudgingCriteria *JudgingCriteria `xml:"JudgingCriteria,omitempty"` +} + +// ContestDesignReward contains design contest reward information +type ContestDesignReward struct { + XMLName xml.Name `xml:"ContestDesignReward"` + RewardCode string `xml:"RewardCode,omitempty"` + Benefit *Benefit `xml:"Benefit,omitempty"` +} + +// JuryMember contains jury member information +type JuryMember struct { + XMLName xml.Name `xml:"JuryMember"` + Party *Party `xml:"Party,omitempty"` +} + +// JudgingCriteria contains judging criteria information +type JudgingCriteria struct { + XMLName xml.Name `xml:"JudgingCriteria"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// Benefit contains benefit information +type Benefit struct { + XMLName xml.Name `xml:"Benefit"` + BenefitValue CurrencyText `xml:"BenefitValue,omitempty"` +} + +// QualificationApplication contains qualification application information +type QualificationApplication struct { + XMLName xml.Name `xml:"QualificationApplication"` + QualificationTypeCode string `xml:"QualificationTypeCode,omitempty"` + ApplicationPeriod *ApplicationPeriod `xml:"ApplicationPeriod,omitempty"` + TendererQualificationRequest *TendererQualificationRequest `xml:"TendererQualificationRequest,omitempty"` +} + +// ApplicationPeriod contains application period information +type ApplicationPeriod struct { + XMLName xml.Name `xml:"ApplicationPeriod"` + StartDate string `xml:"StartDate,omitempty"` + EndDate string `xml:"EndDate,omitempty"` + StartTime string `xml:"StartTime,omitempty"` + EndTime string `xml:"EndTime,omitempty"` +} + +// ConcessionRevenue contains concession revenue information +type ConcessionRevenue struct { + XMLName xml.Name `xml:"ConcessionRevenue"` + RevenueType string `xml:"RevenueType,omitempty"` + RevenueAmount CurrencyText `xml:"RevenueAmount,omitempty"` +} + +// ConcessionTerm contains concession term information +type ConcessionTerm struct { + XMLName xml.Name `xml:"ConcessionTerm"` + ConcessionValue CurrencyText `xml:"ConcessionValue,omitempty"` + Duration *DurationMeasure `xml:"DurationMeasure,omitempty"` +} + +// BudgetAccountLine contains budget account line information +type BudgetAccountLine struct { + XMLName xml.Name `xml:"BudgetAccountLine"` + ID string `xml:"ID,omitempty"` + BudgetYear string `xml:"BudgetYear,omitempty"` + TotalAmount CurrencyText `xml:"TotalAmount,omitempty"` + BudgetAccount *BudgetAccount `xml:"BudgetAccount,omitempty"` +} + +// BudgetAccount contains budget account information +type BudgetAccount struct { + XMLName xml.Name `xml:"BudgetAccount"` + BudgetCode string `xml:"BudgetCode,omitempty"` + RequiredClassificationScheme *RequiredClassificationScheme `xml:"RequiredClassificationScheme,omitempty"` +} + +// RequiredClassificationScheme contains classification scheme information +type RequiredClassificationScheme struct { + XMLName xml.Name `xml:"RequiredClassificationScheme"` + ItemClassificationCode string `xml:"ItemClassificationCode,omitempty"` +} + +// SubcontractCall contains subcontract call information +type SubcontractCall struct { + XMLName xml.Name `xml:"SubcontractCall"` + SubcontractingTypeCode string `xml:"SubcontractingTypeCode,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` +} + +// ParentContract contains parent contract information +type ParentContract struct { + XMLName xml.Name `xml:"ParentContract"` + ContractReference *ContractReference `xml:"ContractReference,omitempty"` + FrameworkAgreement *FrameworkAgreement `xml:"FrameworkAgreement,omitempty"` +} + +// FrameworkAgreement contains framework agreement information +type FrameworkAgreement struct { + XMLName xml.Name `xml:"FrameworkAgreement"` + EstimatedMaximumValue CurrencyText `xml:"EstimatedMaximumValue,omitempty"` + MaximumParticipants string `xml:"MaximumParticipants,omitempty"` + Duration *DurationMeasure `xml:"DurationMeasure,omitempty"` + SubsequentProcedureIndicator string `xml:"SubsequentProcedureIndicator,omitempty"` +} + +// AdditionalDocumentReference contains additional document reference information +type AdditionalDocumentReference struct { + XMLName xml.Name `xml:"AdditionalDocumentReference"` + ID string `xml:"ID,omitempty"` + DocumentType string `xml:"DocumentType,omitempty"` + DocumentDescription string `xml:"DocumentDescription,omitempty"` + LanguageID string `xml:"LanguageID,omitempty"` + Attachment *Attachment `xml:"Attachment,omitempty"` + ValidityPeriod *ValidityPeriod `xml:"ValidityPeriod,omitempty"` + UBLExtensions *UBLExtensions `xml:"UBLExtensions,omitempty"` +} + +// ValidityPeriod contains validity period information +type ValidityPeriod struct { + XMLName xml.Name `xml:"ValidityPeriod"` + StartDate string `xml:"StartDate,omitempty"` + EndDate string `xml:"EndDate,omitempty"` + StartTime string `xml:"StartTime,omitempty"` + EndTime string `xml:"EndTime,omitempty"` +} + +// EnhancedTenderResult structure with more comprehensive information +type EnhancedTenderResult struct { + XMLName xml.Name `xml:"TenderResult"` + AwardDate string `xml:"AwardDate"` + AwardTime string `xml:"AwardTime,omitempty"` + ReceivedTenderQuantity string `xml:"ReceivedTenderQuantity,omitempty"` + LowerTenderAmount CurrencyText `xml:"LowerTenderAmount,omitempty"` + HigherTenderAmount CurrencyText `xml:"HigherTenderAmount,omitempty"` + AwardedTenderedProject *AwardedTenderedProject `xml:"AwardedTenderedProject,omitempty"` + ContractAwardNotice *ContractAwardNotice `xml:"ContractAwardNotice,omitempty"` + SubcontractingTerm []SubcontractingTerm `xml:"SubcontractingTerm,omitempty"` + WinningTenderLot []WinningTenderLot `xml:"WinningTenderLot,omitempty"` + ReceivedSubmissionsStatistics []ReceivedSubmissionsStatistics `xml:"ReceivedSubmissionsStatistics,omitempty"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` +} + +// AwardedTenderedProject contains awarded tendered project information +type AwardedTenderedProject struct { + XMLName xml.Name `xml:"AwardedTenderedProject"` + ProcurementTypeCode string `xml:"ProcurementTypeCode,omitempty"` + TotalAmount CurrencyText `xml:"TotalAmount,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + LegalMonetaryTotal *LegalMonetaryTotal `xml:"LegalMonetaryTotal,omitempty"` + TenderLot []TenderLot `xml:"TenderLot,omitempty"` +} + +// WinningTenderLot contains winning tender lot information +type WinningTenderLot struct { + XMLName xml.Name `xml:"WinningTenderLot"` + TenderLot *TenderLot `xml:"TenderLot,omitempty"` + WinningTender []WinningTender `xml:"WinningTender,omitempty"` +} + +// WinningTender contains winning tender information +type WinningTender struct { + XMLName xml.Name `xml:"WinningTender"` + ID string `xml:"ID"` + TenderVariantIndicator string `xml:"TenderVariantIndicator,omitempty"` + LegalMonetaryTotal *LegalMonetaryTotal `xml:"LegalMonetaryTotal,omitempty"` + TenderingParty *TenderingParty `xml:"TenderingParty,omitempty"` + SubcontractingTerm []SubcontractingTerm `xml:"SubcontractingTerm,omitempty"` +} + +// Enhanced SubcontractingTerm structure +type SubcontractingTerm struct { + XMLName xml.Name `xml:"SubcontractingTerm"` + TermCode string `xml:"TermCode"` + Amount CurrencyText `xml:"Amount,omitempty"` + Rate string `xml:"Rate,omitempty"` + Description string `xml:"Description,omitempty"` + UnknownPriceIndicator string `xml:"UnknownPriceIndicator,omitempty"` + SubcontractingDescription string `xml:"SubcontractingDescription,omitempty"` +} + +// ContractNotice represents the main TED XML contract notice structure +type ContractNotice struct { + XMLName xml.Name `xml:"ContractNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + 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"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot *ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` +} + +// ContractAwardNotice represents the main TED XML contract award notice structure +type ContractAwardNotice struct { + XMLName xml.Name `xml:"ContractAwardNotice"` + Xmlns string `xml:"xmlns,attr,omitempty"` + XmlnsCac string `xml:"xmlns:cac,attr,omitempty"` + XmlnsCbc string `xml:"xmlns:cbc,attr,omitempty"` + XmlnsEfac string `xml:"xmlns:efac,attr,omitempty"` + XmlnsEfbc string `xml:"xmlns:efbc,attr,omitempty"` + XmlnsEfext string `xml:"xmlns:efext,attr,omitempty"` + XmlnsExt string `xml:"xmlns:ext,attr,omitempty"` + Extensions *UBLExtensions `xml:"UBLExtensions,omitempty"` + 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"` + ContractingParty *ContractingParty `xml:"ContractingParty,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` + ProcurementProjectLot []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` + TenderResult *TenderResult `xml:"TenderResult,omitempty"` +} + +// NoticeResult contains notice result information from extensions +type NoticeResult struct { + XMLName xml.Name `xml:"NoticeResult"` + TotalAmount *CurrencyText `xml:"TotalAmount,omitempty"` + LotResult []LotResult `xml:"LotResult,omitempty"` +} + +// LotResult contains lot result information +type LotResult struct { + XMLName xml.Name `xml:"LotResult"` + ID string `xml:"ID"` + TenderResultCode string `xml:"TenderResultCode"` + LotTender []LotTender `xml:"LotTender,omitempty"` + ReceivedSubmissionsStatistics []ReceivedSubmissionsStatistics `xml:"ReceivedSubmissionsStatistics,omitempty"` + SettledContract []SettledContract `xml:"SettledContract,omitempty"` + TenderLot *TenderLot `xml:"TenderLot,omitempty"` +} + +// LotTender contains lot tender information +type LotTender struct { + XMLName xml.Name `xml:"LotTender"` + ID string `xml:"ID"` + RankCode string `xml:"RankCode,omitempty"` + TenderRankedIndicator string `xml:"TenderRankedIndicator,omitempty"` + TenderVariantIndicator string `xml:"TenderVariantIndicator,omitempty"` + LegalMonetaryTotal *LegalMonetaryTotal `xml:"LegalMonetaryTotal,omitempty"` + SubcontractingTerm *SubcontractingTerm `xml:"SubcontractingTerm,omitempty"` + TenderingParty *TenderingParty `xml:"TenderingParty,omitempty"` + TenderLot *TenderLot `xml:"TenderLot,omitempty"` + TenderReference *TenderReference `xml:"TenderReference,omitempty"` +} + +// LegalMonetaryTotal contains monetary total information +type LegalMonetaryTotal struct { + XMLName xml.Name `xml:"LegalMonetaryTotal"` + PayableAmount CurrencyText `xml:"PayableAmount"` +} + +// TenderingParty contains tendering party information +type TenderingParty struct { + XMLName xml.Name `xml:"TenderingParty"` + ID string `xml:"ID"` +} + +// TenderLot contains tender lot information +type TenderLot struct { + XMLName xml.Name `xml:"TenderLot"` + ID string `xml:"ID"` +} + +// TenderReference contains tender reference information +type TenderReference struct { + XMLName xml.Name `xml:"TenderReference"` + ID string `xml:"ID"` +} + +// ReceivedSubmissionsStatistics contains received submissions statistics +type ReceivedSubmissionsStatistics struct { + XMLName xml.Name `xml:"ReceivedSubmissionsStatistics"` + StatisticsCode string `xml:"StatisticsCode"` + StatisticsNumeric string `xml:"StatisticsNumeric"` +} + +// SettledContract contains settled contract information +type SettledContract struct { + XMLName xml.Name `xml:"SettledContract"` + ID string `xml:"ID"` + IssueDate string `xml:"IssueDate,omitempty"` + Title string `xml:"Title,omitempty"` + SignatoryParty *SignatoryParty `xml:"SignatoryParty,omitempty"` + ContractReference *ContractReference `xml:"ContractReference,omitempty"` + LotTender *LotTender `xml:"LotTender,omitempty"` +} + +// SignatoryParty contains signatory party information +type SignatoryParty struct { + XMLName xml.Name `xml:"SignatoryParty"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` +} + +// ContractReference contains contract reference information +type ContractReference struct { + XMLName xml.Name `xml:"ContractReference"` + ID string `xml:"ID"` +} + +// TenderResult contains tender result information +type TenderResult struct { + XMLName xml.Name `xml:"TenderResult"` + AwardDate string `xml:"AwardDate"` +} + +// NoticeDocumentReference contains notice document reference information +type NoticeDocumentReference struct { + XMLName xml.Name `xml:"NoticeDocumentReference"` + ID string `xml:"ID"` +} + +// PresentationPeriod contains presentation period information +type PresentationPeriod struct { + XMLName xml.Name `xml:"PresentationPeriod"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// AppealInformationParty contains appeal information party +type AppealInformationParty struct { + XMLName xml.Name `xml:"AppealInformationParty"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` +} + +// MediationParty contains mediation party information +type MediationParty struct { + XMLName xml.Name `xml:"MediationParty"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` +} + +// UBLExtensions contains eForms extensions +type UBLExtensions struct { + XMLName xml.Name `xml:"UBLExtensions"` + UBLExtension *UBLExtension `xml:"UBLExtension,omitempty"` +} + +// UBLExtension contains extension content +type UBLExtension struct { + XMLName xml.Name `xml:"UBLExtension"` + ExtensionContent *ExtensionContent `xml:"ExtensionContent,omitempty"` +} + +// ExtensionContent contains eForms extension data +type ExtensionContent struct { + XMLName xml.Name `xml:"ExtensionContent"` + EformsExtension *EformsExtension `xml:"EformsExtension,omitempty"` +} + +// EformsExtension contains eForms specific data +type EformsExtension struct { + XMLName xml.Name `xml:"EformsExtension"` + NoticeResult *NoticeResult `xml:"NoticeResult,omitempty"` + NoticeSubType *NoticeSubType `xml:"NoticeSubType,omitempty"` + Organizations *Organizations `xml:"Organizations,omitempty"` + Publication *Publication `xml:"Publication,omitempty"` + Changes *Changes `xml:"Changes,omitempty"` + SelectionCriteria []SelectionCriteria `xml:"SelectionCriteria,omitempty"` + OfficialLanguages *OfficialLanguages `xml:"OfficialLanguages,omitempty"` + AwardCriterionParameter *AwardCriterionParameter `xml:"AwardCriterionParameter,omitempty"` + AccessToolName string `xml:"AccessToolName,omitempty"` +} + +// NoticeSubType contains notice subtype information +type NoticeSubType struct { + XMLName xml.Name `xml:"NoticeSubType"` + SubTypeCode string `xml:"SubTypeCode"` +} + +// SelectionCriteria contains tenderer requirement information +type SelectionCriteria struct { + XMLName xml.Name `xml:"SelectionCriteria"` + CriterionTypeCode string `xml:"CriterionTypeCode"` + TendererRequirementTypeCode string `xml:"TendererRequirementTypeCode"` + Name string `xml:"Name,omitempty"` + Description string `xml:"Description,omitempty"` + CalculationExpressionCode string `xml:"CalculationExpressionCode,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// OfficialLanguages contains official language information +type OfficialLanguages struct { + XMLName xml.Name `xml:"OfficialLanguages"` + Language []Language `xml:"Language"` +} + +// Language represents a language entry +type Language struct { + XMLName xml.Name `xml:"Language"` + ID string `xml:"ID"` +} + +// Organizations contains organization information +type Organizations struct { + XMLName xml.Name `xml:"Organizations"` + Organization []Organization `xml:"Organization"` +} + +// Organization represents an organization entity +type Organization struct { + XMLName xml.Name `xml:"Organization"` + NaturalPersonIndicator string `xml:"NaturalPersonIndicator,omitempty"` + Company *Company `xml:"Company,omitempty"` +} + +// Company represents company informationf +type Company struct { + XMLName xml.Name `xml:"Company"` + WebsiteURI string `xml:"WebsiteURI,omitempty"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` + PartyName *PartyName `xml:"PartyName,omitempty"` + PostalAddress *PostalAddress `xml:"PostalAddress,omitempty"` + PartyLegalEntity *PartyLegalEntity `xml:"PartyLegalEntity,omitempty"` + Contact *Contact `xml:"Contact,omitempty"` +} + +// PartyIdentification contains party ID information +type PartyIdentification struct { + XMLName xml.Name `xml:"PartyIdentification"` + ID string `xml:"ID"` +} + +// PartyName contains party name +type PartyName struct { + XMLName xml.Name `xml:"PartyName"` + Name string `xml:"Name"` +} + +// PostalAddress contains address information +type PostalAddress struct { + XMLName xml.Name `xml:"PostalAddress"` + StreetName string `xml:"StreetName,omitempty"` + CityName string `xml:"CityName,omitempty"` + PostalZone string `xml:"PostalZone,omitempty"` + CountrySubentityCode string `xml:"CountrySubentityCode,omitempty"` + Department string `xml:"Department,omitempty"` + Country *Country `xml:"Country,omitempty"` +} + +// Country contains country information +type Country struct { + XMLName xml.Name `xml:"Country"` + IdentificationCode string `xml:"IdentificationCode"` +} + +// PartyLegalEntity contains legal entity information +type PartyLegalEntity struct { + XMLName xml.Name `xml:"PartyLegalEntity"` + CompanyID string `xml:"CompanyID"` +} + +// Contact contains contact information +type Contact struct { + XMLName xml.Name `xml:"Contact"` + Name string `xml:"Name,omitempty"` + Telephone string `xml:"Telephone,omitempty"` + Telefax string `xml:"Telefax,omitempty"` + ElectronicMail string `xml:"ElectronicMail,omitempty"` +} + +// Publication contains publication information +type Publication struct { + XMLName xml.Name `xml:"Publication"` + NoticePublicationID string `xml:"NoticePublicationID"` + GazetteID string `xml:"GazetteID"` + PublicationDate string `xml:"PublicationDate"` +} + +// Changes contains notice change information +type Changes struct { + XMLName xml.Name `xml:"Changes"` + ChangedNoticeIdentifier string `xml:"ChangedNoticeIdentifier"` + ChangeReason *ChangeReason `xml:"ChangeReason,omitempty"` +} + +// ChangeReason contains change reason information +type ChangeReason struct { + XMLName xml.Name `xml:"ChangeReason"` + ReasonCode string `xml:"ReasonCode"` +} + +// ContractingParty contains contracting party information +type ContractingParty struct { + XMLName xml.Name `xml:"ContractingParty"` + BuyerProfileURI string `xml:"BuyerProfileURI,omitempty"` + ContractingPartyType *ContractingPartyType `xml:"ContractingPartyType,omitempty"` + ContractingActivity *ContractingActivity `xml:"ContractingActivity,omitempty"` + Party *Party `xml:"Party,omitempty"` +} + +// ContractingPartyType contains party type information +type ContractingPartyType struct { + XMLName xml.Name `xml:"ContractingPartyType"` + PartyTypeCode string `xml:"PartyTypeCode"` +} + +// ContractingActivity contains contracting activity information +type ContractingActivity struct { + XMLName xml.Name `xml:"ContractingActivity"` + ActivityTypeCode string `xml:"ActivityTypeCode"` +} + +// Party contains party information +type Party struct { + XMLName xml.Name `xml:"Party"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` + ServiceProviderParty *ServiceProviderParty `xml:"ServiceProviderParty,omitempty"` +} + +// ServiceProviderParty contains service provider information +type ServiceProviderParty struct { + XMLName xml.Name `xml:"ServiceProviderParty"` + ServiceTypeCode string `xml:"ServiceTypeCode,omitempty"` + Party *Party `xml:"Party,omitempty"` +} + +// TenderingTerms contains tendering terms +type TenderingTerms struct { + XMLName xml.Name `xml:"TenderingTerms"` + VariantConstraintCode string `xml:"VariantConstraintCode,omitempty"` + FundingProgramCode string `xml:"FundingProgramCode,omitempty"` + RecurringProcurementIndicator string `xml:"RecurringProcurementIndicator,omitempty"` + MultipleTendersCode string `xml:"MultipleTendersCode,omitempty"` + TendererQualificationRequest []TendererQualificationRequest `xml:"TendererQualificationRequest,omitempty"` + AppealTerms *AppealTerms `xml:"AppealTerms,omitempty"` + CallForTendersDocumentReference []CallForTendersDocumentReference `xml:"CallForTendersDocumentReference,omitempty"` + RequiredFinancialGuarantee *RequiredFinancialGuarantee `xml:"RequiredFinancialGuarantee,omitempty"` + PaymentTerms *PaymentTerms `xml:"PaymentTerms,omitempty"` + AwardingTerms *AwardingTerms `xml:"AwardingTerms,omitempty"` + AdditionalInformationParty *AdditionalInformationParty `xml:"AdditionalInformationParty,omitempty"` + TenderRecipientParty *TenderRecipientParty `xml:"TenderRecipientParty,omitempty"` + TenderValidityPeriod *TenderValidityPeriod `xml:"TenderValidityPeriod,omitempty"` + Language *Language `xml:"Language,omitempty"` + PostAwardProcess *PostAwardProcess `xml:"PostAwardProcess,omitempty"` + ContractExecutionRequirement []ContractExecutionRequirement `xml:"ContractExecutionRequirement,omitempty"` + UBLExtensions *UBLExtensions `xml:"UBLExtensions,omitempty"` +} + +// RequiredFinancialGuarantee contains financial guarantee information +type RequiredFinancialGuarantee struct { + XMLName xml.Name `xml:"RequiredFinancialGuarantee"` + GuaranteeTypeCode string `xml:"GuaranteeTypeCode,omitempty"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// PaymentTerms contains payment terms information +type PaymentTerms struct { + XMLName xml.Name `xml:"PaymentTerms"` + Note string `xml:"Note,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// AwardingTerms contains awarding terms information +type AwardingTerms struct { + XMLName xml.Name `xml:"AwardingTerms"` + AwardingCriterion []AwardingCriterion `xml:"AwardingCriterion,omitempty"` +} + +// AwardingCriterion contains awarding criteria information +type AwardingCriterion struct { + XMLName xml.Name `xml:"AwardingCriterion"` + SubordinateAwardingCriterion []SubordinateAwardingCriterion `xml:"SubordinateAwardingCriterion,omitempty"` +} + +// SubordinateAwardingCriterion contains subordinate awarding criteria +type SubordinateAwardingCriterion struct { + XMLName xml.Name `xml:"SubordinateAwardingCriterion"` + AwardingCriterionTypeCode string `xml:"AwardingCriterionTypeCode,omitempty"` + Name string `xml:"Name,omitempty"` + Description string `xml:"Description,omitempty"` + UBLExtensions *UBLExtensions `xml:"UBLExtensions,omitempty"` +} + +// AwardCriterionParameter contains award criterion parameter information +type AwardCriterionParameter struct { + XMLName xml.Name `xml:"AwardCriterionParameter"` + ParameterCode string `xml:"ParameterCode,omitempty"` + ParameterNumeric string `xml:"ParameterNumeric,omitempty"` +} + +// AdditionalInformationParty contains additional information party +type AdditionalInformationParty struct { + XMLName xml.Name `xml:"AdditionalInformationParty"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` +} + +// TenderRecipientParty contains tender recipient party information +type TenderRecipientParty struct { + XMLName xml.Name `xml:"TenderRecipientParty"` + EndpointID string `xml:"EndpointID,omitempty"` +} + +// TenderValidityPeriod contains tender validity period +type TenderValidityPeriod struct { + XMLName xml.Name `xml:"TenderValidityPeriod"` + DurationMeasure *DurationMeasure `xml:"DurationMeasure,omitempty"` +} + +// PostAwardProcess contains post award process information +type PostAwardProcess struct { + XMLName xml.Name `xml:"PostAwardProcess"` + ElectronicCatalogueUsageIndicator string `xml:"ElectronicCatalogueUsageIndicator,omitempty"` + ElectronicOrderUsageIndicator string `xml:"ElectronicOrderUsageIndicator,omitempty"` + ElectronicPaymentUsageIndicator string `xml:"ElectronicPaymentUsageIndicator,omitempty"` +} + +// ContractExecutionRequirement contains contract execution requirements +type ContractExecutionRequirement struct { + XMLName xml.Name `xml:"ContractExecutionRequirement"` + ExecutionRequirementCode string `xml:"ExecutionRequirementCode,omitempty"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// AppealTerms contains appeal terms information +type AppealTerms struct { + XMLName xml.Name `xml:"AppealTerms"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` + PresentationPeriod *PresentationPeriod `xml:"PresentationPeriod,omitempty"` + AppealInformationParty *AppealInformationParty `xml:"AppealInformationParty,omitempty"` + AppealReceiverParty *AppealReceiverParty `xml:"AppealReceiverParty,omitempty"` + MediationParty *MediationParty `xml:"MediationParty,omitempty"` +} + +// AppealReceiverParty contains appeal receiver party information +type AppealReceiverParty struct { + XMLName xml.Name `xml:"AppealReceiverParty"` + PartyIdentification *PartyIdentification `xml:"PartyIdentification,omitempty"` +} + +// CallForTendersDocumentReference contains document reference information +type CallForTendersDocumentReference struct { + XMLName xml.Name `xml:"CallForTendersDocumentReference"` + ID string `xml:"ID,omitempty"` + DocumentType string `xml:"DocumentType,omitempty"` + LanguageID string `xml:"LanguageID,omitempty"` + DocumentStatusCode string `xml:"DocumentStatusCode,omitempty"` + Attachment *Attachment `xml:"Attachment,omitempty"` + UBLExtensions *UBLExtensions `xml:"UBLExtensions,omitempty"` +} + +// Attachment contains attachment information +type Attachment struct { + XMLName xml.Name `xml:"Attachment"` + ExternalReference *ExternalReference `xml:"ExternalReference,omitempty"` +} + +// ExternalReference contains external reference information +type ExternalReference struct { + XMLName xml.Name `xml:"ExternalReference"` + URI string `xml:"URI"` + DocumentHash string `xml:"DocumentHash,omitempty"` + FileName string `xml:"FileName,omitempty"` +} + +// TendererQualificationRequest contains qualification request information +type TendererQualificationRequest struct { + XMLName xml.Name `xml:"TendererQualificationRequest"` + CompanyLegalFormCode string `xml:"CompanyLegalFormCode,omitempty"` + SpecificTendererRequirement []SpecificTendererRequirement `xml:"SpecificTendererRequirement,omitempty"` +} + +// SpecificTendererRequirement contains specific requirement information +type SpecificTendererRequirement struct { + XMLName xml.Name `xml:"SpecificTendererRequirement"` + TendererRequirementTypeCode string `xml:"TendererRequirementTypeCode"` + Description string `xml:"Description,omitempty"` +} + +// TenderingProcess contains tendering process information +type TenderingProcess struct { + XMLName xml.Name `xml:"TenderingProcess"` + Description string `xml:"Description,omitempty"` + ProcedureCode string `xml:"ProcedureCode"` + SubmissionMethodCode string `xml:"SubmissionMethodCode,omitempty"` + GovernmentAgreementConstraintIndicator string `xml:"GovernmentAgreementConstraintIndicator,omitempty"` + ProcessJustification *ProcessJustification `xml:"ProcessJustification,omitempty"` + TenderSubmissionDeadlinePeriod *TenderSubmissionDeadlinePeriod `xml:"TenderSubmissionDeadlinePeriod,omitempty"` + OpenTenderEvent *OpenTenderEvent `xml:"OpenTenderEvent,omitempty"` + AuctionTerms *AuctionTerms `xml:"AuctionTerms,omitempty"` + ContractingSystem []ContractingSystem `xml:"ContractingSystem,omitempty"` + NoticeDocumentReference []NoticeDocumentReference `xml:"NoticeDocumentReference,omitempty"` + UBLExtensions *UBLExtensions `xml:"UBLExtensions,omitempty"` +} + +// TenderSubmissionDeadlinePeriod contains tender submission deadline information +type TenderSubmissionDeadlinePeriod struct { + XMLName xml.Name `xml:"TenderSubmissionDeadlinePeriod"` + EndDate string `xml:"EndDate"` + EndTime string `xml:"EndTime,omitempty"` +} + +// OpenTenderEvent contains open tender event information +type OpenTenderEvent struct { + XMLName xml.Name `xml:"OpenTenderEvent"` + OccurrenceDate string `xml:"OccurrenceDate,omitempty"` + OccurrenceTime string `xml:"OccurrenceTime,omitempty"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` + OccurenceLocation *OccurenceLocation `xml:"OccurenceLocation,omitempty"` +} + +// OccurenceLocation contains occurrence location information +type OccurenceLocation struct { + XMLName xml.Name `xml:"OccurenceLocation"` + Description string `xml:"Description,omitempty"` + LanguageID string `xml:"languageID,attr,omitempty"` +} + +// AuctionTerms contains auction terms information +type AuctionTerms struct { + XMLName xml.Name `xml:"AuctionTerms"` + AuctionConstraintIndicator string `xml:"AuctionConstraintIndicator,omitempty"` +} + +// ContractingSystem contains contracting system information +type ContractingSystem struct { + XMLName xml.Name `xml:"ContractingSystem"` + ContractingSystemTypeCode string `xml:"ContractingSystemTypeCode,omitempty"` +} + +// ProcurementProject contains procurement project information +type ProcurementProject struct { + XMLName xml.Name `xml:"ProcurementProject"` + ID string `xml:"ID"` + Name string `xml:"Name"` + Description string `xml:"Description"` + ProcurementTypeCode string `xml:"ProcurementTypeCode"` + SMESuitableIndicator string `xml:"SMESuitableIndicator,omitempty"` + Note string `xml:"Note,omitempty"` + + RequestedTenderTotal *RequestedTenderTotal `xml:"RequestedTenderTotal,omitempty"` + MainCommodityClassification *MainCommodityClassification `xml:"MainCommodityClassification,omitempty"` + AdditionalCommodityClassification []AdditionalCommodityClassification `xml:"AdditionalCommodityClassification,omitempty"` + ProcurementAdditionalType *ProcurementAdditionalType `xml:"ProcurementAdditionalType,omitempty"` + RealizedLocation *RealizedLocation `xml:"RealizedLocation,omitempty"` + PlannedPeriod *PlannedPeriod `xml:"PlannedPeriod,omitempty"` + ContractExtension *ContractExtension `xml:"ContractExtension,omitempty"` +} + +// ProcurementAdditionalType contains additional procurement type information +type ProcurementAdditionalType struct { + XMLName xml.Name `xml:"ProcurementAdditionalType"` + ProcurementTypeCode string `xml:"ProcurementTypeCode,omitempty"` +} + +// ContractExtension contains contract extension information +type ContractExtension struct { + XMLName xml.Name `xml:"ContractExtension"` + MaximumNumberNumeric string `xml:"MaximumNumberNumeric,omitempty"` +} + +// ProcessJustification contains process justification +type ProcessJustification struct { + XMLName xml.Name `xml:"ProcessJustification"` + ProcessReasonCode string `xml:"ProcessReasonCode"` +} + +// RequestedTenderTotal contains tender total information +type RequestedTenderTotal struct { + XMLName xml.Name `xml:"RequestedTenderTotal"` + EstimatedOverallContractAmount CurrencyText `xml:"EstimatedOverallContractAmount"` +} + +// CurrencyText represents text with currency attribute +type CurrencyText struct { + CurrencyID string `xml:"currencyID,attr,omitempty"` + Text string `xml:",chardata"` +} + +// MainCommodityClassification contains main commodity classification +type MainCommodityClassification struct { + XMLName xml.Name `xml:"MainCommodityClassification"` + ItemClassificationCode string `xml:"ItemClassificationCode"` +} + +// AdditionalCommodityClassification contains additional commodity classification +type AdditionalCommodityClassification struct { + XMLName xml.Name `xml:"AdditionalCommodityClassification"` + ItemClassificationCode string `xml:"ItemClassificationCode"` +} + +// RealizedLocation contains location information +type RealizedLocation struct { + XMLName xml.Name `xml:"RealizedLocation"` + Description string `xml:"Description,omitempty"` + Address *Address `xml:"Address,omitempty"` +} + +// Address contains address information (different from PostalAddress) +type Address struct { + XMLName xml.Name `xml:"Address"` + StreetName string `xml:"StreetName,omitempty"` + AdditionalStreetName string `xml:"AdditionalStreetName,omitempty"` + CityName string `xml:"CityName,omitempty"` + PostalZone string `xml:"PostalZone,omitempty"` + CountrySubentityCode string `xml:"CountrySubentityCode,omitempty"` + Region string `xml:"Region,omitempty"` + Country *Country `xml:"Country,omitempty"` +} + +// PlannedPeriod contains planned period information +type PlannedPeriod struct { + XMLName xml.Name `xml:"PlannedPeriod"` + StartDate string `xml:"StartDate,omitempty"` + EndDate string `xml:"EndDate,omitempty"` + DurationMeasure *DurationMeasure `xml:"DurationMeasure,omitempty"` +} + +// DurationMeasure contains duration with unit information +type DurationMeasure struct { + XMLName xml.Name `xml:"DurationMeasure"` + UnitCode string `xml:"unitCode,attr,omitempty"` + Text string `xml:",chardata"` +} + +// ProcurementProjectLot contains lot information +type ProcurementProjectLot struct { + XMLName xml.Name `xml:"ProcurementProjectLot"` + ID string `xml:"ID"` + TenderingTerms *TenderingTerms `xml:"TenderingTerms,omitempty"` + TenderingProcess *TenderingProcess `xml:"TenderingProcess,omitempty"` + ProcurementProject *ProcurementProject `xml:"ProcurementProject,omitempty"` +} + +// Validate validates the ContractNotice structure +func (cn ContractNotice) Validate() error { + if cn.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + + // Validate date format - TED XML uses ISO 8601 format with timezone + validFormats := []string{ + "2006-01-02+07:00", + "2006-01-02-07:00", + "2006-01-02T15:04:05+07:00", + "2006-01-02T15:04:05-07:00", + "2006-01-02T15:04:05Z", + "2006-01-02Z", + "2006-01-02", + time.RFC3339, + time.RFC3339Nano, + } + + var validDate bool + for _, format := range validFormats { + if _, err := time.Parse(format, cn.IssueDate); err == nil { + validDate = true + break + } + } + + if !validDate { + return fmt.Errorf("invalid issue date format: %s (expected ISO 8601 format)", cn.IssueDate) + } + + // Validate language code if provided (should be 3-letter ISO code) + if cn.NoticeLanguageCode != "" && len(cn.NoticeLanguageCode) != 3 { + return fmt.Errorf("invalid notice language code: %s (expected 3-letter ISO code)", cn.NoticeLanguageCode) + } + + // Validate tender submission deadline format if present + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.TenderingProcess != nil && + cn.ProcurementProjectLot.TenderingProcess.TenderSubmissionDeadlinePeriod != nil { + + deadline := cn.ProcurementProjectLot.TenderingProcess.TenderSubmissionDeadlinePeriod.EndDate + if deadline != "" { + var validDeadline bool + for _, format := range validFormats { + if _, err := time.Parse(format, deadline); err == nil { + validDeadline = true + break + } + } + if !validDeadline { + return fmt.Errorf("invalid tender submission deadline format: %s (expected ISO 8601 format)", deadline) + } + } + } + + // Validate currency code if provided in budget + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.ProcurementProject != nil && + cn.ProcurementProjectLot.ProcurementProject.RequestedTenderTotal != nil { + + currency := cn.ProcurementProjectLot.ProcurementProject.RequestedTenderTotal.EstimatedOverallContractAmount.CurrencyID + if currency != "" && len(currency) != 3 { + return fmt.Errorf("invalid currency code: %s (expected 3-letter ISO code)", currency) + } + } + + // Validate duration unit code if provided + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.ProcurementProject != nil && + cn.ProcurementProjectLot.ProcurementProject.PlannedPeriod != nil && + cn.ProcurementProjectLot.ProcurementProject.PlannedPeriod.DurationMeasure != nil { + + unitCode := cn.ProcurementProjectLot.ProcurementProject.PlannedPeriod.DurationMeasure.UnitCode + if unitCode != "" { + validUnits := map[string]bool{ + "MONTH": true, + "DAY": true, + "WEEK": true, + "YEAR": true, + } + if !validUnits[unitCode] { + return fmt.Errorf("invalid duration unit code: %s (expected MONTH, DAY, WEEK, or YEAR)", unitCode) + } + } + } + + return nil +} + +// GetID returns the contract notice ID +func (cn ContractNotice) GetID() string { + return cn.ID +} + +// GetNoticeType returns the notice type +func (cn ContractNotice) GetNoticeType() string { + return cn.NoticeTypeCode +} + +// GetLanguage returns the notice language +func (cn ContractNotice) GetLanguage() string { + return cn.NoticeLanguageCode +} + +// GetPublicationInfo returns publication information if available +func (cn ContractNotice) GetPublicationInfo() *Publication { + if cn.Extensions != nil && + cn.Extensions.UBLExtension != nil && + cn.Extensions.UBLExtension.ExtensionContent != nil && + cn.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil { + return cn.Extensions.UBLExtension.ExtensionContent.EformsExtension.Publication + } + return nil +} + +// GetOrganizations returns organizations from extensions +func (cn ContractNotice) GetOrganizations() *Organizations { + if cn.Extensions != nil && + cn.Extensions.UBLExtension != nil && + cn.Extensions.UBLExtension.ExtensionContent != nil && + cn.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil { + return cn.Extensions.UBLExtension.ExtensionContent.EformsExtension.Organizations + } + return nil +} + +// GetNoticeSubType returns the notice subtype code from extensions +func (cn ContractNotice) GetNoticeSubType() string { + if cn.Extensions != nil && + cn.Extensions.UBLExtension != nil && + cn.Extensions.UBLExtension.ExtensionContent != nil && + cn.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil && + cn.Extensions.UBLExtension.ExtensionContent.EformsExtension.NoticeSubType != nil { + return cn.Extensions.UBLExtension.ExtensionContent.EformsExtension.NoticeSubType.SubTypeCode + } + return "" +} + +// GetNoticePublicationID returns the notice publication ID from extensions +func (cn ContractNotice) GetNoticePublicationID() string { + pub := cn.GetPublicationInfo() + if pub != nil { + return pub.NoticePublicationID + } + return "" +} + +// GetOJSID returns the OJS gazette ID from extensions +func (cn ContractNotice) GetOJSID() string { + pub := cn.GetPublicationInfo() + if pub != nil { + return pub.GazetteID + } + return "" +} + +// GetPublicationDate returns the publication date from extensions +func (cn ContractNotice) GetPublicationDate() string { + pub := cn.GetPublicationInfo() + if pub != nil { + return pub.PublicationDate + } + return "" +} + +// GetOrganizationByID returns an organization by its ID +func (cn ContractNotice) GetOrganizationByID(orgID string) *Organization { + orgs := cn.GetOrganizations() + if orgs == nil { + return nil + } + + for _, org := range orgs.Organization { + if org.Company != nil && + org.Company.PartyIdentification != nil && + org.Company.PartyIdentification.ID == orgID { + return &org + } + } + return nil +} + +// GetBuyerID returns the buyer organization ID from contracting party +func (cn ContractNotice) GetBuyerID() string { + if cn.ContractingParty != nil && + cn.ContractingParty.Party != nil && + cn.ContractingParty.Party.PartyIdentification != nil { + return cn.ContractingParty.Party.PartyIdentification.ID + } + return "" +} + +// GetReviewOrganizationID returns the review organization ID from appeal terms +func (cn ContractNotice) GetReviewOrganizationID() string { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.TenderingTerms != nil && + cn.ProcurementProjectLot.TenderingTerms.AppealTerms != nil && + cn.ProcurementProjectLot.TenderingTerms.AppealTerms.AppealReceiverParty != nil && + cn.ProcurementProjectLot.TenderingTerms.AppealTerms.AppealReceiverParty.PartyIdentification != nil { + return cn.ProcurementProjectLot.TenderingTerms.AppealTerms.AppealReceiverParty.PartyIdentification.ID + } + return "" +} + +// GetProcedureCode returns the type of procedure +func (cn ContractNotice) GetProcedureCode() string { + if cn.TenderingProcess != nil { + return cn.TenderingProcess.ProcedureCode + } + return "" +} + +// GetProcurementTitle returns the procurement project title +func (cn ContractNotice) GetProcurementTitle() string { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.ProcurementProject != nil { + return cn.ProcurementProjectLot.ProcurementProject.Name + } + return "" +} + +// GetProcurementDescription returns the procurement project description +func (cn ContractNotice) GetProcurementDescription() string { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.ProcurementProject != nil { + return cn.ProcurementProjectLot.ProcurementProject.Description + } + return "" +} + +// GetContractNature returns the main nature of the contract +func (cn ContractNotice) GetContractNature() string { + if cn.ProcurementProject != nil { + return cn.ProcurementProject.ProcurementTypeCode + } + return "" +} + +// GetMainClassification returns the main CPV classification code +func (cn ContractNotice) GetMainClassification() string { + if cn.ProcurementProject != nil && + cn.ProcurementProject.MainCommodityClassification != nil { + return cn.ProcurementProject.MainCommodityClassification.ItemClassificationCode + } + return "" +} + +// GetPlaceOfPerformance returns the place of performance +func (cn ContractNotice) GetPlaceOfPerformance() string { + if cn.ProcurementProject != nil && + cn.ProcurementProject.RealizedLocation != nil && + cn.ProcurementProject.RealizedLocation.Address != nil { + return cn.ProcurementProject.RealizedLocation.Address.Region + } + return "" +} + +// GetLotID returns the procurement lot ID +func (cn ContractNotice) GetLotID() string { + if cn.ProcurementProjectLot != nil { + return cn.ProcurementProjectLot.ID + } + return "" +} + +// GetSelectionCriteria returns selection criteria from extensions +func (cn ContractNotice) GetSelectionCriteria() []SelectionCriteria { + if cn.Extensions != nil && + cn.Extensions.UBLExtension != nil && + cn.Extensions.UBLExtension.ExtensionContent != nil && + cn.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil { + return cn.Extensions.UBLExtension.ExtensionContent.EformsExtension.SelectionCriteria + } + return nil +} + +// GetOfficialLanguages returns official languages from tender terms extensions +func (cn ContractNotice) GetOfficialLanguages() []string { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.TenderingTerms != nil && + len(cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference) > 0 && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions != nil && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension != nil && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent != nil && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension != nil && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages != nil { + + languages := make([]string, len(cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages.Language)) + for i, lang := range cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages.Language { + languages[i] = lang.ID + } + return languages + } + return nil +} + +// GetDocumentURI returns the document URI from call for tenders document reference +func (cn ContractNotice) GetDocumentURI() string { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.TenderingTerms != nil && + len(cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference) > 0 && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].Attachment != nil && + cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].Attachment.ExternalReference != nil { + return cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference[0].Attachment.ExternalReference.URI + } + return "" +} + +// GetAllDocumentURIs returns all document URIs from call for tenders document references +func (cn ContractNotice) GetAllDocumentURIs() []string { + var uris []string + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.TenderingTerms != nil { + + for _, docRef := range cn.ProcurementProjectLot.TenderingTerms.CallForTendersDocumentReference { + if docRef.Attachment != nil && + docRef.Attachment.ExternalReference != nil && + docRef.Attachment.ExternalReference.URI != "" { + uris = append(uris, docRef.Attachment.ExternalReference.URI) + } + } + } + return uris +} + +// GetTenderDeadline returns the tender submission deadline +func (cn ContractNotice) GetTenderDeadline() string { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.TenderingProcess != nil && + cn.ProcurementProjectLot.TenderingProcess.TenderSubmissionDeadlinePeriod != nil { + return cn.ProcurementProjectLot.TenderingProcess.TenderSubmissionDeadlinePeriod.EndDate + } + return "" +} + +// GetBudget returns the estimated contract amount and currency +func (cn ContractNotice) GetBudget() (amount string, currency string) { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.ProcurementProject != nil && + cn.ProcurementProjectLot.ProcurementProject.RequestedTenderTotal != nil { + + contractAmount := cn.ProcurementProjectLot.ProcurementProject.RequestedTenderTotal.EstimatedOverallContractAmount + return contractAmount.Text, contractAmount.CurrencyID + } + return "", "" +} + +// GetDuration returns the contract duration and unit +func (cn ContractNotice) GetDuration() (duration string, unit string) { + if cn.ProcurementProjectLot != nil && + cn.ProcurementProjectLot.ProcurementProject != nil && + cn.ProcurementProjectLot.ProcurementProject.PlannedPeriod != nil && + cn.ProcurementProjectLot.ProcurementProject.PlannedPeriod.DurationMeasure != nil { + + durationMeasure := cn.ProcurementProjectLot.ProcurementProject.PlannedPeriod.DurationMeasure + return durationMeasure.Text, durationMeasure.UnitCode + } + return "", "" +} + +// ==================== ContractAwardNotice Methods ==================== + +// Validate validates the ContractAwardNotice structure +func (can ContractAwardNotice) Validate() error { + if can.ID == "" { + return fmt.Errorf("contract award notice ID is required") + } + + if can.ContractFolderID == "" { + return fmt.Errorf("contract folder ID is required") + } + + if can.IssueDate == "" { + return fmt.Errorf("issue date is required") + } + + // Validate date format - TED XML uses ISO 8601 format with timezone + validFormats := []string{ + "2006-01-02+07:00", + "2006-01-02-07:00", + "2006-01-02T15:04:05+07:00", + "2006-01-02T15:04:05-07:00", + "2006-01-02T15:04:05Z", + "2006-01-02Z", + "2006-01-02", + time.RFC3339, + time.RFC3339Nano, + } + + var validDate bool + for _, format := range validFormats { + if _, err := time.Parse(format, can.IssueDate); err == nil { + validDate = true + break + } + } + + if !validDate { + return fmt.Errorf("invalid issue date format: %s (expected ISO 8601 format)", can.IssueDate) + } + + // Validate language code if provided (should be 3-letter ISO code) + if can.NoticeLanguageCode != "" && len(can.NoticeLanguageCode) != 3 { + return fmt.Errorf("invalid notice language code: %s (expected 3-letter ISO code)", can.NoticeLanguageCode) + } + + return nil +} + +// GetID returns the contract award notice ID +func (can ContractAwardNotice) GetID() string { + return can.ID +} + +// GetNoticeType returns the notice type +func (can ContractAwardNotice) GetNoticeType() string { + return can.NoticeTypeCode +} + +// GetLanguage returns the notice language +func (can ContractAwardNotice) GetLanguage() string { + return can.NoticeLanguageCode +} + +// GetPublicationInfo returns publication information if available +func (can ContractAwardNotice) GetPublicationInfo() *Publication { + if can.Extensions != nil && + can.Extensions.UBLExtension != nil && + can.Extensions.UBLExtension.ExtensionContent != nil && + can.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil { + return can.Extensions.UBLExtension.ExtensionContent.EformsExtension.Publication + } + return nil +} + +// GetOrganizations returns organizations from extensions +func (can ContractAwardNotice) GetOrganizations() *Organizations { + if can.Extensions != nil && + can.Extensions.UBLExtension != nil && + can.Extensions.UBLExtension.ExtensionContent != nil && + can.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil { + return can.Extensions.UBLExtension.ExtensionContent.EformsExtension.Organizations + } + return nil +} + +// GetNoticeResult returns notice result from extensions +func (can ContractAwardNotice) GetNoticeResult() *NoticeResult { + if can.Extensions != nil && + can.Extensions.UBLExtension != nil && + can.Extensions.UBLExtension.ExtensionContent != nil && + can.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil { + return can.Extensions.UBLExtension.ExtensionContent.EformsExtension.NoticeResult + } + return nil +} + +// GetNoticeSubType returns the notice subtype code from extensions +func (can ContractAwardNotice) GetNoticeSubType() string { + if can.Extensions != nil && + can.Extensions.UBLExtension != nil && + can.Extensions.UBLExtension.ExtensionContent != nil && + can.Extensions.UBLExtension.ExtensionContent.EformsExtension != nil && + can.Extensions.UBLExtension.ExtensionContent.EformsExtension.NoticeSubType != nil { + return can.Extensions.UBLExtension.ExtensionContent.EformsExtension.NoticeSubType.SubTypeCode + } + return "" +} + +// GetNoticePublicationID returns the notice publication ID from extensions +func (can ContractAwardNotice) GetNoticePublicationID() string { + pub := can.GetPublicationInfo() + if pub != nil { + return pub.NoticePublicationID + } + return "" +} + +// GetOJSID returns the OJS gazette ID from extensions +func (can ContractAwardNotice) GetOJSID() string { + pub := can.GetPublicationInfo() + if pub != nil { + return pub.GazetteID + } + return "" +} + +// GetPublicationDate returns the publication date from extensions +func (can ContractAwardNotice) GetPublicationDate() string { + pub := can.GetPublicationInfo() + if pub != nil { + return pub.PublicationDate + } + return "" +} + +// GetBuyerID returns the buyer organization ID from contracting party +func (can ContractAwardNotice) GetBuyerID() string { + if can.ContractingParty != nil && + can.ContractingParty.Party != nil && + can.ContractingParty.Party.PartyIdentification != nil { + return can.ContractingParty.Party.PartyIdentification.ID + } + return "" +} + +// GetProcedureCode returns the type of procedure +func (can ContractAwardNotice) GetProcedureCode() string { + if can.TenderingProcess != nil { + return can.TenderingProcess.ProcedureCode + } + return "" +} + +// GetProcurementTitle returns the procurement project title +func (can ContractAwardNotice) GetProcurementTitle() string { + if can.ProcurementProject != nil { + return can.ProcurementProject.Name + } + return "" +} + +// GetProcurementDescription returns the procurement project description +func (can ContractAwardNotice) GetProcurementDescription() string { + if can.ProcurementProject != nil { + return can.ProcurementProject.Description + } + return "" +} + +// GetContractNature returns the main nature of the contract +func (can ContractAwardNotice) GetContractNature() string { + if can.ProcurementProject != nil { + return can.ProcurementProject.ProcurementTypeCode + } + return "" +} + +// GetMainClassification returns the main CPV classification code +func (can ContractAwardNotice) GetMainClassification() string { + if can.ProcurementProject != nil && + can.ProcurementProject.MainCommodityClassification != nil { + return can.ProcurementProject.MainCommodityClassification.ItemClassificationCode + } + return "" +} + +// GetTotalAwardValue returns the total award amount and currency +func (can ContractAwardNotice) GetTotalAwardValue() (amount string, currency string) { + noticeResult := can.GetNoticeResult() + if noticeResult != nil && noticeResult.TotalAmount != nil { + return noticeResult.TotalAmount.Text, noticeResult.TotalAmount.CurrencyID + } + return "", "" +} + +// GetAwardDate returns the tender award date +func (can ContractAwardNotice) GetAwardDate() string { + if can.TenderResult != nil { + return can.TenderResult.AwardDate + } + return "" +} + +// GetOrganizationByID returns an organization by its ID +func (can ContractAwardNotice) GetOrganizationByID(orgID string) *Organization { + orgs := can.GetOrganizations() + if orgs == nil { + return nil + } + + for _, org := range orgs.Organization { + if org.Company != nil && + org.Company.PartyIdentification != nil && + org.Company.PartyIdentification.ID == orgID { + return &org + } + } + return nil +} diff --git a/ted/parser.go b/ted/parser.go index ef169fb..7c29344 100644 --- a/ted/parser.go +++ b/ted/parser.go @@ -5,26 +5,13 @@ import ( "fmt" "strings" "tm/pkg/xmlparser" - "tm/ted/eform/business_registration_information" - "tm/ted/eform/contract" - "tm/ted/eform/contract_award" - "tm/ted/eform/prior_information" -) - -type ParserType string - -const ( - ParserTypeContractNotice ParserType = "ContractNotice" - ParserTypeContractAwardNotice ParserType = "ContractAwardNotice" - ParserTypePriorInformationNotice ParserType = "PriorInformationNotice" - ParserTypeBusinessRegistrationInformationNotice ParserType = "BusinessRegistrationInformationNotice" ) type TEDParser struct { - ContractNotice xmlparser.Parser[contract.ContractNotice] - ContractAwardNotice xmlparser.Parser[contract_award.ContractAwardNotice] - PriorInformationNotice xmlparser.Parser[prior_information.PriorInformationNotice] - BusinessRegistrationInformationNotice xmlparser.Parser[business_registration_information.BusinessRegistrationInformationNotice] + ContractNotice xmlparser.Parser[ContractNotice] + ContractAwardNotice xmlparser.Parser[ContractAwardNotice] + PriorInformationNotice xmlparser.Parser[PriorInformationNotice] + // BusinessRegistrationInformationNotice xmlparser.Parser[BusinessRegistrationInformationNotice] } // NewTEDParser creates a new TED parser @@ -33,43 +20,55 @@ func NewTEDParser() *TEDParser { options.MaxSize = 50 * 1024 * 1024 // 50MB limit for TED XML files options.IgnoreUnknownElements = true // TED XML has many optional fields - contractNoticeParser := xmlparser.NewParser[contract.ContractNotice](options) - contractAwardNoticeParser := xmlparser.NewParser[contract_award.ContractAwardNotice](options) - priorInformationNoticeParser := xmlparser.NewParser[prior_information.PriorInformationNotice](options) - businessRegistrationInformationNoticeParser := xmlparser.NewParser[business_registration_information.BusinessRegistrationInformationNotice](options) + contractNoticeParser := xmlparser.NewParser[ContractNotice](options) + contractAwardNoticeParser := xmlparser.NewParser[ContractAwardNotice](options) + priorInformationNoticeParser := xmlparser.NewParser[PriorInformationNotice](options) + // businessRegistrationInformationNoticeParser := xmlparser.NewParser[BusinessRegistrationInformationNotice](options) return &TEDParser{ - ContractNotice: contractNoticeParser, - ContractAwardNotice: contractAwardNoticeParser, - PriorInformationNotice: priorInformationNoticeParser, - BusinessRegistrationInformationNotice: businessRegistrationInformationNoticeParser, + ContractNotice: contractNoticeParser, + ContractAwardNotice: contractAwardNoticeParser, + PriorInformationNotice: priorInformationNoticeParser, + // BusinessRegistrationInformationNotice: businessRegistrationInformationNoticeParser, } } -// ParseAnyNotice parses XML content and detects the notice type automatically -func (tp *TEDParser) Parse(content []byte) (interface{}, string, ParserType, error) { +// ParseXML automatically detects and parses any supported TED XML document type +func (p *TEDParser) ParseXML(content []byte) (*ParsedDocument, string, DocumentType, error) { // First, detect the root element type rootElement, err := detectRootElement(content) if err != nil { - return nil, "", ParserType(""), fmt.Errorf("failed to detect root element: %w", err) + return nil, "", DocumentType(""), fmt.Errorf("failed to detect root element: %w", err) } - // Parse based on the detected type - switch ParserType(rootElement) { - case ParserTypeContractNotice: - notice, err := tp.ContractNotice.ParseBytes(content) - return notice, notice.GetID(), ParserTypeContractNotice, err - case ParserTypeContractAwardNotice: - notice, err := tp.ContractAwardNotice.ParseBytes(content) - return notice, notice.GetID(), ParserTypeContractAwardNotice, err - case ParserTypePriorInformationNotice: - notice, err := tp.PriorInformationNotice.ParseBytes(content) - return notice, notice.GetID(), ParserTypePriorInformationNotice, err - case ParserTypeBusinessRegistrationInformationNotice: - notice, err := tp.BusinessRegistrationInformationNotice.ParseBytes(content) - return notice, notice.GetID(), ParserTypeBusinessRegistrationInformationNotice, err + parsedDoc := &ParsedDocument{Type: DocumentType(rootElement)} + + switch parsedDoc.Type { + case DocumentTypeContractNotice: + notice, err := p.ContractNotice.ParseBytes(content) + if err != nil { + return nil, "", DocumentType(""), fmt.Errorf("failed to parse contract notice: %w", err) + } + parsedDoc.ContractNotice = notice + return parsedDoc, notice.GetID(), parsedDoc.Type, nil + + case DocumentTypeContractAwardNotice: + notice, err := p.ContractAwardNotice.ParseBytes(content) + if err != nil { + return nil, "", DocumentType(""), fmt.Errorf("failed to parse contract award notice: %w", err) + } + parsedDoc.ContractAwardNotice = notice + return parsedDoc, notice.GetID(), parsedDoc.Type, nil + + case DocumentTypePriorInformationNotice: + notice, err := p.PriorInformationNotice.ParseBytes(content) + if err != nil { + return nil, "", DocumentType(""), fmt.Errorf("failed to parse prior information notice: %w", err) + } + parsedDoc.PriorInformationNotice = notice + return parsedDoc, notice.GetID(), parsedDoc.Type, nil default: - return nil, "", ParserType(""), fmt.Errorf("unsupported notice type: %s", rootElement) + return nil, "", DocumentType(""), fmt.Errorf("unsupported document type: %s", parsedDoc.Type) } } diff --git a/ted/scraper.go b/ted/scraper.go index a33cc4b..8f5a18a 100644 --- a/ted/scraper.go +++ b/ted/scraper.go @@ -275,6 +275,18 @@ func (s *TEDScraper) tarGzFileProcessor(ctx context.Context, reader io.Reader, o return result, nil } +func (s *TEDScraper) findTenderByContractNoticeID(ctx context.Context, contractNoticeID string) (*tender.Tender, error) { + existingTender, err := s.tenderRepo.GetByContractNoticeID(ctx, contractNoticeID) + if err != nil { + if err.Error() == "document not found" { + return nil, nil + } + return nil, err + } + + return existingTender, nil +} + func (s *TEDScraper) processXMLContent(ctx context.Context, content []byte, fileName string, result *FileProcessingResult) error { s.logger.Debug("Processing XML content", map[string]interface{}{ "file_name": fileName, @@ -283,7 +295,7 @@ func (s *TEDScraper) processXMLContent(ctx context.Context, content []byte, file }) // Parse XML using the flexible TED parser that detects notice type - notice, id, noticeType, err := s.xmlParser.Parse(content) + notice, id, noticeType, err := s.xmlParser.ParseXML(content) if err != nil { s.logger.Error("XML parsing failed", map[string]interface{}{ "file_name": fileName, @@ -293,13 +305,71 @@ func (s *TEDScraper) processXMLContent(ctx context.Context, content []byte, file return fmt.Errorf("failed to parse XML: %w", err) } - _ = notice + // Map to tender entity + t := s.mapToTenderFromParsedDoc(notice, "", fileName) + + existingTender, err := s.findTenderByContractNoticeID(ctx, t.ContractNoticeID) + if err != nil { + s.logger.Warn("Error checking for existing tender", map[string]interface{}{ + "contract_notice_id": t.ContractNoticeID, + "error": err.Error(), + }) + } + + if err == nil && existingTender != nil { + // Update existing tender + s.logger.Info("Found existing tender, updating", map[string]interface{}{ + "existing_tender_id": existingTender.ID.Hex(), + "contract_notice_id": t.ContractNoticeID, + }) + + t.ID = existingTender.ID + t.UpdatedAt = time.Now().Unix() + err = s.tenderRepo.Update(ctx, t) + if err != nil { + s.logger.Error("Failed to update existing tender", map[string]interface{}{ + "tender_id": t.ID.Hex(), + "error": err.Error(), + }) + return fmt.Errorf("failed to update existing tender: %w", err) + } + + s.logger.Info("Successfully updated tender", map[string]interface{}{ + "tender_id": t.ID.Hex(), + }) + } else { + // Create new tender + s.logger.Info("Creating new tender", map[string]interface{}{ + "contract_notice_id": t.ContractNoticeID, + "tender_id": t.TenderID, + }) + + t.CreatedAt = time.Now().Unix() + t.UpdatedAt = t.CreatedAt + + err = s.tenderRepo.Create(ctx, t) + if err != nil { + s.logger.Error("Failed to create new tender", map[string]interface{}{ + "contract_notice_id": t.ContractNoticeID, + "tender_id": t.TenderID, + "error": err.Error(), + }) + return fmt.Errorf("failed to create tender: %w", err) + } + + s.logger.Info("Successfully created new tender", map[string]interface{}{ + "tender_id": t.TenderID, + "contract_notice_id": t.ContractNoticeID, + }) + } // Log detailed parsed information based on notice type s.logger.Info("Successfully parsed notice", map[string]interface{}{ "notice_id": id, "notice_type": noticeType, "file_name": fileName, + "tender_id": t.TenderID, + "tender_url": t.TenderURL, }) result.SuccessCount++ @@ -318,7 +388,7 @@ func (s *TEDScraper) Run(ctx context.Context) error { s.logger.Error("Failed to get OJS", map[string]interface{}{ "error": err.Error(), }) - return err + return nil } // download daily package diff --git a/ted/service.go b/ted/service.go index e01696c..622b16e 100644 --- a/ted/service.go +++ b/ted/service.go @@ -6,31 +6,10 @@ import ( "strings" "time" "tm/internal/tender" - "tm/ted/eform/business_registration_information" - "tm/ted/eform/contract" - "tm/ted/eform/contract_award" - "tm/ted/eform/prior_information" ) -func EFormMapper(notice interface{}) *tender.Tender { - t := new(tender.Tender) - - switch notice := notice.(type) { - case contract.ContractNotice: - return notice.MapToTender() - case contract_award.ContractAwardNotice: - return notice.MapToTender() - case prior_information.PriorInformationNotice: - return notice.MapToTender() - case business_registration_information.BusinessRegistrationInformationNotice: - return notice.MapToTender() - default: - return t - } -} - // parseDate parses various date formats used in TED XML -func parseDate(dateStr string) (time.Time, error) { +func ParseDate(dateStr string) (time.Time, error) { if dateStr == "" { return time.Time{}, fmt.Errorf("empty date string") } @@ -58,20 +37,20 @@ func parseDate(dateStr string) (time.Time, error) { } // parseDateToUnixMilli converts string date to Unix milliseconds (int64) -func parseDateToUnixMilli(dateStr string) int64 { +func ParseDateToUnix(dateStr string) int64 { if dateStr == "" { return 0 } - if parsedTime, err := parseDate(dateStr); err == nil { - return parsedTime.UnixMilli() + if parsedTime, err := ParseDate(dateStr); err == nil { + return parsedTime.Unix() } return 0 } -// parseTimeToUnixMilli converts string time to Unix milliseconds (int64) -func parseTimeToUnixMilli(timeStr string) int64 { +// parseTimeToUnix converts string time to Unix milliseconds (int64) +func ParseTimeToUnix(timeStr string) int64 { if timeStr == "" { return 0 } @@ -83,23 +62,23 @@ func parseTimeToUnixMilli(timeStr string) int64 { timeStr = fmt.Sprintf("%sT%s", today, timeStr) } - if parsedTime, err := parseDate(timeStr); err == nil { - return parsedTime.UnixMilli() + if parsedTime, err := ParseDate(timeStr); err == nil { + return parsedTime.Unix() } return 0 } -// unixMilliToDateString converts Unix milliseconds to date string for TenderID generation -func unixMilliToDateString(unixMilli int64) string { - if unixMilli == 0 { +// unixToDateString converts Unix to date string for TenderID generation +func UnixToDateString(unix int64) string { + if unix == 0 { return "00000000" } - return time.UnixMilli(unixMilli).Format("20060102") + return time.Unix(unix, 0).Format("20060102") } -func calculateSubmissionDeadline(publicationDate time.Time) time.Time { +func CalculateSubmissionDeadline(publicationDate time.Time) time.Time { deadline := publicationDate workingHoursAdded := 0 @@ -114,7 +93,7 @@ func calculateSubmissionDeadline(publicationDate time.Time) time.Time { return deadline } -func calculateApplicationDeadline(tenderDeadline time.Time) time.Time { +func CalculateApplicationDeadline(tenderDeadline time.Time) time.Time { applicationDeadline := tenderDeadline workingDaysSubtracted := 0 @@ -130,7 +109,7 @@ func calculateApplicationDeadline(tenderDeadline time.Time) time.Time { } // padOrTruncate pads a string with zeros or truncates it to the specified length -func padOrTruncate(str string, length int) string { +func PadOrTruncate(str string, length int) string { // Remove any non-alphanumeric characters and convert to uppercase clean := strings.ToUpper(strings.ReplaceAll(str, "-", "")) clean = strings.ReplaceAll(clean, " ", "") @@ -144,7 +123,7 @@ func padOrTruncate(str string, length int) string { } // generateTenderURL generates the TED tender URL from NoticePublicationID -func generateTenderURL(noticePublicationID string) string { +func GenerateTenderURL(noticePublicationID string) string { if noticePublicationID == "" { return "" } @@ -167,7 +146,7 @@ func generateTenderURL(noticePublicationID string) string { // generateTenderID generates a unique tender ID using the format: // {Source}{BUYER_CODE}{TED_CODE}{DATE}{LOCATION_CODE} -func generateTenderID(t *tender.Tender) string { +func GenerateTenderID(t *tender.Tender) string { var parts []string // Source (TED) @@ -176,7 +155,7 @@ func generateTenderID(t *tender.Tender) string { // TED code (Notice Publication ID, first 8 chars) tedCode := "00000000" if t.NoticePublicationID != "" { - tedCode = padOrTruncate(t.NoticePublicationID, 8) + tedCode = PadOrTruncate(t.NoticePublicationID, 8) } parts = append(parts, tedCode) @@ -188,7 +167,7 @@ func generateTenderID(t *tender.Tender) string { // parts = append(parts, buyerCode) // Date (YYYYMMDD format from issue date) - parts = append(parts, padOrTruncate(unixMilliToDateString(t.IssueDate), 4)) + parts = append(parts, PadOrTruncate(UnixToDateString(t.IssueDate), 4)) // Location code (country code + region code, padded to 6 chars) locationCode := "EU"