diff --git a/backend/cmd/backfill-pinyin/main.go b/backend/cmd/backfill-pinyin/main.go
new file mode 100644
index 0000000..1f6b948
--- /dev/null
+++ b/backend/cmd/backfill-pinyin/main.go
@@ -0,0 +1,57 @@
+// backfill-pinyin is a one-time migration tool that fills name_pinyin and
+// name_initials for products that have empty values.
+// Usage: go run ./cmd/backfill-pinyin
+package main
+
+import (
+ "log"
+ "strings"
+
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+
+ "github.com/spf13/viper"
+ "github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
+)
+
+func main() {
+ viper.SetConfigName("config")
+ viper.SetConfigType("yaml")
+ viper.AddConfigPath(".")
+ viper.AddConfigPath("./config")
+ viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+ viper.AutomaticEnv()
+ _ = viper.BindEnv("database.dsn", "DATABASE_DSN")
+ if err := viper.ReadInConfig(); err != nil {
+ log.Println("[config] no config file, relying on env vars")
+ }
+
+ dsn := viper.GetString("database.dsn")
+ if dsn == "" {
+ log.Fatal("DATABASE_DSN is required")
+ }
+
+ db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+ Logger: logger.Default.LogMode(logger.Silent),
+ })
+ if err != nil {
+ log.Fatalf("failed to connect database: %v", err)
+ }
+
+ var products []model.Product
+ db.Where("name_pinyin = '' OR name_pinyin IS NULL").Find(&products)
+ if len(products) == 0 {
+ log.Println("No products to backfill.")
+ return
+ }
+ for i := range products {
+ full, initials := util.ToPinyin(products[i].Name)
+ db.Model(&products[i]).Updates(map[string]interface{}{
+ "name_pinyin": full,
+ "name_initials": initials,
+ })
+ }
+ log.Printf("backfill-pinyin: updated %d products", len(products))
+}
diff --git a/backend/config/config.go b/backend/config/config.go
index 55ed34a..94d8ee5 100644
--- a/backend/config/config.go
+++ b/backend/config/config.go
@@ -22,7 +22,9 @@ type ServerConfig struct {
}
type DatabaseConfig struct {
- DSN string `mapstructure:"dsn"`
+ DSN string `mapstructure:"dsn"`
+ MaxIdleConns int `mapstructure:"max_idle_conns"`
+ MaxOpenConns int `mapstructure:"max_open_conns"`
}
type JWTConfig struct {
@@ -71,6 +73,8 @@ func Load() {
viper.SetDefault("server.cors_origin", "*")
viper.SetDefault("jwt.access_expire_min", 60)
viper.SetDefault("jwt.refresh_expire_h", 168) // 7天
+ viper.SetDefault("database.max_idle_conns", 10)
+ viper.SetDefault("database.max_open_conns", 100)
viper.SetDefault("storage.upload_dir", "./uploads/images")
viper.SetDefault("storage.base_url", "http://localhost:8080/images")
viper.SetDefault("storage.public_url", "http://localhost:8081")
diff --git a/backend/internal/handler/auth.go b/backend/internal/handler/auth.go
index 01bc017..e7d89b9 100644
--- a/backend/internal/handler/auth.go
+++ b/backend/internal/handler/auth.go
@@ -5,6 +5,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/wangjia/jiu/backend/internal/service"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type AuthHandler struct {
@@ -63,7 +64,7 @@ func (h *AuthHandler) Register(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, gin.H{"data": result})
+ util.RespondSuccess(c, result)
}
// Refresh POST /api/v1/auth/refresh
@@ -82,5 +83,5 @@ func (h *AuthHandler) Refresh(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, gin.H{"data": pair})
+ util.RespondSuccess(c, pair)
}
diff --git a/backend/internal/handler/finance.go b/backend/internal/handler/finance.go
index 25c939a..1a0ea3f 100644
--- a/backend/internal/handler/finance.go
+++ b/backend/internal/handler/finance.go
@@ -188,7 +188,7 @@ func (h *FinanceHandler) Summary(c *gin.Context) {
ORDER BY total_amount DESC
`, shopID).Scan(&rows)
- c.JSON(http.StatusOK, gin.H{"data": rows})
+ util.RespondSuccess(c, rows)
}
// partnerLastBalance 查询该往来单位最后一条财务记录的余额
diff --git a/backend/internal/handler/inventory.go b/backend/internal/handler/inventory.go
index 1e6bcb0..0b117f1 100644
--- a/backend/internal/handler/inventory.go
+++ b/backend/internal/handler/inventory.go
@@ -178,7 +178,7 @@ func (h *InventoryHandler) CreateCheck(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": req})
+ util.RespondCreated(c, req)
}
// GetCheck GET /api/v1/inventory/checks/:id
@@ -191,7 +191,7 @@ func (h *InventoryHandler) GetCheck(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
- c.JSON(http.StatusOK, gin.H{"data": check})
+ util.RespondSuccess(c, check)
}
// CompleteCheck PUT /api/v1/inventory/checks/:id/complete
diff --git a/backend/internal/handler/license.go b/backend/internal/handler/license.go
index 52d3f93..bc4be82 100644
--- a/backend/internal/handler/license.go
+++ b/backend/internal/handler/license.go
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/service"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type LicenseHandler struct {
@@ -35,7 +36,7 @@ func (h *LicenseHandler) Activate(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusOK, gin.H{"data": lic})
+ util.RespondSuccess(c, lic)
}
// Verify GET /api/v1/license/verify
@@ -52,7 +53,7 @@ func (h *LicenseHandler) Verify(c *gin.Context) {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusOK, gin.H{"data": lic})
+ util.RespondSuccess(c, lic)
}
// Info GET /api/v1/license/info — 当前门店授权概况
@@ -60,7 +61,7 @@ func (h *LicenseHandler) Info(c *gin.Context) {
shopID := middleware.GetShopID(c)
lic, err := h.svc.ShopInfo(shopID)
if err != nil {
- c.JSON(http.StatusOK, gin.H{"data": nil})
+ util.RespondSuccess(c, nil)
return
}
@@ -90,7 +91,7 @@ func (h *LicenseHandler) Devices(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusOK, gin.H{"data": devs})
+ util.RespondSuccess(c, devs)
}
// Deactivate POST /api/v1/license/deactivate
diff --git a/backend/internal/handler/number_rule.go b/backend/internal/handler/number_rule.go
index 57092d7..77d787e 100644
--- a/backend/internal/handler/number_rule.go
+++ b/backend/internal/handler/number_rule.go
@@ -8,6 +8,7 @@ import (
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type NumberRuleHandler struct {
@@ -43,7 +44,7 @@ func (h *NumberRuleHandler) List(c *gin.Context) {
}
}
- c.JSON(http.StatusOK, gin.H{"data": rules})
+ util.RespondSuccess(c, rules)
}
// Update PUT /api/v1/number-rules/:id
@@ -82,5 +83,5 @@ func (h *NumberRuleHandler) Update(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusOK, gin.H{"data": rule})
+ util.RespondSuccess(c, rule)
}
diff --git a/backend/internal/handler/partner.go b/backend/internal/handler/partner.go
index e4e5309..afac2d7 100644
--- a/backend/internal/handler/partner.go
+++ b/backend/internal/handler/partner.go
@@ -70,7 +70,7 @@ func (h *PartnerHandler) Create(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": p})
+ util.RespondCreated(c, p)
}
func (h *PartnerHandler) Update(c *gin.Context) {
@@ -113,7 +113,7 @@ func (h *PartnerHandler) Update(c *gin.Context) {
return
}
h.db.Where("id = ? AND shop_id = ?", p.ID, shopID).First(&p)
- c.JSON(http.StatusOK, gin.H{"data": p})
+ util.RespondSuccess(c, p)
}
func (h *PartnerHandler) Delete(c *gin.Context) {
diff --git a/backend/internal/handler/product.go b/backend/internal/handler/product.go
index 26c6145..715299e 100644
--- a/backend/internal/handler/product.go
+++ b/backend/internal/handler/product.go
@@ -104,7 +104,7 @@ func (h *ProductHandler) Create(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": createErr.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": product})
+ util.RespondCreated(c, product)
}
// Update PUT /api/v1/products/:id
@@ -155,7 +155,7 @@ func (h *ProductHandler) Update(c *gin.Context) {
// 重新读取完整数据返回
h.db.Preload("Category").First(&product, product.ID)
- c.JSON(http.StatusOK, gin.H{"data": product})
+ util.RespondSuccess(c, product)
}
// Detail GET /api/v1/products/:id/detail
@@ -178,7 +178,7 @@ func (h *ProductHandler) Detail(c *gin.Context) {
h.db.Model(&product).Update("public_id", product.PublicID)
}
- c.JSON(http.StatusOK, gin.H{"data": product})
+ util.RespondSuccess(c, product)
}
// QRCode GET /api/v1/products/:id/qrcode
@@ -248,7 +248,7 @@ func (h *ProductHandler) FindOrCreate(c *gin.Context) {
if len(attrUpdates) > 0 {
h.db.Model(&product).Updates(attrUpdates)
}
- c.JSON(http.StatusOK, gin.H{"data": product})
+ util.RespondSuccess(c, product)
return
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -277,13 +277,13 @@ func (h *ProductHandler) FindOrCreate(c *gin.Context) {
// Race condition: try to find the record created by another request
if h.db.Where("shop_id = ? AND name = ? AND series = ? AND spec = ? AND deleted_at IS NULL",
shopID, req.Name, req.Series, req.Spec).First(&product).Error == nil {
- c.JSON(http.StatusOK, gin.H{"data": product})
+ util.RespondSuccess(c, product)
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": createErr.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": product})
+ util.RespondCreated(c, product)
}
// Delete DELETE /api/v1/products/:id (软删除)
diff --git a/backend/internal/handler/product_attr.go b/backend/internal/handler/product_attr.go
index bce31ad..96fcf71 100644
--- a/backend/internal/handler/product_attr.go
+++ b/backend/internal/handler/product_attr.go
@@ -8,6 +8,7 @@ import (
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
)
// ProductAttrHandler 处理商品属性字典(产地/保质期/储存方式/描述文档)
@@ -25,7 +26,7 @@ func (h *ProductAttrHandler) ListOrigins(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductOriginOption, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductAttrHandler) CreateOrigin(c *gin.Context) {
@@ -49,7 +50,7 @@ func (h *ProductAttrHandler) CreateOrigin(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductAttrHandler) UpdateOrigin(c *gin.Context) {
@@ -71,7 +72,7 @@ func (h *ProductAttrHandler) UpdateOrigin(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"code": req.Code, "name": req.Name, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductAttrHandler) DeleteOrigin(c *gin.Context) {
@@ -86,7 +87,7 @@ func (h *ProductAttrHandler) ListShelfLives(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductShelfLifeOption, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductAttrHandler) CreateShelfLife(c *gin.Context) {
@@ -110,7 +111,7 @@ func (h *ProductAttrHandler) CreateShelfLife(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductAttrHandler) UpdateShelfLife(c *gin.Context) {
@@ -132,7 +133,7 @@ func (h *ProductAttrHandler) UpdateShelfLife(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"code": req.Code, "name": req.Name, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductAttrHandler) DeleteShelfLife(c *gin.Context) {
@@ -147,7 +148,7 @@ func (h *ProductAttrHandler) ListStorages(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductStorageOption, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductAttrHandler) CreateStorage(c *gin.Context) {
@@ -171,7 +172,7 @@ func (h *ProductAttrHandler) CreateStorage(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductAttrHandler) UpdateStorage(c *gin.Context) {
@@ -193,7 +194,7 @@ func (h *ProductAttrHandler) UpdateStorage(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"code": req.Code, "name": req.Name, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductAttrHandler) DeleteStorage(c *gin.Context) {
@@ -208,7 +209,7 @@ func (h *ProductAttrHandler) ListDescriptionDocs(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductDescriptionDoc, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductAttrHandler) CreateDescriptionDoc(c *gin.Context) {
@@ -232,7 +233,7 @@ func (h *ProductAttrHandler) CreateDescriptionDoc(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductAttrHandler) UpdateDescriptionDoc(c *gin.Context) {
@@ -254,7 +255,7 @@ func (h *ProductAttrHandler) UpdateDescriptionDoc(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"title": req.Title, "content": req.Content, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductAttrHandler) DeleteDescriptionDoc(c *gin.Context) {
diff --git a/backend/internal/handler/product_image.go b/backend/internal/handler/product_image.go
index 933cb63..9e145e5 100644
--- a/backend/internal/handler/product_image.go
+++ b/backend/internal/handler/product_image.go
@@ -18,6 +18,7 @@ import (
"github.com/wangjia/jiu/backend/config"
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type ProductImageHandler struct {
@@ -105,7 +106,7 @@ func (h *ProductImageHandler) Upload(c *gin.Context) {
return
}
- c.JSON(http.StatusCreated, gin.H{"data": pi})
+ util.RespondCreated(c, pi)
}
// Delete DELETE /api/v1/products/:id/images/:image_id
diff --git a/backend/internal/handler/product_option.go b/backend/internal/handler/product_option.go
index 2d564f3..63993d8 100644
--- a/backend/internal/handler/product_option.go
+++ b/backend/internal/handler/product_option.go
@@ -8,6 +8,7 @@ import (
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type ProductOptionHandler struct {
@@ -24,7 +25,7 @@ func (h *ProductOptionHandler) ListNames(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductNameOption, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductOptionHandler) CreateName(c *gin.Context) {
@@ -48,7 +49,7 @@ func (h *ProductOptionHandler) CreateName(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductOptionHandler) UpdateName(c *gin.Context) {
@@ -70,7 +71,7 @@ func (h *ProductOptionHandler) UpdateName(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"code": req.Code, "name": req.Name, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductOptionHandler) DeleteName(c *gin.Context) {
@@ -85,7 +86,7 @@ func (h *ProductOptionHandler) ListSeries(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductSeriesOption, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductOptionHandler) CreateSeries(c *gin.Context) {
@@ -109,7 +110,7 @@ func (h *ProductOptionHandler) CreateSeries(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductOptionHandler) UpdateSeries(c *gin.Context) {
@@ -131,7 +132,7 @@ func (h *ProductOptionHandler) UpdateSeries(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"code": req.Code, "name": req.Name, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductOptionHandler) DeleteSeries(c *gin.Context) {
@@ -146,7 +147,7 @@ func (h *ProductOptionHandler) ListSpecs(c *gin.Context) {
shopID := middleware.GetShopID(c)
items := make([]model.ProductSpecOption, 0)
h.db.Where("shop_id = ?", shopID).Order("id ASC").Find(&items)
- c.JSON(http.StatusOK, gin.H{"data": items})
+ util.RespondSuccess(c, items)
}
func (h *ProductOptionHandler) CreateSpec(c *gin.Context) {
@@ -172,7 +173,7 @@ func (h *ProductOptionHandler) CreateSpec(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": item})
+ util.RespondCreated(c, item)
}
func (h *ProductOptionHandler) UpdateSpec(c *gin.Context) {
@@ -195,7 +196,7 @@ func (h *ProductOptionHandler) UpdateSpec(c *gin.Context) {
h.db.Model(&item).Where("shop_id = ?", shopID).Updates(map[string]interface{}{
"code": req.Code, "name": req.Name, "quantity": req.Quantity, "remark": req.Remark,
})
- c.JSON(http.StatusOK, gin.H{"data": item})
+ util.RespondSuccess(c, item)
}
func (h *ProductOptionHandler) DeleteSpec(c *gin.Context) {
diff --git a/backend/internal/handler/stock_in.go b/backend/internal/handler/stock_in.go
index 24d3c3c..94bc670 100644
--- a/backend/internal/handler/stock_in.go
+++ b/backend/internal/handler/stock_in.go
@@ -72,7 +72,7 @@ func (h *StockInHandler) Get(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
- c.JSON(http.StatusOK, gin.H{"data": order})
+ util.RespondSuccess(c, order)
}
// Create POST /api/v1/stock-in/orders
@@ -114,7 +114,7 @@ func (h *StockInHandler) Create(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": req})
+ util.RespondCreated(c, req)
}
// Update PUT /api/v1/stock-in/orders/:id (只允许草稿状态)
diff --git a/backend/internal/handler/stock_out.go b/backend/internal/handler/stock_out.go
index dfb2197..a6470a8 100644
--- a/backend/internal/handler/stock_out.go
+++ b/backend/internal/handler/stock_out.go
@@ -63,7 +63,7 @@ func (h *StockOutHandler) Get(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
- c.JSON(http.StatusOK, gin.H{"data": order})
+ util.RespondSuccess(c, order)
}
@@ -110,7 +110,7 @@ func (h *StockOutHandler) Create(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": req})
+ util.RespondCreated(c, req)
}
// Update PUT /api/v1/stock-out/orders/:id (只允许草稿状态)
diff --git a/backend/internal/handler/user.go b/backend/internal/handler/user.go
index 211d801..5f4ab7c 100644
--- a/backend/internal/handler/user.go
+++ b/backend/internal/handler/user.go
@@ -9,6 +9,7 @@ import (
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type UserHandler struct {
@@ -25,7 +26,7 @@ func (h *UserHandler) List(c *gin.Context) {
users := make([]model.User, 0)
h.db.Where("shop_id = ? AND deleted_at IS NULL", shopID).
Order("id ASC").Find(&users)
- c.JSON(http.StatusOK, gin.H{"data": users})
+ util.RespondSuccess(c, users)
}
// Create POST /api/v1/users
@@ -64,7 +65,7 @@ func (h *UserHandler) Create(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "用户名已存在"})
return
}
- c.JSON(http.StatusCreated, gin.H{"data": u})
+ util.RespondCreated(c, u)
}
// Update PUT /api/v1/users/:id
@@ -100,7 +101,7 @@ func (h *UserHandler) Update(c *gin.Context) {
h.db.Model(&u).Where("shop_id = ?", shopID).Updates(updates)
}
h.db.Where("id = ? AND shop_id = ?", u.ID, shopID).First(&u)
- c.JSON(http.StatusOK, gin.H{"data": u})
+ util.RespondSuccess(c, u)
}
// ResetPassword PUT /api/v1/users/:id/reset-password
diff --git a/backend/internal/handler/warehouse.go b/backend/internal/handler/warehouse.go
index b0b739e..0139089 100644
--- a/backend/internal/handler/warehouse.go
+++ b/backend/internal/handler/warehouse.go
@@ -8,6 +8,7 @@ import (
"github.com/wangjia/jiu/backend/internal/middleware"
"github.com/wangjia/jiu/backend/internal/model"
+ "github.com/wangjia/jiu/backend/internal/util"
)
type WarehouseHandler struct {
@@ -22,7 +23,7 @@ func (h *WarehouseHandler) List(c *gin.Context) {
shopID := middleware.GetShopID(c)
warehouses := make([]model.Warehouse, 0)
h.db.Where("shop_id = ? AND deleted_at IS NULL", shopID).Find(&warehouses)
- c.JSON(http.StatusOK, gin.H{"data": warehouses})
+ util.RespondSuccess(c, warehouses)
}
func (h *WarehouseHandler) Create(c *gin.Context) {
@@ -34,7 +35,7 @@ func (h *WarehouseHandler) Create(c *gin.Context) {
}
w.ShopID = shopID
h.db.Create(&w)
- c.JSON(http.StatusCreated, gin.H{"data": w})
+ util.RespondCreated(c, w)
}
func (h *WarehouseHandler) Update(c *gin.Context) {
@@ -63,7 +64,7 @@ func (h *WarehouseHandler) Update(c *gin.Context) {
return
}
h.db.Where("id = ? AND shop_id = ?", w.ID, shopID).First(&w)
- c.JSON(http.StatusOK, gin.H{"data": w})
+ util.RespondSuccess(c, w)
}
func (h *WarehouseHandler) Delete(c *gin.Context) {
diff --git a/backend/internal/model/stock.go b/backend/internal/model/stock.go
index af6a0cb..28a3163 100644
--- a/backend/internal/model/stock.go
+++ b/backend/internal/model/stock.go
@@ -91,16 +91,19 @@ type Inventory struct {
StockInItemID *uint64 `json:"stock_in_item_id"`
InventoryCheckID *uint64 `json:"inventory_check_id"`
Quantity float64 `gorm:"type:decimal(12,3);not null;default:0" json:"quantity"`
- ProductCode string `gorm:"size:50" json:"product_code"`
- ProductName string `gorm:"size:200" json:"product_name"`
- Series string `gorm:"size:100" json:"series"`
- Spec string `gorm:"size:100" json:"spec"`
- Unit string `gorm:"size:20" json:"unit"`
- WarehouseName string `gorm:"size:100" json:"warehouse_name"`
- UnitPrice *float64 `gorm:"type:decimal(16,2)" json:"unit_price"`
- ProductionDate *Date `gorm:"type:date" json:"production_date"`
- BatchNo string `gorm:"size:50" json:"batch_no"`
- SupplierName string `gorm:"size:200" json:"supplier_name"`
+ // Snapshot fields: copied from product/warehouse/stock-in at approval time.
+ // They reflect the state at the moment of stock-in and are NOT updated when the
+ // referenced product or warehouse record is later modified.
+ ProductCode string `gorm:"size:50" json:"product_code"`
+ ProductName string `gorm:"size:200" json:"product_name"`
+ Series string `gorm:"size:100" json:"series"`
+ Spec string `gorm:"size:100" json:"spec"`
+ Unit string `gorm:"size:20" json:"unit"`
+ WarehouseName string `gorm:"size:100" json:"warehouse_name"`
+ UnitPrice *float64 `gorm:"type:decimal(16,2)" json:"unit_price"`
+ ProductionDate *Date `gorm:"type:date" json:"production_date"`
+ BatchNo string `gorm:"size:50" json:"batch_no"`
+ SupplierName string `gorm:"size:200" json:"supplier_name"`
Remark string `gorm:"size:500" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
diff --git a/backend/main.go b/backend/main.go
index 6c56996..8d664f6 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -12,7 +12,6 @@ import (
"github.com/wangjia/jiu/backend/config"
"github.com/wangjia/jiu/backend/internal/model"
"github.com/wangjia/jiu/backend/internal/router"
- "github.com/wangjia/jiu/backend/internal/util"
)
func main() {
@@ -38,9 +37,6 @@ func main() {
// 自动迁移(GORM AutoMigrate 只增不删,生产安全)
autoMigrate(db)
- // 回填存量商品的拼音索引(一次性,已有值的跳过)
- backfillPinyin(db)
-
// 启动 Gin
gin.SetMode(config.C.Server.Mode)
r := gin.New()
@@ -90,8 +86,8 @@ func initDB() *gorm.DB {
}
sqlDB, _ := db.DB()
- sqlDB.SetMaxIdleConns(10)
- sqlDB.SetMaxOpenConns(100)
+ sqlDB.SetMaxIdleConns(config.C.Database.MaxIdleConns)
+ sqlDB.SetMaxOpenConns(config.C.Database.MaxOpenConns)
return db
}
@@ -132,18 +128,3 @@ func autoMigrate(db *gorm.DB) {
log.Println("AutoMigrate completed")
}
-func backfillPinyin(db *gorm.DB) {
- var products []model.Product
- db.Where("name_pinyin = '' OR name_pinyin IS NULL").Find(&products)
- if len(products) == 0 {
- return
- }
- for i := range products {
- full, initials := util.ToPinyin(products[i].Name)
- db.Model(&products[i]).Updates(map[string]interface{}{
- "name_pinyin": full,
- "name_initials": initials,
- })
- }
- log.Printf("backfillPinyin: updated %d products", len(products))
-}
diff --git a/web/_data/site.json b/web/_data/site.json
index 117a1d7..75db653 100644
--- a/web/_data/site.json
+++ b/web/_data/site.json
@@ -2,9 +2,10 @@
"name": "岩美酒库管理系统",
"tagline": "为酒行与酒店设计的库存管理平台",
"copyright": "© 2026 岩美科技 · 保留所有权利",
- "icp": "沪 ICP 备 2026000000 号 · 沪公网安备 31010000000000 号",
+ "icp": "",
"support": {
- "email": "yammy2023@163.com"
+ "email": "yammy2023@163.com",
+ "wechat": ""
},
"appUrl": "/app/",
"appBaseUrl": "https://jiu.51yanmei.com",
diff --git a/web/_includes/footer.njk b/web/_includes/footer.njk
index fdcc5b1..0f372f2 100644
--- a/web/_includes/footer.njk
+++ b/web/_includes/footer.njk
@@ -5,6 +5,7 @@
为酒行与酒店设计的库存、审核、财务一体化管理平台。
@@ -42,7 +43,7 @@ diff --git a/web/index.njk b/web/index.njk index c7e28dc..17db6f9 100644 --- a/web/index.njk +++ b/web/index.njk @@ -555,7 +555,7 @@ details[open] .faq-q .icon { transform: rotate(45deg); }