Enhance tender estimated value resolution and add unit tests

- Updated the `ResolvedEstimatedValueAndCurrency` method to aggregate procurement lot values when the tender-level estimated value is not set, improving accuracy in value retrieval.
- Introduced the `AggregateProcurementLotEstimatedValue` function to sum estimated values from procurement lots and return the first found currency.
- Modified the `ToResponseWithLanguage` method to utilize the new estimated value resolution logic.
- Added unit tests for the new functionality, ensuring correct behavior for various scenarios in the `entity_test.go` and `budget_test.go` files.

This update improves the handling of estimated values in tenders, enhancing the overall reliability of the tender management system.
This commit is contained in:
Mazyar
2026-06-20 12:29:47 +03:30
parent 6fb57c41c1
commit 4cfca5aa55
7 changed files with 259 additions and 26 deletions
+57 -16
View File
@@ -3,6 +3,7 @@ package ted
import (
"encoding/xml"
"fmt"
"strconv"
"strings"
"time"
)
@@ -337,15 +338,11 @@ func (pin PriorInformationNotice) GetTenderDeadline() string {
// GetBudget returns the estimated contract amount and currency.
func (pin PriorInformationNotice) GetBudget() (amount string, currency string) {
if pin.firstLot() != nil &&
pin.firstLot().ProcurementProject != nil &&
pin.firstLot().ProcurementProject.RequestedTenderTotal != nil {
contractAmount := pin.firstLot().ProcurementProject.RequestedTenderTotal.EstimatedOverallContractAmount
return contractAmount.Text, contractAmount.CurrencyID
if amount, currency = budgetFromProcurementLots(pin.ProcurementProjectLot); amount != "" {
return amount, currency
}
if pin.ProcurementProject != nil && pin.ProcurementProject.RequestedTenderTotal != nil {
a := pin.ProcurementProject.RequestedTenderTotal.EstimatedOverallContractAmount
return a.Text, a.CurrencyID
return pin.ProcurementProject.RequestedTenderTotal.AmountAndCurrency()
}
return "", ""
}
@@ -1551,12 +1548,34 @@ type ProcessJustification struct {
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
@@ -1972,21 +1991,43 @@ func (cn ContractNotice) GetTenderDeadline() string {
}
// GetBudget returns the estimated contract amount and currency.
// Lot-level BT-27 is preferred; procedure-level BT-27 is used when lots omit RequestedTenderTotal.
// 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 cn.firstLot() != nil &&
cn.firstLot().ProcurementProject != nil &&
cn.firstLot().ProcurementProject.RequestedTenderTotal != nil {
contractAmount := cn.firstLot().ProcurementProject.RequestedTenderTotal.EstimatedOverallContractAmount
return contractAmount.Text, contractAmount.CurrencyID
if amount, currency = budgetFromProcurementLots(cn.ProcurementProjectLots); amount != "" {
return amount, currency
}
if cn.ProcurementProject != nil && cn.ProcurementProject.RequestedTenderTotal != nil {
a := cn.ProcurementProject.RequestedTenderTotal.EstimatedOverallContractAmount
return a.Text, a.CurrencyID
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 &&
@@ -2223,7 +2264,7 @@ func (can ContractAwardNotice) GetMainClassificationDescription() string {
func (can ContractAwardNotice) GetTotalAwardValue() (amount string, currency string) {
noticeResult := can.GetNoticeResult()
if noticeResult != nil && noticeResult.TotalAmount != nil {
return noticeResult.TotalAmount.Text, noticeResult.TotalAmount.CurrencyID
return noticeResult.TotalAmount.AmountAndCurrency()
}
return "", ""
}