Enhance Tender Entity and TED Parser with Status Management and Upsert Logic

- Added new fields to the Tender entity for handling cancellation, award, and suspension details, improving the entity's capability to manage tender statuses.
- Implemented a DetectNoticeStatus method in the TEDParser to identify the status of TED notices from XML data, enhancing the parser's functionality.
- Updated the ParseXML method to include notice status detection, ensuring accurate status representation in parsed documents.
- Introduced upsert logic in the TED scraper to handle tender records more effectively, allowing for updates or creation based on existing ContractIDs.
- Enhanced logging and error handling throughout the scraping process to improve traceability and robustness.
- Added a comprehensive usage example document to illustrate the new features and usage patterns for the TED XML parser and scraper.
This commit is contained in:
n.nakhostin
2025-09-28 10:27:20 +03:30
parent b40615ec99
commit f57e53bd9b
4 changed files with 1306 additions and 124 deletions
+408 -84
View File
@@ -3,6 +3,7 @@ package ted
import (
"encoding/xml"
"fmt"
"strings"
"time"
)
@@ -27,9 +28,24 @@ const (
DocumentTypeSubcontractNotice DocumentType = "SubcontractNotice"
)
// NoticeStatus represents the status of a TED notice
type NoticeStatus string
// Notice status constants for all possible TED notice statuses
const (
NoticeStatusDraft NoticeStatus = "Draft"
NoticeStatusPublished NoticeStatus = "Published"
NoticeStatusCancelled NoticeStatus = "Cancelled"
NoticeStatusAwarded NoticeStatus = "Awarded"
NoticeStatusClosed NoticeStatus = "Closed"
NoticeStatusModified NoticeStatus = "Modified"
NoticeStatusSuspended NoticeStatus = "Suspended"
)
// ParsedDocument wraps any TED document with type information
type ParsedDocument struct {
Type DocumentType
Type DocumentType `json:"type"`
Status NoticeStatus `json:"status"`
ContractNotice *ContractNotice `json:"contract_notice,omitempty"`
ContractAwardNotice *ContractAwardNotice `json:"contract_award_notice,omitempty"`
PriorInformationNotice *PriorInformationNotice `json:"prior_information_notice,omitempty"`
@@ -78,6 +94,7 @@ type TEDDocument interface {
GetID() string
GetNoticeType() string
GetLanguage() string
GetNoticeStatus() NoticeStatus
Validate() error
}
@@ -123,6 +140,11 @@ func (pin PriorInformationNotice) GetLanguage() string {
return pin.NoticeLanguageCode
}
func (pin PriorInformationNotice) GetNoticeStatus() NoticeStatus {
// Prior information notices are typically published
return NoticeStatusPublished
}
func (pin PriorInformationNotice) Validate() error {
if pin.ID == "" {
return fmt.Errorf("prior information notice ID is required")
@@ -163,9 +185,10 @@ type DesignContest struct {
}
// 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) GetID() string { return dc.ID }
func (dc DesignContest) GetNoticeType() string { return dc.NoticeTypeCode }
func (dc DesignContest) GetLanguage() string { return dc.NoticeLanguageCode }
func (dc DesignContest) GetNoticeStatus() NoticeStatus { return NoticeStatusPublished }
func (dc DesignContest) Validate() error {
if dc.ID == "" {
return fmt.Errorf("design contest ID is required")
@@ -205,9 +228,10 @@ type QualificationSystemNotice struct {
}
// 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) GetID() string { return qsn.ID }
func (qsn QualificationSystemNotice) GetNoticeType() string { return qsn.NoticeTypeCode }
func (qsn QualificationSystemNotice) GetLanguage() string { return qsn.NoticeLanguageCode }
func (qsn QualificationSystemNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusPublished }
func (qsn QualificationSystemNotice) Validate() error {
if qsn.ID == "" {
return fmt.Errorf("qualification system notice ID is required")
@@ -249,9 +273,10 @@ type ConcessionNotice struct {
}
// 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) GetID() string { return cn.ID }
func (cn ConcessionNotice) GetNoticeType() string { return cn.NoticeTypeCode }
func (cn ConcessionNotice) GetLanguage() string { return cn.NoticeLanguageCode }
func (cn ConcessionNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusPublished }
func (cn ConcessionNotice) Validate() error {
if cn.ID == "" {
return fmt.Errorf("concession notice ID is required")
@@ -291,9 +316,10 @@ type PlanningNotice struct {
}
// 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) GetID() string { return pn.ID }
func (pn PlanningNotice) GetNoticeType() string { return pn.NoticeTypeCode }
func (pn PlanningNotice) GetLanguage() string { return pn.NoticeLanguageCode }
func (pn PlanningNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusPublished }
func (pn PlanningNotice) Validate() error {
if pn.ID == "" {
return fmt.Errorf("planning notice ID is required")
@@ -333,9 +359,10 @@ type CompetitionNotice struct {
}
// 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) GetID() string { return cn.ID }
func (cn CompetitionNotice) GetNoticeType() string { return cn.NoticeTypeCode }
func (cn CompetitionNotice) GetLanguage() string { return cn.NoticeLanguageCode }
func (cn CompetitionNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusPublished }
func (cn CompetitionNotice) Validate() error {
if cn.ID == "" {
return fmt.Errorf("competition notice ID is required")
@@ -376,9 +403,10 @@ type ResultNotice struct {
}
// 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) GetID() string { return rn.ID }
func (rn ResultNotice) GetNoticeType() string { return rn.NoticeTypeCode }
func (rn ResultNotice) GetLanguage() string { return rn.NoticeLanguageCode }
func (rn ResultNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusAwarded }
func (rn ResultNotice) Validate() error {
if rn.ID == "" {
return fmt.Errorf("result notice ID is required")
@@ -420,9 +448,10 @@ type ChangeNotice struct {
}
// 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) GetID() string { return cn.ID }
func (cn ChangeNotice) GetNoticeType() string { return cn.NoticeTypeCode }
func (cn ChangeNotice) GetLanguage() string { return cn.NoticeLanguageCode }
func (cn ChangeNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusModified }
func (cn ChangeNotice) Validate() error {
if cn.ID == "" {
return fmt.Errorf("change notice ID is required")
@@ -463,9 +492,10 @@ type SubcontractNotice struct {
}
// 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) GetID() string { return sn.ID }
func (sn SubcontractNotice) GetNoticeType() string { return sn.NoticeTypeCode }
func (sn SubcontractNotice) GetLanguage() string { return sn.NoticeLanguageCode }
func (sn SubcontractNotice) GetNoticeStatus() NoticeStatus { return NoticeStatusPublished }
func (sn SubcontractNotice) Validate() error {
if sn.ID == "" {
return fmt.Errorf("subcontract notice ID is required")
@@ -665,67 +695,123 @@ type SubcontractingTerm struct {
SubcontractingDescription string `xml:"SubcontractingDescription,omitempty"`
}
// NoticeStatusInformation contains status-specific fields
type NoticeStatusInformation struct {
Status NoticeStatus `xml:"NoticeStatus,omitempty"`
StatusCode string `xml:"StatusCode,omitempty"`
CancellationReason *CancellationReason `xml:"CancellationReason,omitempty"`
AwardInformation *AwardInformation `xml:"AwardInformation,omitempty"`
Modifications []NoticeModification `xml:"Modifications>Modification,omitempty"`
SuspensionReason *SuspensionReason `xml:"SuspensionReason,omitempty"`
}
// CancellationReason contains cancellation information
type CancellationReason struct {
XMLName xml.Name `xml:"CancellationReason"`
ReasonCode string `xml:"ReasonCode"`
Description string `xml:"Description,omitempty"`
LanguageID string `xml:"languageID,attr,omitempty"`
}
// AwardInformation contains award-specific information
type AwardInformation struct {
XMLName xml.Name `xml:"AwardInformation"`
AwardDate string `xml:"AwardDate"`
AwardedValue CurrencyText `xml:"AwardedValue,omitempty"`
WinningTenderer *WinningTenderer `xml:"WinningTenderer,omitempty"`
ContractNumber string `xml:"ContractNumber,omitempty"`
}
// WinningTenderer contains information about the winning tenderer
type WinningTenderer struct {
XMLName xml.Name `xml:"WinningTenderer"`
NaturalPersonIndicator string `xml:"NaturalPersonIndicator,omitempty"`
Company *Company `xml:"Company,omitempty"`
}
// NoticeModification contains modification information
type NoticeModification struct {
XMLName xml.Name `xml:"Modification"`
ModificationDate string `xml:"ModificationDate"`
ModificationReason string `xml:"ModificationReason"`
Description string `xml:"Description,omitempty"`
LanguageID string `xml:"languageID,attr,omitempty"`
}
// SuspensionReason contains suspension information
type SuspensionReason struct {
XMLName xml.Name `xml:"SuspensionReason"`
ReasonCode string `xml:"ReasonCode"`
Description string `xml:"Description,omitempty"`
LanguageID string `xml:"languageID,attr,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"`
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"`
StatusInformation *NoticeStatusInformation `xml:"StatusInformation,omitempty"`
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"`
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"`
StatusInformation *NoticeStatusInformation `xml:"StatusInformation,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"`
}
// 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"`
XMLName xml.Name `xml:"NoticeResult"`
TotalAmount *CurrencyText `xml:"TotalAmount,omitempty"`
LotResult []LotResult `xml:"LotResult,omitempty"`
LotTender []LotTender `xml:"LotTender,omitempty"`
SettledContract []SettledContract `xml:"SettledContract,omitempty"`
TenderingParty []TenderingParty `xml:"TenderingParty,omitempty"`
}
// LotResult contains lot result information
@@ -761,7 +847,14 @@ type LegalMonetaryTotal struct {
// TenderingParty contains tendering party information
type TenderingParty struct {
XMLName xml.Name `xml:"TenderingParty"`
XMLName xml.Name `xml:"TenderingParty"`
ID string `xml:"ID"`
Tenderer []Tenderer `xml:"Tenderer,omitempty"`
}
// Tenderer contains tenderer information
type Tenderer struct {
XMLName xml.Name `xml:"Tenderer"`
ID string `xml:"ID"`
}
@@ -786,13 +879,16 @@ type ReceivedSubmissionsStatistics struct {
// 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"`
XMLName xml.Name `xml:"SettledContract"`
ID string `xml:"ID"`
IssueDate string `xml:"IssueDate,omitempty"`
AwardDate string `xml:"AwardDate,omitempty"`
Title string `xml:"Title,omitempty"`
ContractFrameworkIndicator string `xml:"ContractFrameworkIndicator,omitempty"`
SignatoryParty *SignatoryParty `xml:"SignatoryParty,omitempty"`
ContractReference *ContractReference `xml:"ContractReference,omitempty"`
LotTender *LotTender `xml:"LotTender,omitempty"`
NoticeDocumentReference *NoticeDocumentReference `xml:"NoticeDocumentReference,omitempty"`
}
// SignatoryParty contains signatory party information
@@ -1439,6 +1535,62 @@ func (cn ContractNotice) GetLanguage() string {
return cn.NoticeLanguageCode
}
// GetNoticeStatus returns the notice status
func (cn ContractNotice) GetNoticeStatus() NoticeStatus {
if cn.StatusInformation != nil {
if cn.StatusInformation.Status != "" {
return cn.StatusInformation.Status
}
// Check for specific status indicators
if cn.StatusInformation.CancellationReason != nil {
return NoticeStatusCancelled
}
if cn.StatusInformation.AwardInformation != nil {
return NoticeStatusAwarded
}
if cn.StatusInformation.SuspensionReason != nil {
return NoticeStatusSuspended
}
if len(cn.StatusInformation.Modifications) > 0 {
return NoticeStatusModified
}
}
// Default to Published if no status information is available
return NoticeStatusPublished
}
// GetCancellationReason returns the cancellation reason if notice is cancelled
func (cn ContractNotice) GetCancellationReason() *CancellationReason {
if cn.StatusInformation != nil {
return cn.StatusInformation.CancellationReason
}
return nil
}
// GetAwardInformation returns the award information if notice is awarded
func (cn ContractNotice) GetAwardInformation() *AwardInformation {
if cn.StatusInformation != nil {
return cn.StatusInformation.AwardInformation
}
return nil
}
// GetModifications returns the list of modifications if notice is modified
func (cn ContractNotice) GetModifications() []NoticeModification {
if cn.StatusInformation != nil {
return cn.StatusInformation.Modifications
}
return nil
}
// GetSuspensionReason returns the suspension reason if notice is suspended
func (cn ContractNotice) GetSuspensionReason() *SuspensionReason {
if cn.StatusInformation != nil {
return cn.StatusInformation.SuspensionReason
}
return nil
}
// GetPublicationInfo returns publication information if available
func (cn ContractNotice) GetPublicationInfo() *Publication {
if cn.Extensions != nil &&
@@ -1759,6 +1911,47 @@ func (can ContractAwardNotice) GetLanguage() string {
return can.NoticeLanguageCode
}
// GetNoticeStatus returns the notice status
func (can ContractAwardNotice) GetNoticeStatus() NoticeStatus {
if can.StatusInformation != nil && can.StatusInformation.Status != "" {
return can.StatusInformation.Status
}
// Award notices are typically awarded status
return NoticeStatusAwarded
}
// GetCancellationReason returns the cancellation reason if notice is cancelled
func (can ContractAwardNotice) GetCancellationReason() *CancellationReason {
if can.StatusInformation != nil {
return can.StatusInformation.CancellationReason
}
return nil
}
// GetAwardInformation returns the award information if notice is awarded
func (can ContractAwardNotice) GetAwardInformation() *AwardInformation {
if can.StatusInformation != nil {
return can.StatusInformation.AwardInformation
}
return nil
}
// GetModifications returns the list of modifications if notice is modified
func (can ContractAwardNotice) GetModifications() []NoticeModification {
if can.StatusInformation != nil {
return can.StatusInformation.Modifications
}
return nil
}
// GetSuspensionReason returns the suspension reason if notice is suspended
func (can ContractAwardNotice) GetSuspensionReason() *SuspensionReason {
if can.StatusInformation != nil {
return can.StatusInformation.SuspensionReason
}
return nil
}
// GetPublicationInfo returns publication information if available
func (can ContractAwardNotice) GetPublicationInfo() *Publication {
if can.Extensions != nil &&
@@ -1915,3 +2108,134 @@ func (can ContractAwardNotice) GetOrganizationByID(orgID string) *Organization {
}
return nil
}
// GetAwardedContractors returns detailed information about awarded contractors
func (can ContractAwardNotice) GetAwardedContractors() []AwardedContractorInfo {
var contractors []AwardedContractorInfo
noticeResult := can.GetNoticeResult()
if noticeResult == nil {
return contractors
}
// Create a map of tender ID to tender info for quick lookup
tenderMap := make(map[string]*LotTender)
for _, lotTender := range noticeResult.LotTender {
tenderMap[lotTender.ID] = &lotTender
}
// Create a map of tendering party ID to organization ID
partyToOrgMap := make(map[string]string)
for _, tenderingParty := range noticeResult.TenderingParty {
for _, tenderer := range tenderingParty.Tenderer {
partyToOrgMap[tenderingParty.ID] = tenderer.ID
}
}
// Process settled contracts to extract award information
for _, contract := range noticeResult.SettledContract {
if contract.LotTender != nil {
// Get tender information
tender := tenderMap[contract.LotTender.ID]
if tender == nil {
continue
}
// Get organization information
var org *Organization
if tender.TenderingParty != nil {
orgID := partyToOrgMap[tender.TenderingParty.ID]
if orgID != "" {
org = can.GetOrganizationByID(orgID)
}
}
// Create awarded contractor info
contractor := AwardedContractorInfo{
ContractID: contract.ID,
TenderID: contract.LotTender.ID,
AwardDate: contract.AwardDate,
IssueDate: contract.IssueDate,
}
// Set contract reference if available
if contract.ContractReference != nil {
contractor.ContractReference = contract.ContractReference.ID
}
// Set amount and currency from tender
if tender.LegalMonetaryTotal != nil {
contractor.Amount = tender.LegalMonetaryTotal.PayableAmount.Text
contractor.Currency = tender.LegalMonetaryTotal.PayableAmount.CurrencyID
}
// Set organization information
if org != nil && org.Company != nil {
if org.Company.PartyName != nil {
contractor.Name = org.Company.PartyName.Name
}
if org.Company.PartyLegalEntity != nil {
contractor.CompanyID = org.Company.PartyLegalEntity.CompanyID
}
if org.Company.PartyIdentification != nil {
contractor.OrganizationID = org.Company.PartyIdentification.ID
}
// Set address information
if org.Company.PostalAddress != nil {
addressParts := []string{}
if org.Company.PostalAddress.StreetName != "" {
addressParts = append(addressParts, org.Company.PostalAddress.StreetName)
}
if org.Company.PostalAddress.CityName != "" {
addressParts = append(addressParts, org.Company.PostalAddress.CityName)
}
if org.Company.PostalAddress.PostalZone != "" {
addressParts = append(addressParts, org.Company.PostalAddress.PostalZone)
}
contractor.Address = strings.Join(addressParts, ", ")
if org.Company.PostalAddress.Country != nil {
contractor.Country = org.Company.PostalAddress.Country.IdentificationCode
}
}
// Set contact information
if org.Company.Contact != nil {
contractor.ContactName = org.Company.Contact.Name
contractor.ContactEmail = org.Company.Contact.ElectronicMail
contractor.ContactPhone = org.Company.Contact.Telephone
}
}
// Set lot information
if tender.TenderLot != nil {
contractor.LotID = tender.TenderLot.ID
}
contractors = append(contractors, contractor)
}
}
return contractors
}
// AwardedContractorInfo contains detailed information about an awarded contractor
type AwardedContractorInfo struct {
ContractID string `json:"contract_id"`
TenderID string `json:"tender_id"`
LotID string `json:"lot_id,omitempty"`
Name string `json:"name"`
CompanyID string `json:"company_id,omitempty"`
OrganizationID string `json:"organization_id,omitempty"`
Address string `json:"address,omitempty"`
Country string `json:"country,omitempty"`
Amount string `json:"amount"`
Currency string `json:"currency,omitempty"`
AwardDate string `json:"award_date,omitempty"`
IssueDate string `json:"issue_date,omitempty"`
ContractReference string `json:"contract_reference,omitempty"`
ContactName string `json:"contact_name,omitempty"`
ContactEmail string `json:"contact_email,omitempty"`
ContactPhone string `json:"contact_phone,omitempty"`
}