package models import ( "errors" "strings" "time" "gorm.io/gorm" ) type ProjectProductTemplate struct { ID uint `gorm:"primaryKey" json:"id"` ProjectTypeCode string `gorm:"size:64;not null;index;uniqueIndex:idx_project_product_template" json:"projectTypeCode"` ProductCode string `gorm:"size:64;not null;index;uniqueIndex:idx_project_product_template" json:"productCode"` Quantity *int `json:"quantity"` QuantityRule string `gorm:"size:32;not null;default:'exact'" json:"quantityRule"` IsOptional bool `gorm:"not null;default:false" json:"isOptional"` Notes string `gorm:"size:1024" json:"notes"` Sort int `gorm:"not null;default:0;index" json:"sort"` CreatedAt int64 `gorm:"not null" json:"created_at"` UpdatedAt int64 `gorm:"not null" json:"updated_at"` } func (ProjectProductTemplate) TableName() string { return "project_product_templates" } func (p *ProjectProductTemplate) BeforeCreate(tx *gorm.DB) (err error) { now := time.Now().UnixMilli() p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode)) p.ProductCode = normalizeProductCodeValue(p.ProductCode) p.QuantityRule = normalizeQuantityRuleValue(p.QuantityRule) p.Notes = strings.TrimSpace(p.Notes) p.CreatedAt = now p.UpdatedAt = now return nil } func (p *ProjectProductTemplate) BeforeUpdate(tx *gorm.DB) (err error) { p.ProjectTypeCode = strings.TrimSpace(strings.ToLower(p.ProjectTypeCode)) p.ProductCode = normalizeProductCodeValue(p.ProductCode) p.QuantityRule = normalizeQuantityRuleValue(p.QuantityRule) p.Notes = strings.TrimSpace(p.Notes) p.UpdatedAt = time.Now().UnixMilli() return nil } func normalizeQuantityRuleValue(rule string) string { value := strings.TrimSpace(strings.ToLower(rule)) if value == "" { return "exact" } return value } func intPtr(value int) *int { return &value } func EnsureDefaultProjectProductTemplates(db *gorm.DB) error { defaults := []ProjectProductTemplate{ {ProjectTypeCode: "heartrate", ProductCode: "charging_case_large", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: false, Notes: "可收纳20个手环", Sort: 10}, {ProjectTypeCode: "heartrate", ProductCode: "charging_case_small", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: true, Notes: "可选,可收纳10个手环", Sort: 20}, {ProjectTypeCode: "heartrate", ProductCode: "collection_band", Quantity: nil, QuantityRule: "flexible", IsOptional: false, Notes: "数量按班级规模或实际采购数量配置", Sort: 30}, {ProjectTypeCode: "heartrate", ProductCode: "control_tablet", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: false, Notes: "", Sort: 40}, {ProjectTypeCode: "heartrate", ProductCode: "collection_gateway", Quantity: intPtr(1), QuantityRule: "minimum", IsOptional: false, Notes: "至少1个", Sort: 50}, {ProjectTypeCode: "heartrate", ProductCode: "display_stand_screen", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: true, Notes: "可选,用于投屏展示", Sort: 60}, {ProjectTypeCode: "light", ProductCode: "charging_case_large", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: false, Notes: "可收纳20个手环", Sort: 110}, {ProjectTypeCode: "light", ProductCode: "charging_case_small", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: true, Notes: "可选,可收纳10个手环", Sort: 120}, {ProjectTypeCode: "light", ProductCode: "collection_band", Quantity: nil, QuantityRule: "flexible", IsOptional: false, Notes: "数量按班级规模或实际采购数量配置", Sort: 130}, {ProjectTypeCode: "light", ProductCode: "control_tablet", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: false, Notes: "", Sort: 140}, {ProjectTypeCode: "light", ProductCode: "collection_gateway", Quantity: intPtr(1), QuantityRule: "minimum", IsOptional: false, Notes: "至少1个", Sort: 150}, {ProjectTypeCode: "light", ProductCode: "display_stand_screen", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: true, Notes: "可选,用于投屏展示", Sort: 160}, {ProjectTypeCode: "light", ProductCode: "led_light_strip", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: false, Notes: "心肺耐力测试附加LED灯带", Sort: 170}, {ProjectTypeCode: "run50", ProductCode: "control_tablet", Quantity: intPtr(1), QuantityRule: "exact", IsOptional: false, Notes: "", Sort: 210}, {ProjectTypeCode: "run50", ProductCode: "collection_band", Quantity: nil, QuantityRule: "flexible", IsOptional: false, Notes: "数量按测试规模配置", Sort: 220}, } for _, item := range defaults { var existing ProjectProductTemplate err := db.Where("project_type_code = ? AND product_code = ?", item.ProjectTypeCode, item.ProductCode).First(&existing).Error switch { case err == nil: existing.Quantity = item.Quantity existing.QuantityRule = item.QuantityRule existing.IsOptional = item.IsOptional existing.Notes = item.Notes existing.Sort = item.Sort 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 }