31ea370cea
- 修复 JWTConfig 缺少 mapstructure tag 导致 access_expire_min 解析为 0, token 签发即过期,所有 API 请求返回 401 - 全部 config struct 补齐 mapstructure tag(secret/dsn/hmac_secret 等) - 模型层从 hotel/HotelID 统一重命名为 shop/ShopID - 删除旧 migrations(001-004),新增 001_init 综合迁移文件 - 更新 schema.sql、testutil、handler/service/model 相关引用 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
3.6 KiB
Go
157 lines
3.6 KiB
Go
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])
|
|
}
|