feat: расчёт стоимости платного тестирования/аренды GPU-серверов

Новая вкладка «Аренда» в конфигураторе (доступна для проектов с флагом
rental_enabled): расчёт по методичке "Регламент расчёта стоимости
платного тестирования и аренды GPU-серверов" — Разовый + Еженедельный
платёж по каждому компоненту (New/БУ чекбоксом), с аплифтом к Estimate.

Заодно: поддержка (support_code) в Base-вкладке теперь добавляется как
обычный LOT в спеку (SVC_{срок}y{уровень}_{платформа}) через
autocomplete-пикер вместо выпадающего списка — партномер собирается
тем же lot_name-based механизмом, что и для GPU/CPU/памяти, справочная
цена по регламенту техподдержки хранится в qt_settings.support_pricing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-07-22 13:34:07 +03:00
co-authored by Claude Sonnet 5
parent b48436de93
commit 7edb80e498
25 changed files with 1052 additions and 71 deletions
+34 -4
View File
@@ -71,7 +71,6 @@ func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConf
if strings.TrimSpace(req.ServerModel) != "" {
articleResult, articleErr := article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
SupportCode: req.SupportCode,
ServerPricelist: pricelistID,
})
if articleErr != nil {
@@ -169,7 +168,6 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re
if strings.TrimSpace(req.ServerModel) != "" {
articleResult, articleErr := article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
SupportCode: req.SupportCode,
ServerPricelist: pricelistID,
})
if articleErr != nil {
@@ -226,7 +224,6 @@ func (s *LocalConfigurationService) BuildArticlePreview(req *ArticlePreviewReque
}
return article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
SupportCode: req.SupportCode,
ServerPricelist: pricelistID,
})
}
@@ -534,7 +531,6 @@ func (s *LocalConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigR
if strings.TrimSpace(req.ServerModel) != "" {
articleResult, articleErr := article.Build(s.localDB, req.Items, article.BuildOptions{
ServerModel: req.ServerModel,
SupportCode: req.SupportCode,
ServerPricelist: pricelistID,
})
if articleErr != nil {
@@ -1325,6 +1321,40 @@ func (s *LocalConfigurationService) UpdateVendorSpecNoAuth(uuid string, spec loc
return cfg, nil
}
// RentalUpdateRequest carries the editable fields of a configuration's
// Аренда (rental) tab. Persisted independently of the BOM/pricing tabs.
type RentalUpdateRequest struct {
Items []models.RentalItemCondition `json:"items"`
UpliftPercent float64 `json:"uplift_percent"`
}
// UpdateRentalNoAuth updates only the rental fields of a configuration without ownership check.
func (s *LocalConfigurationService) UpdateRentalNoAuth(uuid string, req *RentalUpdateRequest) (*models.Configuration, error) {
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
if err != nil {
return nil, ErrConfigNotFound
}
conditions := make(localdb.RentalItemConditions, 0, len(req.Items))
for _, item := range req.Items {
conditions = append(conditions, localdb.RentalItemCondition{
LotName: models.NormalizeLotName(item.LotName),
Condition: item.Condition,
})
}
localCfg.RentalItems = conditions
localCfg.RentalUpliftPercent = req.UpliftPercent
localCfg.UpdatedAt = time.Now()
localCfg.SyncStatus = "pending"
cfg, err := s.saveWithVersionAndPending(localCfg, "update", "")
if err != nil {
return nil, fmt.Errorf("update rental fields without auth with version: %w", err)
}
return cfg, nil
}
func (s *LocalConfigurationService) ApplyVendorSpecItemsNoAuth(uuid string, items localdb.LocalConfigItems) (*models.Configuration, error) {
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
if err != nil {