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} } // @Summary 获取产品套件列表 // @Description 查询产品套件列表,含套件下的库存明细 // @Tags 产品套件管理 // @Produce json // @Param keyword query string false "关键词(代码/名称/备注模糊搜索)" // @Param projectTypeCode query string false "项目类型代码筛选" // @Security BearerAuth // @Success 200 {object} SwagAPIResponse "查询成功" // @Router /admin/product-suites [get] 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) } // @Summary 创建产品套件 // @Description 创建新的产品套件,可选关联库存记录 // @Tags 产品套件管理 // @Accept json // @Produce json // @Param suite body productSuitePayload true "套件信息" // @Security BearerAuth // @Success 201 {object} SwagAPIResponse "创建成功" // @Failure 400 {object} SwagAPIResponse "请求参数错误" // @Router /admin/product-suites [post] 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) } // @Summary 更新产品套件 // @Description 更新指定产品套件的信息和关联库存 // @Tags 产品套件管理 // @Accept json // @Produce json // @Param id path int true "套件ID" // @Param suite body productSuitePayload true "更新信息" // @Security BearerAuth // @Success 200 {object} SwagAPIResponse "更新成功" // @Failure 400 {object} SwagAPIResponse "请求参数错误" // @Failure 404 {object} SwagAPIResponse "套件不存在" // @Router /admin/product-suites/{id} [put] 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) } // @Summary 删除产品套件 // @Description 删除指定产品套件,自动解除关联库存的绑定 // @Tags 产品套件管理 // @Produce json // @Param id path int true "套件ID" // @Security BearerAuth // @Success 200 {object} SwagAPIResponse "删除成功" // @Failure 404 {object} SwagAPIResponse "套件不存在" // @Router /admin/product-suites/{id} [delete] 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") }