Refine vendor mapping CSV operations and ignore import flow

This commit is contained in:
Mikhail Chusavitin
2026-02-27 16:49:39 +03:00
parent 6f1de7a20e
commit 04ce74ca1b
5 changed files with 106 additions and 30 deletions

View File

@@ -1413,17 +1413,19 @@ func (h *PricingHandler) ImportVendorMappingsCSV(c *gin.Context) {
vendorIdx := findHeader("vendor", "вендор", "поставщик")
partnumberIdx := findHeader("partnumber", "part_number", "pn", "партномер", "артикул")
lotIdx := findHeader("lot_name", "lot", "лот", аименование_lot")
descIdx := findHeader("description", "desc", "описание", "comment", "комментарий")
descIdx := findHeader("description", "desc", "описание", "comment", "комментарий")
ignoreIdx := findHeader("ignore", "ignored", "игнор", "ignore_flag")
startRow := 1
if partnumberIdx < 0 || lotIdx < 0 {
// Fallback: no header row, assume fixed order vendor;partnumber;lot_name;description
vendorIdx, partnumberIdx, lotIdx, descIdx = 0, 1, 2, 3
startRow = 0
}
// Fallback: no header row, assume fixed order vendor;partnumber;lot_name;description;ignore
vendorIdx, partnumberIdx, lotIdx, descIdx, ignoreIdx = 0, 1, 2, 3, 4
startRow = 0
}
imported := 0
skipped := 0
imported := 0
ignoredCnt := 0
skipped := 0
errs := make([]string, 0, 10)
field := func(rec []string, idx int) string {
@@ -1437,23 +1439,32 @@ func (h *PricingHandler) ImportVendorMappingsCSV(c *gin.Context) {
rec := rows[i]
vendor := field(rec, vendorIdx)
partnumber := field(rec, partnumberIdx)
lotName := field(rec, lotIdx)
description := field(rec, descIdx)
lotName := field(rec, lotIdx)
description := field(rec, descIdx)
ignoreRaw := field(rec, ignoreIdx)
// Skip empty lines.
if vendor == "" && partnumber == "" && lotName == "" && description == "" {
skipped++
continue
}
if partnumber == "" {
if vendor == "" && partnumber == "" && lotName == "" && description == "" && ignoreRaw == "" {
skipped++
continue
}
if partnumber == "" {
errs = append(errs, fmt.Sprintf("row %d: partnumber is required", i+1))
continue
}
if lotName == "" {
errs = append(errs, fmt.Sprintf("row %d: lot_name is required", i+1))
continue
}
if err := h.vendorMapService.UpsertMapping(vendor, partnumber, lotName, description, nil); err != nil {
if lotName == "" {
if strings.TrimSpace(ignoreRaw) != "" {
if err := h.vendorMapService.SetIgnore(vendor, partnumber, h.dbUsername, true); err != nil {
errs = append(errs, fmt.Sprintf("row %d: ignore failed: %v", i+1, err))
continue
}
ignoredCnt++
continue
}
errs = append(errs, fmt.Sprintf("row %d: lot_name is required (or set Ignore)", i+1))
continue
}
if err := h.vendorMapService.UpsertMapping(vendor, partnumber, lotName, description, nil); err != nil {
errs = append(errs, fmt.Sprintf("row %d: %v", i+1, err))
continue
}
@@ -1465,6 +1476,7 @@ func (h *PricingHandler) ImportVendorMappingsCSV(c *gin.Context) {
"error": "import failed",
"errors": errs,
"imported": imported,
"ignored": ignoredCnt,
"skipped": skipped,
})
return
@@ -1472,12 +1484,67 @@ func (h *PricingHandler) ImportVendorMappingsCSV(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "import completed",
"imported": imported,
"ignored": ignoredCnt,
"skipped": skipped,
"errors": errs,
"hasErrors": len(errs) > 0,
})
}
func (h *PricingHandler) ExportUnmappedVendorMappingsCSV(c *gin.Context) {
if h.vendorMapService == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"error": "Глобальные сопоставления доступны только в онлайн режиме",
"offline": true,
})
return
}
filename := fmt.Sprintf("vendor_mappings_unmapped_%s.csv", time.Now().Format("20060102_150405"))
c.Header("Content-Type", "text/csv; charset=utf-8")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
c.Status(http.StatusOK)
_, _ = c.Writer.Write([]byte{0xEF, 0xBB, 0xBF})
w := csv.NewWriter(c.Writer)
w.Comma = ';'
if err := w.Write([]string{"vendor", "partnumber", "lot_name", "description", "ignore"}); err != nil {
c.String(http.StatusInternalServerError, "export failed: %v", err)
return
}
page := 1
perPage := 500
for {
items, total, err := h.vendorMapService.List(page, perPage, "", true, false)
if err != nil {
c.String(http.StatusInternalServerError, "export failed: %v", err)
return
}
for _, it := range items {
if err := w.Write([]string{
strings.TrimSpace(it.Vendor),
strings.TrimSpace(it.Partnumber),
"", // to be filled by user
strings.TrimSpace(it.Description),
"", // optional ignore marker
}); err != nil {
c.String(http.StatusInternalServerError, "export failed: %v", err)
return
}
}
w.Flush()
if err := w.Error(); err != nil {
c.String(http.StatusInternalServerError, "export failed: %v", err)
return
}
if int64(page*perPage) >= total || len(items) == 0 {
break
}
page++
}
}
func (h *PricingHandler) IgnoreVendorMapping(c *gin.Context) {
if h.vendorMapService == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{