285 lines
8.9 KiB
Go
285 lines
8.9 KiB
Go
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")
|
|
}
|