feat: product proto type.

This commit is contained in:
2026-05-01 09:26:41 +08:00
parent 7b43ccf42f
commit f30cc1ea46
8 changed files with 663 additions and 2 deletions
+41 -2
View File
@@ -21,6 +21,7 @@ type ProductInventoryAdminController struct {
type productInventoryPayload struct { type productInventoryPayload struct {
ProductCode string `json:"productCode"` ProductCode string `json:"productCode"`
ProjectTypeCode string `json:"projectTypeCode"` ProjectTypeCode string `json:"projectTypeCode"`
SuiteID *uint `json:"suiteId"`
SuiteCode string `json:"suiteCode"` SuiteCode string `json:"suiteCode"`
SerialNumber *string `json:"serialNumber"` SerialNumber *string `json:"serialNumber"`
AssetName string `json:"assetName"` AssetName string `json:"assetName"`
@@ -88,11 +89,17 @@ func (pc *ProductInventoryAdminController) Create(c *gin.Context) {
writeError(c, http.StatusBadRequest, err.Error()) writeError(c, http.StatusBadRequest, err.Error())
return return
} }
resolvedSuiteID, resolvedSuiteCode, err := pc.resolveSuite(payload.SuiteID, payload.SuiteCode)
if err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
record := models.ProductInventory{ record := models.ProductInventory{
ProductCode: payload.ProductCode, ProductCode: payload.ProductCode,
ProjectTypeCode: payload.ProjectTypeCode, ProjectTypeCode: payload.ProjectTypeCode,
SuiteCode: payload.SuiteCode, SuiteID: resolvedSuiteID,
SuiteCode: resolvedSuiteCode,
SerialNumber: payload.SerialNumber, SerialNumber: payload.SerialNumber,
AssetName: payload.AssetName, AssetName: payload.AssetName,
Status: models.ProductInventoryStatus(payload.Status), Status: models.ProductInventoryStatus(payload.Status),
@@ -130,10 +137,16 @@ func (pc *ProductInventoryAdminController) Update(c *gin.Context) {
writeError(c, http.StatusBadRequest, err.Error()) writeError(c, http.StatusBadRequest, err.Error())
return return
} }
resolvedSuiteID, resolvedSuiteCode, err := pc.resolveSuite(payload.SuiteID, payload.SuiteCode)
if err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
record.ProductCode = payload.ProductCode record.ProductCode = payload.ProductCode
record.ProjectTypeCode = payload.ProjectTypeCode record.ProjectTypeCode = payload.ProjectTypeCode
record.SuiteCode = payload.SuiteCode record.SuiteID = resolvedSuiteID
record.SuiteCode = resolvedSuiteCode
record.SerialNumber = payload.SerialNumber record.SerialNumber = payload.SerialNumber
record.AssetName = payload.AssetName record.AssetName = payload.AssetName
record.Status = models.ProductInventoryStatus(payload.Status) record.Status = models.ProductInventoryStatus(payload.Status)
@@ -219,6 +232,32 @@ func (pc *ProductInventoryAdminController) validateInventoryPayload(payload prod
return nil return nil
} }
func (pc *ProductInventoryAdminController) resolveSuite(suiteID *uint, suiteCode string) (*uint, string, error) {
if suiteID != nil && *suiteID > 0 {
var suite models.ProductSuite
if err := pc.DB.First(&suite, *suiteID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", errors.New("suiteId is invalid")
}
return nil, "", errors.New("failed to query product suite")
}
return &suite.ID, suite.Code, nil
}
if trimmedCode := strings.TrimSpace(suiteCode); trimmedCode != "" {
var suite models.ProductSuite
if err := pc.DB.Where("code = ?", trimmedCode).First(&suite).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, "", errors.New("suiteCode is invalid")
}
return nil, "", errors.New("failed to query product suite")
}
return &suite.ID, suite.Code, nil
}
return nil, "", nil
}
func (pc *ProductInventoryAdminController) applySoldDefaults(record *models.ProductInventory) { func (pc *ProductInventoryAdminController) applySoldDefaults(record *models.ProductInventory) {
if record == nil { if record == nil {
return return
+191
View File
@@ -0,0 +1,191 @@
package controllers
import (
"encoding/json"
"errors"
"hr_receiver/config"
"hr_receiver/models"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ProductPrototypeAdminController struct {
DB *gorm.DB
}
type productPrototypePayload struct {
Name string `json:"name"`
ProductCode string `json:"productCode"`
ProjectTypeCode string `json:"projectTypeCode"`
AssetName string `json:"assetName"`
Status string `json:"status"`
RegionID *uint32 `json:"regionId"`
StorageLocation string `json:"storageLocation"`
ParameterValues string `json:"parameterValues"`
Notes string `json:"notes"`
}
func NewProductPrototypeAdminController() *ProductPrototypeAdminController {
return &ProductPrototypeAdminController{DB: config.DB}
}
func (pc *ProductPrototypeAdminController) List(c *gin.Context) {
var items []models.ProductPrototype
query := pc.DB.Model(&models.ProductPrototype{}).Order("updated_at DESC, id DESC")
if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
likeValue := "%" + keyword + "%"
query = query.Where("name LIKE ? OR notes LIKE ?", likeValue, likeValue)
}
if productCode := strings.TrimSpace(c.Query("productCode")); productCode != "" {
query = query.Where("product_code = ?", strings.ToLower(productCode))
}
if projectTypeCode := strings.TrimSpace(c.Query("projectTypeCode")); projectTypeCode != "" {
query = query.Where("project_type_code = ?", strings.ToLower(projectTypeCode))
}
if err := query.Find(&items).Error; err != nil {
writeError(c, http.StatusInternalServerError, "failed to query product prototypes")
return
}
writeSuccess(c, http.StatusOK, "query success", items)
}
func (pc *ProductPrototypeAdminController) Create(c *gin.Context) {
var payload productPrototypePayload
if err := c.ShouldBindJSON(&payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
if err := pc.validatePayload(payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
record := models.ProductPrototype{
Name: payload.Name,
ProductCode: payload.ProductCode,
ProjectTypeCode: payload.ProjectTypeCode,
AssetName: payload.AssetName,
Status: payload.Status,
RegionID: payload.RegionID,
StorageLocation: payload.StorageLocation,
ParameterValues: payload.ParameterValues,
Notes: payload.Notes,
}
if err := pc.DB.Create(&record).Error; err != nil {
if strings.Contains(strings.ToLower(err.Error()), "unique") {
writeError(c, http.StatusConflict, "prototype already exists")
return
}
writeError(c, http.StatusInternalServerError, "failed to create prototype")
return
}
writeSuccess(c, http.StatusCreated, "create success", record)
}
func (pc *ProductPrototypeAdminController) Update(c *gin.Context) {
record, err := pc.findByID(c.Param("id"))
if err != nil {
respondProductPrototypeLookupError(c, err)
return
}
var payload productPrototypePayload
if err := c.ShouldBindJSON(&payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
if err := pc.validatePayload(payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
record.Name = payload.Name
record.ProductCode = payload.ProductCode
record.ProjectTypeCode = payload.ProjectTypeCode
record.AssetName = payload.AssetName
record.Status = payload.Status
record.RegionID = payload.RegionID
record.StorageLocation = payload.StorageLocation
record.ParameterValues = payload.ParameterValues
record.Notes = payload.Notes
if err := pc.DB.Save(&record).Error; err != nil {
writeError(c, http.StatusInternalServerError, "failed to update prototype")
return
}
writeSuccess(c, http.StatusOK, "update success", record)
}
func (pc *ProductPrototypeAdminController) Delete(c *gin.Context) {
record, err := pc.findByID(c.Param("id"))
if err != nil {
respondProductPrototypeLookupError(c, err)
return
}
if err := pc.DB.Delete(&record).Error; err != nil {
writeError(c, http.StatusInternalServerError, "failed to delete prototype")
return
}
writeSuccess(c, http.StatusOK, "delete success", nil)
}
func (pc *ProductPrototypeAdminController) findByID(id string) (models.ProductPrototype, error) {
var record models.ProductPrototype
numericID, err := strconv.ParseUint(strings.TrimSpace(id), 10, 64)
if err != nil {
return record, gorm.ErrRecordNotFound
}
if err := pc.DB.First(&record, numericID).Error; err != nil {
return record, err
}
return record, nil
}
func (pc *ProductPrototypeAdminController) validatePayload(payload productPrototypePayload) error {
if strings.TrimSpace(payload.Name) == "" {
return errors.New("name is required")
}
productCode := strings.TrimSpace(strings.ToLower(payload.ProductCode))
if productCode == "" {
return errors.New("productCode is required")
}
var product models.ProductDefinition
if err := pc.DB.Where("code = ? AND is_active = ?", productCode, true).First(&product).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("productCode is invalid")
}
return errors.New("failed to query product definition")
}
if strings.TrimSpace(payload.ProjectTypeCode) != "" {
var projectType models.ProjectType
if err := pc.DB.Where("code = ? AND is_active = ?", strings.ToLower(strings.TrimSpace(payload.ProjectTypeCode)), true).First(&projectType).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("projectTypeCode is invalid")
}
return errors.New("failed to query project type")
}
}
switch models.ProductInventoryStatus(strings.ToLower(strings.TrimSpace(payload.Status))) {
case "", models.ProductInventoryStatusInStock, models.ProductInventoryStatusSold, models.ProductInventoryStatusMaintenance, models.ProductInventoryStatusRetired:
default:
return errors.New("status is invalid")
}
if strings.TrimSpace(payload.ParameterValues) != "" && !json.Valid([]byte(strings.TrimSpace(payload.ParameterValues))) {
return errors.New("parameterValues is invalid")
}
return nil
}
func respondProductPrototypeLookupError(c *gin.Context, err error) {
if errors.Is(err, gorm.ErrRecordNotFound) {
writeError(c, http.StatusNotFound, "product prototype not found")
return
}
writeError(c, http.StatusInternalServerError, "failed to query product prototype")
}
+244
View File
@@ -0,0 +1,244 @@
package controllers
import (
"errors"
"hr_receiver/config"
"hr_receiver/models"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ProductSuiteAdminController struct {
DB *gorm.DB
}
type productSuitePayload struct {
Code string `json:"code"`
Name string `json:"name"`
ProjectTypeCode string `json:"projectTypeCode"`
RegionID *uint32 `json:"regionId"`
SoldTargetType string `json:"soldTargetType"`
SoldTargetName string `json:"soldTargetName"`
Notes string `json:"notes"`
InventoryIDs []uint `json:"inventoryIds"`
}
type productSuiteListItem struct {
models.ProductSuite
Inventories []models.ProductInventory `json:"inventories"`
}
func NewProductSuiteAdminController() *ProductSuiteAdminController {
return &ProductSuiteAdminController{DB: config.DB}
}
func (pc *ProductSuiteAdminController) List(c *gin.Context) {
var suites []models.ProductSuite
query := pc.DB.Model(&models.ProductSuite{}).Order("updated_at DESC, id DESC")
if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
likeValue := "%" + keyword + "%"
query = query.Where("code LIKE ? OR name LIKE ? OR notes LIKE ?", likeValue, likeValue, likeValue)
}
if projectTypeCode := strings.TrimSpace(c.Query("projectTypeCode")); projectTypeCode != "" {
query = query.Where("project_type_code = ?", strings.ToLower(projectTypeCode))
}
if err := query.Find(&suites).Error; err != nil {
writeError(c, http.StatusInternalServerError, "failed to query product suites")
return
}
result := make([]productSuiteListItem, 0, len(suites))
for _, suite := range suites {
var inventories []models.ProductInventory
if err := pc.DB.Where("suite_id = ?", suite.ID).Order("updated_at DESC, id DESC").Find(&inventories).Error; err != nil {
writeError(c, http.StatusInternalServerError, "failed to query suite inventories")
return
}
result = append(result, productSuiteListItem{
ProductSuite: suite,
Inventories: inventories,
})
}
writeSuccess(c, http.StatusOK, "query success", result)
}
func (pc *ProductSuiteAdminController) Create(c *gin.Context) {
var payload productSuitePayload
if err := c.ShouldBindJSON(&payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
if err := pc.validatePayload(payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
record := models.ProductSuite{
Code: payload.Code,
Name: payload.Name,
ProjectTypeCode: payload.ProjectTypeCode,
RegionID: payload.RegionID,
SoldTargetType: payload.SoldTargetType,
SoldTargetName: payload.SoldTargetName,
Notes: payload.Notes,
}
if err := pc.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&record).Error; err != nil {
return err
}
return pc.syncSuiteInventories(tx, record, payload.InventoryIDs)
}); err != nil {
if strings.Contains(strings.ToLower(err.Error()), "unique") {
writeError(c, http.StatusConflict, "suite code already exists")
return
}
writeError(c, http.StatusInternalServerError, "failed to create suite")
return
}
writeSuccess(c, http.StatusCreated, "create success", record)
}
func (pc *ProductSuiteAdminController) Update(c *gin.Context) {
record, err := pc.findByID(c.Param("id"))
if err != nil {
respondProductSuiteLookupError(c, err)
return
}
var payload productSuitePayload
if err := c.ShouldBindJSON(&payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
if err := pc.validatePayload(payload); err != nil {
writeError(c, http.StatusBadRequest, err.Error())
return
}
record.Code = payload.Code
record.Name = payload.Name
record.ProjectTypeCode = payload.ProjectTypeCode
record.RegionID = payload.RegionID
record.SoldTargetType = payload.SoldTargetType
record.SoldTargetName = payload.SoldTargetName
record.Notes = payload.Notes
if err := pc.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Save(&record).Error; err != nil {
return err
}
return pc.syncSuiteInventories(tx, record, payload.InventoryIDs)
}); err != nil {
if strings.Contains(strings.ToLower(err.Error()), "unique") {
writeError(c, http.StatusConflict, "suite code already exists")
return
}
writeError(c, http.StatusInternalServerError, "failed to update suite")
return
}
writeSuccess(c, http.StatusOK, "update success", record)
}
func (pc *ProductSuiteAdminController) Delete(c *gin.Context) {
record, err := pc.findByID(c.Param("id"))
if err != nil {
respondProductSuiteLookupError(c, err)
return
}
if err := pc.DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&models.ProductInventory{}).Where("suite_id = ?", record.ID).Updates(map[string]interface{}{
"suite_id": nil,
"suite_code": "",
}).Error; err != nil {
return err
}
return tx.Delete(&record).Error
}); err != nil {
writeError(c, http.StatusInternalServerError, "failed to delete suite")
return
}
writeSuccess(c, http.StatusOK, "delete success", nil)
}
func (pc *ProductSuiteAdminController) findByID(id string) (models.ProductSuite, error) {
var record models.ProductSuite
numericID, err := strconv.ParseUint(strings.TrimSpace(id), 10, 64)
if err != nil {
return record, gorm.ErrRecordNotFound
}
if err := pc.DB.First(&record, numericID).Error; err != nil {
return record, err
}
return record, nil
}
func (pc *ProductSuiteAdminController) validatePayload(payload productSuitePayload) error {
if strings.TrimSpace(payload.Code) == "" {
return errors.New("code is required")
}
if strings.TrimSpace(payload.Name) == "" {
return errors.New("name is required")
}
if strings.TrimSpace(payload.ProjectTypeCode) != "" {
var projectType models.ProjectType
if err := pc.DB.Where("code = ? AND is_active = ?", strings.ToLower(strings.TrimSpace(payload.ProjectTypeCode)), true).First(&projectType).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("projectTypeCode is invalid")
}
return errors.New("failed to query project type")
}
}
if strings.TrimSpace(payload.SoldTargetType) != "" {
switch strings.ToLower(strings.TrimSpace(payload.SoldTargetType)) {
case "kindergarten", "school", "dealer", "person", "other":
default:
return errors.New("soldTargetType is invalid")
}
}
return nil
}
func (pc *ProductSuiteAdminController) syncSuiteInventories(tx *gorm.DB, suite models.ProductSuite, inventoryIDs []uint) error {
if err := tx.Model(&models.ProductInventory{}).Where("suite_id = ?", suite.ID).Updates(map[string]interface{}{
"suite_id": nil,
"suite_code": "",
}).Error; err != nil {
return err
}
if len(inventoryIDs) == 0 {
return nil
}
var count int64
if err := tx.Model(&models.ProductInventory{}).Where("id IN ?", inventoryIDs).Count(&count).Error; err != nil {
return err
}
if count != int64(len(inventoryIDs)) {
return errors.New("some inventory records do not exist")
}
return tx.Model(&models.ProductInventory{}).Where("id IN ?", inventoryIDs).Updates(map[string]interface{}{
"suite_id": suite.ID,
"suite_code": suite.Code,
}).Error
}
func respondProductSuiteLookupError(c *gin.Context, err error) {
if errors.Is(err, gorm.ErrRecordNotFound) {
writeError(c, http.StatusNotFound, "product suite not found")
return
}
writeError(c, http.StatusInternalServerError, "failed to query product suite")
}
+5
View File
@@ -32,8 +32,10 @@ func main() {
&models.Kindergarten{}, &models.Kindergarten{},
&models.ProjectType{}, &models.ProjectType{},
&models.ProductDefinition{}, &models.ProductDefinition{},
&models.ProductPrototype{},
&models.ProjectProductTemplate{}, &models.ProjectProductTemplate{},
&models.ProductInventory{}, &models.ProductInventory{},
&models.ProductSuite{},
&models.AppFile{}, &models.AppFile{},
&models.AppFileShareCode{}, &models.AppFileShareCode{},
&models.MqttHeartRateRecord{}, &models.MqttHeartRateRecord{},
@@ -59,6 +61,9 @@ func main() {
if err := models.EnsureDefaultProductDefinitions(config.DB); err != nil { if err := models.EnsureDefaultProductDefinitions(config.DB); err != nil {
log.Printf("default product definitions init failed: %v", err) log.Printf("default product definitions init failed: %v", err)
} }
if err := models.EnsureDefaultProductPrototypes(config.DB); err != nil {
log.Printf("default product prototypes init failed: %v", err)
}
if err := models.EnsureDefaultProjectProductTemplates(config.DB); err != nil { if err := models.EnsureDefaultProjectProductTemplates(config.DB); err != nil {
log.Printf("default project product templates init failed: %v", err) log.Printf("default project product templates init failed: %v", err)
} }
+7
View File
@@ -20,6 +20,7 @@ type ProductInventory struct {
ID uint `gorm:"primaryKey" json:"id"` ID uint `gorm:"primaryKey" json:"id"`
ProductCode string `gorm:"size:64;not null;index" json:"productCode"` ProductCode string `gorm:"size:64;not null;index" json:"productCode"`
ProjectTypeCode string `gorm:"size:64;index" json:"projectTypeCode"` ProjectTypeCode string `gorm:"size:64;index" json:"projectTypeCode"`
SuiteID *uint `gorm:"index" json:"suiteId"`
SuiteCode string `gorm:"size:128;index" json:"suiteCode"` SuiteCode string `gorm:"size:128;index" json:"suiteCode"`
SerialNumber *string `gorm:"size:128;uniqueIndex" json:"serialNumber"` SerialNumber *string `gorm:"size:128;uniqueIndex" json:"serialNumber"`
AssetName string `gorm:"size:255" json:"assetName"` AssetName string `gorm:"size:255" json:"assetName"`
@@ -46,6 +47,9 @@ func (p *ProductInventory) BeforeCreate(tx *gorm.DB) (err error) {
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
p.ProductCode = normalizeProductCodeValue(p.ProductCode) p.ProductCode = normalizeProductCodeValue(p.ProductCode)
p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode)) p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode))
if p.SuiteID != nil && *p.SuiteID == 0 {
p.SuiteID = nil
}
p.SuiteCode = strings.TrimSpace(p.SuiteCode) p.SuiteCode = strings.TrimSpace(p.SuiteCode)
p.SerialNumber = normalizeOptionalProductString(p.SerialNumber) p.SerialNumber = normalizeOptionalProductString(p.SerialNumber)
p.AssetName = strings.TrimSpace(p.AssetName) p.AssetName = strings.TrimSpace(p.AssetName)
@@ -66,6 +70,9 @@ func (p *ProductInventory) BeforeCreate(tx *gorm.DB) (err error) {
func (p *ProductInventory) BeforeUpdate(tx *gorm.DB) (err error) { func (p *ProductInventory) BeforeUpdate(tx *gorm.DB) (err error) {
p.ProductCode = normalizeProductCodeValue(p.ProductCode) p.ProductCode = normalizeProductCodeValue(p.ProductCode)
p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode)) p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode))
if p.SuiteID != nil && *p.SuiteID == 0 {
p.SuiteID = nil
}
p.SuiteCode = strings.TrimSpace(p.SuiteCode) p.SuiteCode = strings.TrimSpace(p.SuiteCode)
p.SerialNumber = normalizeOptionalProductString(p.SerialNumber) p.SerialNumber = normalizeOptionalProductString(p.SerialNumber)
p.AssetName = strings.TrimSpace(p.AssetName) p.AssetName = strings.TrimSpace(p.AssetName)
+114
View File
@@ -0,0 +1,114 @@
package models
import (
"errors"
"strings"
"time"
"gorm.io/gorm"
)
type ProductPrototype struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:255;not null" json:"name"`
ProductCode string `gorm:"size:64;not null;index" json:"productCode"`
ProjectTypeCode string `gorm:"size:64;index" json:"projectTypeCode"`
AssetName string `gorm:"size:255" json:"assetName"`
Status string `gorm:"size:32;not null;default:'in_stock'" json:"status"`
RegionID *uint32 `gorm:"index" json:"regionId"`
StorageLocation string `gorm:"size:255" json:"storageLocation"`
ParameterValues string `gorm:"type:text" json:"parameterValues"`
Notes string `gorm:"size:1024" json:"notes"`
CreatedAt int64 `gorm:"not null" json:"created_at"`
UpdatedAt int64 `gorm:"not null" json:"updated_at"`
}
func (ProductPrototype) TableName() string {
return "product_prototypes"
}
func (p *ProductPrototype) BeforeCreate(tx *gorm.DB) (err error) {
now := time.Now().UnixMilli()
p.Name = strings.TrimSpace(p.Name)
p.ProductCode = normalizeProductCodeValue(p.ProductCode)
p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode))
p.AssetName = strings.TrimSpace(p.AssetName)
p.Status = string(normalizeProductInventoryStatus(ProductInventoryStatus(p.Status)))
if p.RegionID != nil && *p.RegionID == 0 {
p.RegionID = nil
}
p.StorageLocation = strings.TrimSpace(p.StorageLocation)
p.ParameterValues = normalizeJSONTextValue(p.ParameterValues)
p.Notes = strings.TrimSpace(p.Notes)
p.CreatedAt = now
p.UpdatedAt = now
return nil
}
func (p *ProductPrototype) BeforeUpdate(tx *gorm.DB) (err error) {
p.Name = strings.TrimSpace(p.Name)
p.ProductCode = normalizeProductCodeValue(p.ProductCode)
p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode))
p.AssetName = strings.TrimSpace(p.AssetName)
p.Status = string(normalizeProductInventoryStatus(ProductInventoryStatus(p.Status)))
if p.RegionID != nil && *p.RegionID == 0 {
p.RegionID = nil
}
p.StorageLocation = strings.TrimSpace(p.StorageLocation)
p.ParameterValues = normalizeJSONTextValue(p.ParameterValues)
p.Notes = strings.TrimSpace(p.Notes)
p.UpdatedAt = time.Now().UnixMilli()
return nil
}
func EnsureDefaultProductPrototypes(db *gorm.DB) error {
defaults := []ProductPrototype{
{
Name: "心率手环默认原型",
ProductCode: "collection_band",
ProjectTypeCode: "heartrate",
AssetName: "智能心率采集手环",
Status: string(ProductInventoryStatusInStock),
ParameterValues: mustJSON(map[string]interface{}{
"bandSize": "M",
"firmwareVersion": "default",
}),
Notes: "用于批量新增智能心率采集手环时复用通用参数",
},
{
Name: "50米手环默认原型",
ProductCode: "collection_band",
ProjectTypeCode: "run50",
AssetName: "50米往返跑手环",
Status: string(ProductInventoryStatusInStock),
ParameterValues: mustJSON(map[string]interface{}{
"bandSize": "M",
"firmwareVersion": "default",
}),
Notes: "用于50米往返跑手环的默认参数",
},
}
for _, item := range defaults {
var existing ProductPrototype
err := db.Where("name = ? AND product_code = ? AND project_type_code = ?", item.Name, item.ProductCode, item.ProjectTypeCode).First(&existing).Error
switch {
case err == nil:
existing.AssetName = item.AssetName
existing.Status = item.Status
existing.ParameterValues = item.ParameterValues
existing.Notes = item.Notes
if err := db.Save(&existing).Error; err != nil {
return err
}
case errors.Is(err, gorm.ErrRecordNotFound):
if err := db.Create(&item).Error; err != nil {
return err
}
default:
return err
}
}
return nil
}
+49
View File
@@ -0,0 +1,49 @@
package models
import (
"strings"
"time"
"gorm.io/gorm"
)
type ProductSuite struct {
ID uint `gorm:"primaryKey" json:"id"`
Code string `gorm:"size:128;not null;uniqueIndex" json:"code"`
Name string `gorm:"size:255;not null" json:"name"`
ProjectTypeCode string `gorm:"size:64;index" json:"projectTypeCode"`
RegionID *uint32 `gorm:"index" json:"regionId"`
SoldTargetType string `gorm:"size:64;index" json:"soldTargetType"`
SoldTargetName string `gorm:"size:255" json:"soldTargetName"`
Notes string `gorm:"size:1024" json:"notes"`
CreatedAt int64 `gorm:"not null" json:"created_at"`
UpdatedAt int64 `gorm:"not null" json:"updated_at"`
}
func (ProductSuite) TableName() string {
return "product_suites"
}
func (p *ProductSuite) BeforeCreate(tx *gorm.DB) (err error) {
now := time.Now().UnixMilli()
p.Code = strings.TrimSpace(p.Code)
p.Name = strings.TrimSpace(p.Name)
p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode))
p.SoldTargetType = strings.TrimSpace(strings.ToLower(p.SoldTargetType))
p.SoldTargetName = strings.TrimSpace(p.SoldTargetName)
p.Notes = strings.TrimSpace(p.Notes)
p.CreatedAt = now
p.UpdatedAt = now
return nil
}
func (p *ProductSuite) BeforeUpdate(tx *gorm.DB) (err error) {
p.Code = strings.TrimSpace(p.Code)
p.Name = strings.TrimSpace(p.Name)
p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode))
p.SoldTargetType = strings.TrimSpace(strings.ToLower(p.SoldTargetType))
p.SoldTargetName = strings.TrimSpace(p.SoldTargetName)
p.Notes = strings.TrimSpace(p.Notes)
p.UpdatedAt = time.Now().UnixMilli()
return nil
}
+12
View File
@@ -17,8 +17,10 @@ func SetupRouter() *gin.Engine {
kindergartenAdminController := controllers.NewKindergartenAdminController() kindergartenAdminController := controllers.NewKindergartenAdminController()
projectTypeAdminController := controllers.NewProjectTypeAdminController() projectTypeAdminController := controllers.NewProjectTypeAdminController()
productDefinitionAdminController := controllers.NewProductDefinitionAdminController() productDefinitionAdminController := controllers.NewProductDefinitionAdminController()
productPrototypeAdminController := controllers.NewProductPrototypeAdminController()
projectProductTemplateAdminController := controllers.NewProjectProductTemplateAdminController() projectProductTemplateAdminController := controllers.NewProjectProductTemplateAdminController()
productInventoryAdminController := controllers.NewProductInventoryAdminController() productInventoryAdminController := controllers.NewProductInventoryAdminController()
productSuiteAdminController := controllers.NewProductSuiteAdminController()
userAdminController := controllers.NewUserAdminController() userAdminController := controllers.NewUserAdminController()
gatewayController := controllers.NewGatewayAdminController() gatewayController := controllers.NewGatewayAdminController()
systemDebugController := controllers.NewSystemDebugController() systemDebugController := controllers.NewSystemDebugController()
@@ -87,6 +89,11 @@ func SetupRouter() *gin.Engine {
admin.PUT("/product-definitions/:id", productDefinitionAdminController.Update) admin.PUT("/product-definitions/:id", productDefinitionAdminController.Update)
admin.DELETE("/product-definitions/:id", productDefinitionAdminController.Delete) admin.DELETE("/product-definitions/:id", productDefinitionAdminController.Delete)
admin.GET("/product-prototypes", productPrototypeAdminController.List)
admin.POST("/product-prototypes", productPrototypeAdminController.Create)
admin.PUT("/product-prototypes/:id", productPrototypeAdminController.Update)
admin.DELETE("/product-prototypes/:id", productPrototypeAdminController.Delete)
admin.GET("/project-product-templates", projectProductTemplateAdminController.List) admin.GET("/project-product-templates", projectProductTemplateAdminController.List)
admin.GET("/product-inventories", productInventoryAdminController.List) admin.GET("/product-inventories", productInventoryAdminController.List)
@@ -94,6 +101,11 @@ func SetupRouter() *gin.Engine {
admin.PUT("/product-inventories/:id", productInventoryAdminController.Update) admin.PUT("/product-inventories/:id", productInventoryAdminController.Update)
admin.DELETE("/product-inventories/:id", productInventoryAdminController.Delete) admin.DELETE("/product-inventories/:id", productInventoryAdminController.Delete)
admin.GET("/product-suites", productSuiteAdminController.List)
admin.POST("/product-suites", productSuiteAdminController.Create)
admin.PUT("/product-suites/:id", productSuiteAdminController.Update)
admin.DELETE("/product-suites/:id", productSuiteAdminController.Delete)
admin.GET("/users", userAdminController.List) admin.GET("/users", userAdminController.List)
admin.POST("/users", userAdminController.Create) admin.POST("/users", userAdminController.Create)
admin.PUT("/users/:id", userAdminController.Update) admin.PUT("/users/:id", userAdminController.Update)