package handler import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" "gorm.io/gorm" "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/model" ) type ImportHandler struct { db *gorm.DB } func NewImportHandler(db *gorm.DB) *ImportHandler { return &ImportHandler{db: db} } // ImportProducts POST /api/v1/import/products // 支持 .xlsx / .csv,列顺序:名称,系列,规格,单位,品牌,最低库存,备注 func (h *ImportHandler) ImportProducts(c *gin.Context) { shopID := middleware.GetShopID(c) file, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "file required"}) return } f, err := file.Open() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer f.Close() xl, err := excelize.OpenReader(f) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid excel file: " + err.Error()}) return } sheetName := xl.GetSheetName(0) rows, err := xl.GetRows(sheetName) if err != nil || len(rows) < 2 { c.JSON(http.StatusBadRequest, gin.H{"error": "empty or invalid sheet"}) return } var products []model.Product var errRows []map[string]interface{} for i, row := range rows[1:] { // 跳过表头 if len(row) < 1 || strings.TrimSpace(row[0]) == "" { continue } p := model.Product{ TenantBase: model.TenantBase{ShopID: shopID}, } p.Name = cell(row, 0) p.Series = cell(row, 1) p.Spec = cell(row, 2) p.Unit = cell(row, 3) p.Brand = cell(row, 4) p.Remark = cell(row, 6) if p.Name == "" { errRows = append(errRows, map[string]interface{}{"row": i + 2, "error": "name is empty"}) continue } products = append(products, p) } if len(products) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "no valid rows", "errors": errRows}) return } if err := h.db.CreateInBatches(&products, 100).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "imported": len(products), "errors": errRows, }) } // ImportPartners POST /api/v1/import/partners // 列顺序:名称,类型(supplier/customer),联系人,电话,地址,备注 func (h *ImportHandler) ImportPartners(c *gin.Context) { shopID := middleware.GetShopID(c) file, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "file required"}) return } f, err := file.Open() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer f.Close() xl, err := excelize.OpenReader(f) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid excel file"}) return } rows, err := xl.GetRows(xl.GetSheetName(0)) if err != nil || len(rows) < 2 { c.JSON(http.StatusBadRequest, gin.H{"error": "empty sheet"}) return } var partners []model.Partner for _, row := range rows[1:] { if len(row) < 1 || strings.TrimSpace(row[0]) == "" { continue } t := cell(row, 1) if t == "" { t = "supplier" } partners = append(partners, model.Partner{ TenantBase: model.TenantBase{ShopID: shopID}, Name: cell(row, 0), Type: t, Contact: cell(row, 2), Phone: cell(row, 3), Address: cell(row, 4), Remark: cell(row, 5), }) } if err := h.db.CreateInBatches(&partners, 100).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"imported": len(partners)}) } func cell(row []string, idx int) string { if idx >= len(row) { return "" } return strings.TrimSpace(row[idx]) }