fix: текстовый BOM работает в пасте конфигуратора через единый серверный парсер
Паста BOM на странице конфигурирования теперь распознаёт текстовый и
Inspur-форматы: вместо дублирования парсера на JS добавлен stateless
эндпоинт POST /api/vendor-spec/parse-text, который использует те же
детекторы и парсеры, что и импорт файла (KISS — один парсер на оба
входа). JS-копии _parseInspurBOMText/_isInspurBOMText удалены.
Заголовок конфигурации определяется по маркеру ", в составе:" с любым
префиксом ("Сервер X3" и "Вычислительный GPU сервер X3" → модель X3);
строки тримятся, пробел в начале не попадает в P/N; запятые и дефисы
внутри описания сохраняются (RAID0,1,10; 8-GPU-2304GB).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -690,8 +690,35 @@ func parseInspurBOM(data []byte, sourceFileName string) (*importedWorkspace, err
|
||||
// description (e.g. "8-GPU-2304GB") from being mistaken for the separator.
|
||||
var textBOMItemLine = regexp.MustCompile(`(?i)^(.*\S)\s*[-–—]\s*(\d+)\s*шт\.?\s*$`)
|
||||
|
||||
// textBOMHeaderLine matches the configuration header "Сервер <model>, в составе:".
|
||||
var textBOMHeaderLine = regexp.MustCompile(`(?i)^\s*сервер\s+(.+?)\s*,\s*в\s+составе`)
|
||||
// textBOMHeaderLine matches a configuration header ending with ", в составе:"
|
||||
// regardless of the leading words (e.g. "Сервер <model>" or
|
||||
// "Вычислительный GPU сервер <model>"). The captured group is everything before
|
||||
// the comma; the model is its last whitespace-separated token.
|
||||
var textBOMHeaderLine = regexp.MustCompile(`(?i)^(.*?)\s*,\s*в\s+составе`)
|
||||
|
||||
// ParsePastedBOMText detects and parses a single-column text BOM (Inspur or
|
||||
// Russian text BOM) pasted into the configurator. It shares the same detectors
|
||||
// and parsers as the vendor file-import path, so paste and upload behave
|
||||
// identically. It returns the parsed vendor spec rows and the detected format,
|
||||
// or (nil, "") when the text is not a recognized single-column format and the
|
||||
// caller should fall back to manual column mapping.
|
||||
func ParsePastedBOMText(text string) ([]localdb.VendorSpecItem, string) {
|
||||
data := []byte(text)
|
||||
var ws *importedWorkspace
|
||||
var err error
|
||||
switch {
|
||||
case IsInspurBOM(data):
|
||||
ws, err = parseInspurBOM(data, "")
|
||||
case IsTextBOM(data):
|
||||
ws, err = parseTextBOM(data, "")
|
||||
default:
|
||||
return nil, ""
|
||||
}
|
||||
if err != nil || ws == nil || len(ws.Configurations) == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
return ws.Configurations[0].Rows, ws.SourceFormat
|
||||
}
|
||||
|
||||
// IsTextBOM reports whether data looks like a human-readable Russian text BOM,
|
||||
// i.e. it contains at least one "<description> - <quantity> шт." line.
|
||||
@@ -721,7 +748,9 @@ func parseTextBOM(data []byte, sourceFileName string) (*importedWorkspace, error
|
||||
continue
|
||||
}
|
||||
if m := textBOMHeaderLine.FindStringSubmatch(line); m != nil {
|
||||
serverModel = strings.TrimSpace(m[1])
|
||||
if fields := strings.Fields(m[1]); len(fields) > 0 {
|
||||
serverModel = fields[len(fields)-1]
|
||||
}
|
||||
continue
|
||||
}
|
||||
m := textBOMItemLine.FindStringSubmatch(line)
|
||||
|
||||
Reference in New Issue
Block a user