Refine vendor mapping CSV operations and ignore import flow
This commit is contained in:
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user