package ted import ( "encoding/xml" "fmt" "strconv" "strings" "time" ) // NoticeTypeCodeField captures cbc:NoticeTypeCode: inner text (notice type, BT-02) and listName (form type, BT-03). type NoticeTypeCodeField struct { ListName string `xml:"listName,attr,omitempty"` Text string `xml:",chardata"` } // 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 } // ublEformsExtension returns embedded eForms extension data from UBL extensions (shared across notice root types). func ublEformsExtension(ext *UBLExtensions) *EformsExtension { if ext == nil || ext.UBLExtension == nil || ext.UBLExtension.ExtensionContent == nil { return nil } return ext.UBLExtension.ExtensionContent.EformsExtension } // 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 NoticeTypeCodeField `xml:"NoticeTypeCode,omitempty"` 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 strings.TrimSpace(pin.NoticeTypeCode.Text) } // GetNoticeFormType returns the form type from listName on cbc:NoticeTypeCode (BT-03), e.g. "planning". func (pin PriorInformationNotice) GetNoticeFormType() string { return strings.TrimSpace(pin.NoticeTypeCode.ListName) } func (pin PriorInformationNotice) firstLot() *ProcurementProjectLot { if len(pin.ProcurementProjectLot) == 0 { return nil } return &pin.ProcurementProjectLot[0] } // GetPublicationInfo returns publication information if available. func (pin PriorInformationNotice) GetPublicationInfo() *Publication { if ef := ublEformsExtension(pin.Extensions); ef != nil { return ef.Publication } return nil } // GetOrganizations returns organizations from extensions. func (pin PriorInformationNotice) GetOrganizations() *Organizations { if ef := ublEformsExtension(pin.Extensions); ef != nil { return ef.Organizations } return nil } // GetNoticeSubType returns the notice subtype code from extensions. func (pin PriorInformationNotice) GetNoticeSubType() string { if ef := ublEformsExtension(pin.Extensions); ef != nil && ef.NoticeSubType != nil { return ef.NoticeSubType.SubTypeCode } return "" } // GetNoticePublicationID returns the notice publication ID from extensions. func (pin PriorInformationNotice) GetNoticePublicationID() string { pub := pin.GetPublicationInfo() if pub != nil { return pub.NoticePublicationID } return "" } // GetOJSID returns the OJS gazette ID from extensions. func (pin PriorInformationNotice) GetOJSID() string { pub := pin.GetPublicationInfo() if pub != nil { return pub.GazetteID } return "" } // GetPublicationDate returns the publication date from extensions. func (pin PriorInformationNotice) GetPublicationDate() string { pub := pin.GetPublicationInfo() if pub != nil { return pub.PublicationDate } return "" } // GetOrganizationByID returns an organization by its ID. func (pin PriorInformationNotice) GetOrganizationByID(orgID string) *Organization { orgs := pin.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 (pin PriorInformationNotice) GetBuyerID() string { if pin.ContractingParty != nil && pin.ContractingParty.Party != nil && pin.ContractingParty.Party.PartyIdentification != nil { return pin.ContractingParty.Party.PartyIdentification.ID } return "" } // GetReviewOrganizationID returns the review organization ID from appeal terms. func (pin PriorInformationNotice) GetReviewOrganizationID() string { if pin.firstLot() != nil && pin.firstLot().TenderingTerms != nil && pin.firstLot().TenderingTerms.AppealTerms != nil && pin.firstLot().TenderingTerms.AppealTerms.AppealReceiverParty != nil && pin.firstLot().TenderingTerms.AppealTerms.AppealReceiverParty.PartyIdentification != nil { return pin.firstLot().TenderingTerms.AppealTerms.AppealReceiverParty.PartyIdentification.ID } return "" } // GetProcedureCode returns the type of procedure. func (pin PriorInformationNotice) GetProcedureCode() string { if pin.TenderingProcess != nil { return pin.TenderingProcess.ProcedureCode } return "" } // GetProcurementTitle returns the procurement project title. func (pin PriorInformationNotice) GetProcurementTitle() string { if pin.ProcurementProject != nil && strings.TrimSpace(pin.ProcurementProject.Name) != "" { return pin.ProcurementProject.Name } if pin.firstLot() != nil && pin.firstLot().ProcurementProject != nil { return pin.firstLot().ProcurementProject.Name } return "" } // GetProcurementDescription returns the procurement project description. func (pin PriorInformationNotice) GetProcurementDescription() string { if pin.ProcurementProject != nil && strings.TrimSpace(pin.ProcurementProject.Description) != "" { return pin.ProcurementProject.Description } if pin.firstLot() != nil && pin.firstLot().ProcurementProject != nil { return pin.firstLot().ProcurementProject.Description } return "" } // GetContractNature returns the main nature of the contract. func (pin PriorInformationNotice) GetContractNature() string { if pin.ProcurementProject != nil && strings.TrimSpace(pin.ProcurementProject.ProcurementTypeCode) != "" { return pin.ProcurementProject.ProcurementTypeCode } if pin.firstLot() != nil && pin.firstLot().ProcurementProject != nil { return pin.firstLot().ProcurementProject.ProcurementTypeCode } return "" } // GetMainClassification returns the main CPV classification code. func (pin PriorInformationNotice) GetMainClassification() string { if pin.ProcurementProject != nil && pin.ProcurementProject.MainCommodityClassification != nil { return pin.ProcurementProject.MainCommodityClassification.ItemClassificationCode } if lot := pin.firstLot(); lot != nil && lot.ProcurementProject != nil && lot.ProcurementProject.MainCommodityClassification != nil { return lot.ProcurementProject.MainCommodityClassification.ItemClassificationCode } return "" } // GetMainClassificationDescription returns the CPV / item classification description when present in the notice XML. func (pin PriorInformationNotice) GetMainClassificationDescription() string { if pin.ProcurementProject != nil { if s := mainCommodityClassificationLabel(pin.ProcurementProject.MainCommodityClassification); s != "" { return s } } if lot := pin.firstLot(); lot != nil && lot.ProcurementProject != nil { return mainCommodityClassificationLabel(lot.ProcurementProject.MainCommodityClassification) } return "" } // GetPlaceOfPerformance returns the place of performance. func (pin PriorInformationNotice) GetPlaceOfPerformance() string { if pin.ProcurementProject != nil && pin.ProcurementProject.RealizedLocation != nil && pin.ProcurementProject.RealizedLocation.Address != nil { return pin.ProcurementProject.RealizedLocation.Address.Region } if lot := pin.firstLot(); lot != nil && lot.ProcurementProject != nil && lot.ProcurementProject.RealizedLocation != nil && lot.ProcurementProject.RealizedLocation.Address != nil { return lot.ProcurementProject.RealizedLocation.Address.Region } return "" } // GetSelectionCriteria returns selection criteria from extensions. func (pin PriorInformationNotice) GetSelectionCriteria() []SelectionCriteria { if ef := ublEformsExtension(pin.Extensions); ef != nil { return ef.SelectionCriteria } return nil } // GetOfficialLanguages returns official languages from tender terms extensions. func (pin PriorInformationNotice) GetOfficialLanguages() []string { if pin.firstLot() != nil && pin.firstLot().TenderingTerms != nil && len(pin.firstLot().TenderingTerms.CallForTendersDocumentReference) > 0 && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions != nil && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension != nil && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent != nil && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension != nil && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages != nil { langs := pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages.Language languages := make([]string, len(langs)) for i, lang := range langs { languages[i] = lang.ID } return languages } return nil } // GetDocumentURI returns the document URI from call for tenders document reference. func (pin PriorInformationNotice) GetDocumentURI() string { if pin.firstLot() != nil && pin.firstLot().TenderingTerms != nil && len(pin.firstLot().TenderingTerms.CallForTendersDocumentReference) > 0 && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].Attachment != nil && pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].Attachment.ExternalReference != nil { return pin.firstLot().TenderingTerms.CallForTendersDocumentReference[0].Attachment.ExternalReference.URI } return "" } // GetTenderDeadline returns the tender submission deadline. func (pin PriorInformationNotice) GetTenderDeadline() string { if pin.firstLot() != nil && pin.firstLot().TenderingProcess != nil && pin.firstLot().TenderingProcess.TenderSubmissionDeadlinePeriod != nil { return pin.firstLot().TenderingProcess.TenderSubmissionDeadlinePeriod.EndDate } return "" } // GetBudget returns the estimated contract amount and currency. func (pin PriorInformationNotice) GetBudget() (amount string, currency string) { if amount, currency = budgetFromProcurementLots(pin.ProcurementProjectLot); amount != "" { return amount, currency } if pin.ProcurementProject != nil && pin.ProcurementProject.RequestedTenderTotal != nil { return pin.ProcurementProject.RequestedTenderTotal.AmountAndCurrency() } return "", "" } // GetDuration returns the contract duration and unit. func (pin PriorInformationNotice) GetDuration() (duration string, unit string) { if pin.firstLot() != nil && pin.firstLot().ProcurementProject != nil && pin.firstLot().ProcurementProject.PlannedPeriod != nil && pin.firstLot().ProcurementProject.PlannedPeriod.DurationMeasure != nil { durationMeasure := pin.firstLot().ProcurementProject.PlannedPeriod.DurationMeasure return durationMeasure.Text, durationMeasure.UnitCode } if pin.ProcurementProject != nil && pin.ProcurementProject.PlannedPeriod != nil && pin.ProcurementProject.PlannedPeriod.DurationMeasure != nil { dm := pin.ProcurementProject.PlannedPeriod.DurationMeasure return dm.Text, dm.UnitCode } return "", "" } 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 NoticeTypeCodeField `xml:"NoticeTypeCode,omitempty"` 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"` ProcurementProjectLots []ProcurementProjectLot `xml:"ProcurementProjectLot,omitempty"` } func (cn ContractNotice) firstLot() *ProcurementProjectLot { if len(cn.ProcurementProjectLots) == 0 { return nil } return &cn.ProcurementProjectLots[0] } // 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 NoticeTypeCodeField `xml:"NoticeTypeCode,omitempty"` 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"` } func (can ContractAwardNotice) firstAwardLot() *ProcurementProjectLot { if len(can.ProcurementProjectLot) == 0 { return nil } return &can.ProcurementProjectLot[0] } func pickWinningLotTender(lotTenders []LotTender) *LotTender { if len(lotTenders) == 0 { return nil } for i := range lotTenders { if strings.TrimSpace(lotTenders[i].RankCode) == "1" { return &lotTenders[i] } } return &lotTenders[0] } // 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"` TransmissionDate string `xml:"TransmissionDate,omitempty"` TransmissionTime string `xml:"TransmissionTime,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"` TransmissionDate string `xml:"TransmissionDate,omitempty"` TransmissionTime string `xml:"TransmissionTime,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"` CountrySubentity string `xml:"CountrySubentity,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"` TotalAmount CurrencyText `xml:"TotalAmount,omitempty"` } // AmountAndCurrency returns the best-effort amount text and currency from RequestedTenderTotal. func (r *RequestedTenderTotal) AmountAndCurrency() (amount string, currency string) { if r == nil { return "", "" } if amount, currency = r.EstimatedOverallContractAmount.AmountAndCurrency(); amount != "" { return amount, currency } return r.TotalAmount.AmountAndCurrency() } // CurrencyText represents text with currency attribute type CurrencyText struct { CurrencyID string `xml:"currencyID,attr,omitempty"` Text string `xml:",chardata"` Value string `xml:"Value,omitempty"` } // AmountAndCurrency returns the amount text and currency id from a CurrencyText node. func (c CurrencyText) AmountAndCurrency() (amount string, currency string) { amount = strings.TrimSpace(c.Text) if amount == "" { amount = strings.TrimSpace(c.Value) } return amount, strings.TrimSpace(c.CurrencyID) } // MainCommodityClassification contains main commodity classification type MainCommodityClassification struct { XMLName xml.Name `xml:"MainCommodityClassification"` ItemClassificationCode string `xml:"ItemClassificationCode"` ItemClassificationName string `xml:"ItemClassificationName,omitempty"` ItemClassificationDesc string `xml:"ItemClassificationDescription,omitempty"` Name string `xml:"Name,omitempty"` } // 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.firstLot() != nil && cn.firstLot().TenderingProcess != nil && cn.firstLot().TenderingProcess.TenderSubmissionDeadlinePeriod != nil { deadline := cn.firstLot().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.firstLot() != nil && cn.firstLot().ProcurementProject != nil && cn.firstLot().ProcurementProject.RequestedTenderTotal != nil { currency := cn.firstLot().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.firstLot() != nil && cn.firstLot().ProcurementProject != nil && cn.firstLot().ProcurementProject.PlannedPeriod != nil && cn.firstLot().ProcurementProject.PlannedPeriod.DurationMeasure != nil { unitCode := cn.firstLot().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 code (BT-02, cbc:NoticeTypeCode text). func (cn ContractNotice) GetNoticeType() string { return strings.TrimSpace(cn.NoticeTypeCode.Text) } // GetNoticeFormType returns the form type from listName on cbc:NoticeTypeCode (BT-03), e.g. "competition". func (cn ContractNotice) GetNoticeFormType() string { return strings.TrimSpace(cn.NoticeTypeCode.ListName) } // 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 ef := ublEformsExtension(cn.Extensions); ef != nil { return ef.Publication } return nil } // GetOrganizations returns organizations from extensions func (cn ContractNotice) GetOrganizations() *Organizations { if ef := ublEformsExtension(cn.Extensions); ef != nil { return ef.Organizations } return nil } // GetNoticeSubType returns the notice subtype code from extensions func (cn ContractNotice) GetNoticeSubType() string { if ef := ublEformsExtension(cn.Extensions); ef != nil && ef.NoticeSubType != nil { return ef.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.firstLot() != nil && cn.firstLot().TenderingTerms != nil && cn.firstLot().TenderingTerms.AppealTerms != nil && cn.firstLot().TenderingTerms.AppealTerms.AppealReceiverParty != nil && cn.firstLot().TenderingTerms.AppealTerms.AppealReceiverParty.PartyIdentification != nil { return cn.firstLot().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 notice-level procurement project title func (cn ContractNotice) GetProcurementTitle() string { if cn.ProcurementProject != nil && strings.TrimSpace(cn.ProcurementProject.Name) != "" { return cn.ProcurementProject.Name } if cn.firstLot() != nil && cn.firstLot().ProcurementProject != nil { return cn.firstLot().ProcurementProject.Name } return "" } // GetProcurementDescription returns the notice-level procurement project description func (cn ContractNotice) GetProcurementDescription() string { if cn.ProcurementProject != nil && strings.TrimSpace(cn.ProcurementProject.Description) != "" { return cn.ProcurementProject.Description } if cn.firstLot() != nil && cn.firstLot().ProcurementProject != nil { return cn.firstLot().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 "" } func mainCommodityClassificationLabel(m *MainCommodityClassification) string { if m == nil { return "" } for _, s := range []string{m.ItemClassificationName, m.ItemClassificationDesc, m.Name} { if v := strings.TrimSpace(s); v != "" { return v } } return "" } // GetMainClassificationDescription returns the CPV / item classification description when present in the notice XML. func (cn ContractNotice) GetMainClassificationDescription() string { if cn.ProcurementProject == nil { return "" } return mainCommodityClassificationLabel(cn.ProcurementProject.MainCommodityClassification) } // 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.firstLot() != nil { return cn.firstLot().ID } return "" } // GetSelectionCriteria returns selection criteria from extensions func (cn ContractNotice) GetSelectionCriteria() []SelectionCriteria { if ef := ublEformsExtension(cn.Extensions); ef != nil { return ef.SelectionCriteria } return nil } // GetOfficialLanguages returns official languages from tender terms extensions func (cn ContractNotice) GetOfficialLanguages() []string { if cn.firstLot() != nil && cn.firstLot().TenderingTerms != nil && len(cn.firstLot().TenderingTerms.CallForTendersDocumentReference) > 0 && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions != nil && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension != nil && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent != nil && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension != nil && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages != nil { languages := make([]string, len(cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].UBLExtensions.UBLExtension.ExtensionContent.EformsExtension.OfficialLanguages.Language)) for i, lang := range cn.firstLot().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.firstLot() != nil && cn.firstLot().TenderingTerms != nil && len(cn.firstLot().TenderingTerms.CallForTendersDocumentReference) > 0 && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].Attachment != nil && cn.firstLot().TenderingTerms.CallForTendersDocumentReference[0].Attachment.ExternalReference != nil { return cn.firstLot().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 for i := range cn.ProcurementProjectLots { lot := &cn.ProcurementProjectLots[i] if lot.TenderingTerms == nil { continue } for _, docRef := range lot.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.firstLot() != nil && cn.firstLot().TenderingProcess != nil && cn.firstLot().TenderingProcess.TenderSubmissionDeadlinePeriod != nil { return cn.firstLot().TenderingProcess.TenderSubmissionDeadlinePeriod.EndDate } return "" } // GetBudget returns the estimated contract amount and currency. // Lot-level BT-27 values are summed when present; procedure-level BT-27 is used when lots omit RequestedTenderTotal. func (cn ContractNotice) GetBudget() (amount string, currency string) { if amount, currency = budgetFromProcurementLots(cn.ProcurementProjectLots); amount != "" { return amount, currency } if cn.ProcurementProject != nil && cn.ProcurementProject.RequestedTenderTotal != nil { return cn.ProcurementProject.RequestedTenderTotal.AmountAndCurrency() } return "", "" } func budgetFromProcurementLots(lots []ProcurementProjectLot) (amount string, currency string) { var total float64 for i := range lots { pp := lots[i].ProcurementProject if pp == nil || pp.RequestedTenderTotal == nil { continue } lotAmount, lotCurrency := pp.RequestedTenderTotal.AmountAndCurrency() if lotAmount == "" { continue } value, ok := ParseEuropeanAmount(lotAmount) if !ok || value <= 0 { continue } total += value if currency == "" { currency = lotCurrency } } if total <= 0 { return "", "" } return strconv.FormatFloat(total, 'f', -1, 64), currency } // GetDuration returns the contract duration and unit func (cn ContractNotice) GetDuration() (duration string, unit string) { if cn.firstLot() != nil && cn.firstLot().ProcurementProject != nil && cn.firstLot().ProcurementProject.PlannedPeriod != nil && cn.firstLot().ProcurementProject.PlannedPeriod.DurationMeasure != nil { durationMeasure := cn.firstLot().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 code (BT-02). func (can ContractAwardNotice) GetNoticeType() string { return strings.TrimSpace(can.NoticeTypeCode.Text) } // GetNoticeFormType returns the form type from listName (BT-03). func (can ContractAwardNotice) GetNoticeFormType() string { return strings.TrimSpace(can.NoticeTypeCode.ListName) } // 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 && strings.TrimSpace(can.ProcurementProject.Name) != "" { return can.ProcurementProject.Name } if lot := can.firstAwardLot(); lot != nil && lot.ProcurementProject != nil { return lot.ProcurementProject.Name } return "" } // GetProcurementDescription returns the procurement project description func (can ContractAwardNotice) GetProcurementDescription() string { if can.ProcurementProject != nil && strings.TrimSpace(can.ProcurementProject.Description) != "" { return can.ProcurementProject.Description } if lot := can.firstAwardLot(); lot != nil && lot.ProcurementProject != nil { return lot.ProcurementProject.Description } return "" } // GetContractNature returns the main nature of the contract func (can ContractAwardNotice) GetContractNature() string { if can.ProcurementProject != nil && strings.TrimSpace(can.ProcurementProject.ProcurementTypeCode) != "" { return can.ProcurementProject.ProcurementTypeCode } if lot := can.firstAwardLot(); lot != nil && lot.ProcurementProject != nil { return lot.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 } if lot := can.firstAwardLot(); lot != nil && lot.ProcurementProject != nil && lot.ProcurementProject.MainCommodityClassification != nil { return lot.ProcurementProject.MainCommodityClassification.ItemClassificationCode } return "" } // GetMainClassificationDescription returns the CPV / item classification description when present in the notice XML. func (can ContractAwardNotice) GetMainClassificationDescription() string { if can.ProcurementProject != nil { if s := mainCommodityClassificationLabel(can.ProcurementProject.MainCommodityClassification); s != "" { return s } } if lot := can.firstAwardLot(); lot != nil && lot.ProcurementProject != nil { return mainCommodityClassificationLabel(lot.ProcurementProject.MainCommodityClassification) } 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.AmountAndCurrency() } 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 } // GetWinningTenderPartyID resolves the tendering party reference for the primary winning bid from NoticeResult. func (can ContractAwardNotice) GetWinningTenderPartyID() string { nr := can.GetNoticeResult() if nr == nil { return "" } for _, lr := range nr.LotResult { winner := pickWinningLotTender(lr.LotTender) if winner != nil && winner.TenderingParty != nil && winner.TenderingParty.ID != "" { return winner.TenderingParty.ID } } return "" } // GetWinningTenderOrganization resolves the winning tenderer organization from NoticeResult via Organizations. func (can ContractAwardNotice) GetWinningTenderOrganization() *Organization { id := can.GetWinningTenderPartyID() if id == "" { return nil } return can.GetOrganizationByID(id) }