44f266189c
- Updated the GetOJS function to accept a base URL parameter, improving flexibility in constructing the calendar URL. - Removed the deprecated eform.go file, streamlining the codebase by eliminating unused components. - Introduced new eform structures for BusinessRegistrationInformationNotice, ContractNotice, ContractAwardNotice, and PriorInformationNotice, enhancing the handling of TED XML data. - Implemented mapping functions to convert eform notices to the Tender entity, improving integration with the tender management system. - Added a service layer to encapsulate business logic related to eform processing, adhering to Clean Architecture principles.
202 lines
5.1 KiB
Go
202 lines
5.1 KiB
Go
package ted
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"tm/internal/tender"
|
|
"tm/ted/eform/business_registration_information"
|
|
"tm/ted/eform/contract"
|
|
"tm/ted/eform/contract_award"
|
|
"tm/ted/eform/prior_information"
|
|
)
|
|
|
|
func EFormMapper(notice interface{}) *tender.Tender {
|
|
t := new(tender.Tender)
|
|
|
|
switch notice := notice.(type) {
|
|
case contract.ContractNotice:
|
|
return notice.MapToTender()
|
|
case contract_award.ContractAwardNotice:
|
|
return notice.MapToTender()
|
|
case prior_information.PriorInformationNotice:
|
|
return notice.MapToTender()
|
|
case business_registration_information.BusinessRegistrationInformationNotice:
|
|
return notice.MapToTender()
|
|
default:
|
|
return t
|
|
}
|
|
}
|
|
|
|
// parseDate parses various date formats used in TED XML
|
|
func parseDate(dateStr string) (time.Time, error) {
|
|
if dateStr == "" {
|
|
return time.Time{}, fmt.Errorf("empty date string")
|
|
}
|
|
|
|
// Common TED date formats
|
|
formats := []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,
|
|
}
|
|
|
|
for _, format := range formats {
|
|
if parsedTime, err := time.Parse(format, dateStr); err == nil {
|
|
return parsedTime, nil
|
|
}
|
|
}
|
|
|
|
return time.Time{}, fmt.Errorf("unable to parse date: %s", dateStr)
|
|
}
|
|
|
|
// parseDateToUnixMilli converts string date to Unix milliseconds (int64)
|
|
func parseDateToUnixMilli(dateStr string) int64 {
|
|
if dateStr == "" {
|
|
return 0
|
|
}
|
|
|
|
if parsedTime, err := parseDate(dateStr); err == nil {
|
|
return parsedTime.UnixMilli()
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// parseTimeToUnixMilli converts string time to Unix milliseconds (int64)
|
|
func parseTimeToUnixMilli(timeStr string) int64 {
|
|
if timeStr == "" {
|
|
return 0
|
|
}
|
|
|
|
// If time string is just time without date, use today's date
|
|
if !strings.Contains(timeStr, "T") && !strings.Contains(timeStr, " ") {
|
|
// Assume it's just a time like "15:04:05"
|
|
today := time.Now().Format("2006-01-02")
|
|
timeStr = fmt.Sprintf("%sT%s", today, timeStr)
|
|
}
|
|
|
|
if parsedTime, err := parseDate(timeStr); err == nil {
|
|
return parsedTime.UnixMilli()
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// unixMilliToDateString converts Unix milliseconds to date string for TenderID generation
|
|
func unixMilliToDateString(unixMilli int64) string {
|
|
if unixMilli == 0 {
|
|
return "00000000"
|
|
}
|
|
|
|
return time.UnixMilli(unixMilli).Format("20060102")
|
|
}
|
|
|
|
func calculateSubmissionDeadline(publicationDate time.Time) time.Time {
|
|
deadline := publicationDate
|
|
workingHoursAdded := 0
|
|
|
|
for workingHoursAdded < 48 {
|
|
deadline = deadline.Add(1 * time.Hour)
|
|
|
|
if deadline.Weekday() != time.Saturday && deadline.Weekday() != time.Sunday {
|
|
workingHoursAdded++
|
|
}
|
|
}
|
|
|
|
return deadline
|
|
}
|
|
|
|
func calculateApplicationDeadline(tenderDeadline time.Time) time.Time {
|
|
applicationDeadline := tenderDeadline
|
|
workingDaysSubtracted := 0
|
|
|
|
for workingDaysSubtracted < 7 {
|
|
applicationDeadline = applicationDeadline.AddDate(0, 0, -1)
|
|
|
|
if applicationDeadline.Weekday() != time.Saturday && applicationDeadline.Weekday() != time.Sunday {
|
|
workingDaysSubtracted++
|
|
}
|
|
}
|
|
|
|
return applicationDeadline
|
|
}
|
|
|
|
// padOrTruncate pads a string with zeros or truncates it to the specified length
|
|
func padOrTruncate(str string, length int) string {
|
|
// Remove any non-alphanumeric characters and convert to uppercase
|
|
clean := strings.ToUpper(strings.ReplaceAll(str, "-", ""))
|
|
clean = strings.ReplaceAll(clean, " ", "")
|
|
|
|
if len(clean) >= length {
|
|
return clean[:length]
|
|
}
|
|
|
|
// Pad with zeros
|
|
return clean + strings.Repeat("0", length-len(clean))
|
|
}
|
|
|
|
// generateTenderURL generates the TED tender URL from NoticePublicationID
|
|
func generateTenderURL(noticePublicationID string) string {
|
|
if noticePublicationID == "" {
|
|
return ""
|
|
}
|
|
|
|
// Remove leading zeros and format for URL
|
|
// Example: 00522942-2025 -> https://ted.europa.eu/en/notice/-/detail/522942-2025
|
|
parts := strings.Split(noticePublicationID, "-")
|
|
if len(parts) != 2 {
|
|
return ""
|
|
}
|
|
|
|
// Convert to int and back to string to remove leading zeros
|
|
if num, err := strconv.Atoi(parts[0]); err == nil {
|
|
cleanID := fmt.Sprintf("%d-%s", num, parts[1])
|
|
return fmt.Sprintf("https://ted.europa.eu/en/notice/-/detail/%s", cleanID)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// generateTenderID generates a unique tender ID using the format:
|
|
// {Source}{BUYER_CODE}{TED_CODE}{DATE}{LOCATION_CODE}
|
|
func generateTenderID(t *tender.Tender) string {
|
|
var parts []string
|
|
|
|
// Source (TED)
|
|
parts = append(parts, "1")
|
|
|
|
// TED code (Notice Publication ID, first 8 chars)
|
|
tedCode := "00000000"
|
|
if t.NoticePublicationID != "" {
|
|
tedCode = padOrTruncate(t.NoticePublicationID, 8)
|
|
}
|
|
parts = append(parts, tedCode)
|
|
|
|
// Buyer code (first 8 chars of buyer organization ID, or 8 zeros if not available)
|
|
// buyerCode := "00000000"
|
|
// if t.BuyerOrganization != nil && t.BuyerOrganization.ID != "" {
|
|
// buyerCode = padOrTruncate(t.BuyerOrganization.ID, 8)
|
|
// }
|
|
// parts = append(parts, buyerCode)
|
|
|
|
// Date (YYYYMMDD format from issue date)
|
|
parts = append(parts, padOrTruncate(unixMilliToDateString(t.IssueDate), 4))
|
|
|
|
// Location code (country code + region code, padded to 6 chars)
|
|
locationCode := "EU"
|
|
if t.CountryCode != "" {
|
|
locationCode = t.CountryCode
|
|
}
|
|
parts = append(parts, locationCode)
|
|
|
|
return strings.Join(parts, "")
|
|
}
|