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
+16 -27
View File
@@ -12,7 +12,6 @@ import (
type BuildOptions struct {
ServerModel string
SupportCode string
ServerPricelist *uint
}
@@ -95,12 +94,8 @@ func Build(local *localdb.LocalDB, items []models.ConfigItem, opts BuildOptions)
segs = append(segs, namedSeg{"PSU", psuSeg})
}
if strings.TrimSpace(opts.SupportCode) != "" {
code := strings.TrimSpace(opts.SupportCode)
if !isSupportCodeValid(code) {
return BuildResult{}, fmt.Errorf("invalid_support_code")
}
segs = append(segs, namedSeg{"SUPPORT", code})
if supportSeg := buildSupportSegment(items); supportSeg != "" {
segs = append(segs, namedSeg{"SUPPORT", supportSeg})
}
article := strings.Join(namedSegsValues(segs), "-")
@@ -132,28 +127,22 @@ func findSegGroup(segs []namedSeg, group string) int {
return -1
}
func isSupportCodeValid(code string) bool {
if len(code) < 3 {
return false
}
if !strings.Contains(code, "y") {
return false
}
parts := strings.Split(code, "y")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return false
}
for _, r := range parts[0] {
if r < '0' || r > '9' {
return false
// buildSupportSegment finds a support LOT in items (added to the spec via the
// Base tab's support picker, e.g. "SVC_3yB_HGX-H200") and returns its
// duration+level token ("3yB") for the article. Support is a regular BOM
// LOT, not pricelist-backed, so it's detected by lot_name prefix like the
// other lot_name-pattern parsers in this file, rather than by lot_category.
func buildSupportSegment(items []models.ConfigItem) string {
for _, it := range items {
if !strings.HasPrefix(strings.ToUpper(it.LotName), "SVC_") {
continue
}
parts := strings.SplitN(it.LotName, "_", 3)
if len(parts) >= 2 && parts[1] != "" {
return parts[1]
}
}
switch parts[1] {
case "W", "B", "S", "P":
return true
default:
return false
}
return ""
}
func buildCPUSegment(items []models.ConfigItem, cats map[string]string) string {