package api import ( "box-cost/db/model" "box-cost/db/repo" "encoding/json" "errors" "fmt" "math" "time" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // StageRequest 用于添加和更新stage的请求结构 type StageRequest struct { PlanId string `json:"planId"` // PackId string `json:"packId"` CompId string `json:"componentId"` StageId string `json:"stageId,omitempty"` Stages []*model.ComponentStage `json:"stages"` } // 更新计划和部件的价格 func updatePrices(c *gin.Context, db *mongo.Collection, planId primitive.ObjectID, compId string) error { // 1. 获取计划详情 var plan model.ProductPlan err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&plan) if err != nil { return err } // 2. 遍历所有组件,找到目标组件并重新计算价格 var totalPrice float64 = 0 for _, comp := range plan.Pack.Components { compPrice := 0.0 // 计算组件的所有stage总价 for _, stage := range comp.Stages { stagePrice := stage.OrderPrice * float64(stage.OrderCount) compPrice += stagePrice } // 截断组件总价到两位小数 _compPrice := math.Trunc(compPrice*1000) / 1000 // 更新组件总价 if comp.Id == compId { _, err = db.UpdateOne(c.Request.Context(), bson.M{"_id": planId, "pack.components.id": compId}, bson.M{"$set": bson.M{"pack.components.$.totalPrice": _compPrice}}) if err != nil { return err } } totalPrice += compPrice } // 3. 更新计划总价(截断到两位小数) totalPrice = math.Trunc(totalPrice*1000) / 1000 _, err = db.UpdateOne(c.Request.Context(), bson.M{"_id": planId}, bson.M{"$set": bson.M{"totalPrice": &totalPrice}}) return err } // 增加stage // 根据planId packId compentId定位到到计划中的compent // 注意更新计划价格 组件价格 func AddStage(c *gin.Context, apictx *ApiSession) (interface{}, error) { var req StageRequest if err := c.ShouldBindJSON(&req); err != nil { return nil, err } planId, err := primitive.ObjectIDFromHex(req.PlanId) if err != nil { return nil, err } // 更新数据库,添加多个stage update := bson.M{ "$push": bson.M{ "pack.components.$[components].stages": bson.M{ "$each": req.Stages, }, }, "$set": bson.M{ "updateTime": time.Now(), }, } arrayFilters := []interface{}{ bson.M{"components.id": req.CompId}, } opts := options.Update().SetArrayFilters(options.ArrayFilters{ Filters: arrayFilters, }) db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan) result, err := db.UpdateOne(c.Request.Context(), bson.M{"_id": planId}, update, opts, ) if err != nil { return nil, err } // 更新价格 err = updatePrices(c, db, planId, req.CompId) if err != nil { return nil, err } _, err = AddPlanHistory(c, apictx, req.PlanId) if err != nil { fmt.Println("记录历史失败:", err) } return result, nil } // 删除stage // 根据planId packId compentId stageId定位到到计划中的stage // 注意更新计划价格 组件价格 func DeleteStage(c *gin.Context, apictx *ApiSession) (interface{}, error) { var req StageRequest if err := c.ShouldBindJSON(&req); err != nil { return nil, err } planId, err := primitive.ObjectIDFromHex(req.PlanId) if err != nil { return nil, err } if req.StageId == "" || req.CompId == "" { return nil, errors.New("stageId and compId are required") } // 更新数据库 db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan) update := bson.M{ "$pull": bson.M{ "pack.components.$[components].stages": bson.M{ "id": req.StageId, }, }, } arrayFilters := []interface{}{ bson.M{"components.id": req.CompId}, } opts := options.Update().SetArrayFilters(options.ArrayFilters{ Filters: arrayFilters, }) result, err := db.UpdateOne(c.Request.Context(), bson.M{"_id": planId}, update, opts, ) if err != nil { return nil, err } // 更新价格 err = updatePrices(c, db, planId, req.CompId) if err != nil { return nil, err } _, err = AddPlanHistory(c, apictx, req.PlanId) if err != nil { fmt.Println("记录历史失败:", err) } return result, nil } // 根据planId compentId stageId定位到到计划中的stage // 注意更新计划价格 组件价格 // 根据planId compentId stageId定位到到计划中的stage // 注意更新计划价格 组件价格 func updateStage(c *gin.Context, db *mongo.Collection, req *StageRequest) (interface{}, error) { if len(req.PlanId) == 0 || len(req.CompId) == 0 || len(req.StageId) == 0 { return nil, fmt.Errorf("planId, componentId and stageId are required") } planId, err := primitive.ObjectIDFromHex(req.PlanId) if err != nil { return nil, fmt.Errorf("invalid planId: %v", err) } if len(req.Stages) == 0 { return nil, fmt.Errorf("stages array cannot be empty") } // 构建更新操作 update := bson.M{ "$set": bson.M{ "pack.components.$[comp].stages.$[stg]": req.Stages[0], "updateTime": time.Now(), }, } // 使用正确的数组过滤器 arrayFilters := options.ArrayFilters{ Filters: []interface{}{ bson.M{"comp.id": req.CompId}, bson.M{"stg.id": req.StageId}, }, } // 执行更新 opts := options.FindOneAndUpdate(). SetArrayFilters(arrayFilters). SetReturnDocument(options.After) var updatedDoc bson.M err = db.FindOneAndUpdate( c.Request.Context(), bson.M{"_id": planId}, update, opts, ).Decode(&updatedDoc) if err != nil { if err == mongo.ErrNoDocuments { // 打印更详细的错误信息 fmt.Printf("Debug - Failed to update. PlanId: %s, CompId: %s, StageId: %s\n", planId.Hex(), req.CompId, req.StageId) return nil, fmt.Errorf("plan or stage not found, planId: %s, compId: %s, stageId: %s", req.PlanId, req.CompId, req.StageId) } return nil, fmt.Errorf("failed to update stage: %v", err) } fmt.Printf("Debug - Update successful\n") // 更新价格 err = updatePrices(c, db, planId, req.CompId) if err != nil { return nil, fmt.Errorf("failed to update prices: %v", err) } return true, nil } // 更新stages // 根据planId packId compentId stageId定位到到计划中的stage // 注意更新计划价格 组件价格 func UpdateStages(c *gin.Context, apictx *ApiSession) (interface{}, error) { var req StageRequest if err := c.ShouldBindJSON(&req); err != nil { return nil, err } // 参数检查 if len(req.PlanId) == 0 || len(req.CompId) == 0 || len(req.Stages) == 0 { return nil, fmt.Errorf("invalid parameters: planId, componentId and stages are required") } planId, err := primitive.ObjectIDFromHex(req.PlanId) if err != nil { return nil, fmt.Errorf("invalid planId: %v", err) } // 获取更新前的数据用于验证 db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan) ctx := c.Request.Context() var plan model.ProductPlan err = db.FindOne(ctx, bson.M{"_id": planId}).Decode(&plan) if err != nil { return nil, fmt.Errorf("failed to find plan: %v", err) } // 验证组件是否存在 componentExists := false if plan.Pack != nil { for _, comp := range plan.Pack.Components { if comp.Id == req.CompId { componentExists = true break } } } if !componentExists { return nil, fmt.Errorf("component %s not found in plan", req.CompId) } // 准备批量更新操作 var models []mongo.WriteModel now := time.Now() for _, stage := range req.Stages { if stage.Id == "" { continue // 跳过没有ID的stage } update := bson.M{ "$set": bson.M{ "pack.components.$[components].stages.$[stage]": stage, "updateTime": now, }, } arrayFilters := options.ArrayFilters{ Filters: []interface{}{ bson.M{"components.id": req.CompId}, bson.M{"stage.id": stage.Id}, }, } model := mongo.NewUpdateOneModel(). SetFilter(bson.M{"_id": planId}). SetUpdate(update). SetArrayFilters(arrayFilters) models = append(models, model) } if len(models) == 0 { return nil, fmt.Errorf("no valid stages to update") } // 执行批量更新 opts := options.BulkWrite().SetOrdered(false) // 允许并行执行 result, err := db.BulkWrite(c.Request.Context(), models, opts) if err != nil { return nil, fmt.Errorf("failed to update stages: %v", err) } // 更新价格 err = updatePrices(c, db, planId, req.CompId) if err != nil { return nil, fmt.Errorf("failed to update prices: %v", err) } // 记录历史 _, err = AddPlanHistory(c, apictx, req.PlanId) if err != nil { fmt.Println("记录历史失败:", err) } return result, nil } type UpdatePlanFieldsReq struct { PlanId string `json:"planId"` Total int `json:"total,omitempty"` CreateUser string `json:"createUser,omitempty"` Name string `json:"name,omitempty"` } // 单独更新计划字段 func UpdatePlanfileds(c *gin.Context, apictx *ApiSession) (interface{}, error) { var req UpdatePlanFieldsReq if err := c.ShouldBindJSON(&req); err != nil { return nil, err } planId, _ := primitive.ObjectIDFromHex(req.PlanId) if planId.IsZero() { return nil, fmt.Errorf("invalid planId") } result, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, req.PlanId, &model.ProductPlan{ Total: req.Total, CreateUser: req.CreateUser, Name: req.Name, UpdateTime: time.Now(), }) if err != nil { return nil, err } // 记录历史 _, err = AddPlanHistory(c, apictx, req.PlanId) if err != nil { fmt.Println("记录历史失败:", err) } return result, nil } // 新增组件 func AddCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) { _planId := c.Param("id") planId, _ := primitive.ObjectIDFromHex(_planId) if planId.IsZero() { return nil, fmt.Errorf("planId is invalid") } var component model.PackComponent if err := c.ShouldBindJSON(&component); err != nil { return nil, err } // 计算新的总价 newTotalPrice := component.TotalPrice oldPlan := &model.ProductPlan{} db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan) if err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&oldPlan); err != nil { return nil, err } else if oldPlan.TotalPrice != nil { newTotalPrice += *oldPlan.TotalPrice } _newTotalPrice := math.Trunc(newTotalPrice*1000) / 1000 update := bson.M{ "$push": bson.M{ "pack.components": component, }, "$set": bson.M{ "updateTime": time.Now(), "totalPrice": _newTotalPrice, }, "$inc": bson.M{ "pack.compCounts": 1, }, } filter := bson.M{"_id": planId} result := db.FindOneAndUpdate(c.Request.Context(), filter, update) if result.Err() != nil { return nil, result.Err() } _, err := AddPlanHistory(c, apictx, _planId) if err != nil { fmt.Println("记录历史失败:", err) } return true, nil } func DeleteCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) { _planId := c.Param("id") planId, _ := primitive.ObjectIDFromHex(_planId) if planId.IsZero() { return nil, fmt.Errorf("planId is invalid") } componentId := c.Param("componentId") if len(componentId) == 0 { return nil, fmt.Errorf("componentId is invalid") } oldPlan := &model.ProductPlan{} // Find the component to be deleted and its price db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan) var componentPrice float64 if err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&oldPlan); err != nil { return nil, err } else if oldPlan.Pack != nil { for _, comp := range oldPlan.Pack.Components { if comp.Id == componentId { componentPrice = comp.TotalPrice break } } } var newTotalPrice float64 if oldPlan.TotalPrice != nil { newTotalPrice = *oldPlan.TotalPrice - componentPrice if newTotalPrice < 0 { newTotalPrice = 0 } } _newTotalPrice := math.Trunc(newTotalPrice*1000) / 1000 update := bson.M{ "$pull": bson.M{ "pack.components": bson.M{"id": componentId}, }, "$set": bson.M{ "updateTime": time.Now(), "totalPrice": _newTotalPrice, }, "$inc": bson.M{ "pack.compCounts": -1, }, } filter := bson.M{"_id": planId} result := db.FindOneAndUpdate(c.Request.Context(), filter, update) if result.Err() != nil { return nil, result.Err() } _, err := AddPlanHistory(c, apictx, _planId) if err != nil { fmt.Println("记录历史失败:", err) } return true, nil } type UpdateCompnonetReq struct { Name string `json:"name,omitempty"` Stages []*model.ComponentStage `json:"stages,omitempty"` } func UpdateCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) { _planId := c.Param("id") planId, _ := primitive.ObjectIDFromHex(_planId) if planId.IsZero() { return nil, fmt.Errorf("planId is invalid") } componentId := c.Param("componentId") if len(componentId) == 0 { return nil, fmt.Errorf("componentId is invalid") } var nameUpdate UpdateCompnonetReq if err := c.ShouldBindJSON(&nameUpdate); err != nil { return nil, err } // 检查是否有需要更新的字段 if len(nameUpdate.Name) == 0 && len(nameUpdate.Stages) == 0 { return nil, fmt.Errorf("no fields to update") } db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan) update := bson.M{ "$set": bson.M{ "updateTime": time.Now(), }, } if len(nameUpdate.Name) > 0 { update["$set"].(bson.M)["pack.components.$.name"] = nameUpdate.Name } if len(nameUpdate.Stages) > 0 { update["$set"].(bson.M)["pack.components.$.stages"] = nameUpdate.Stages } filter := bson.M{ "_id": planId, "pack.components.id": componentId, } result := db.FindOneAndUpdate(c.Request.Context(), filter, update) if result.Err() != nil { return nil, result.Err() } // 如果更新了stages,需要更新价格 if len(nameUpdate.Stages) > 0 { err := updatePrices(c, db, planId, componentId) if err != nil { return nil, fmt.Errorf("failed to update prices: %v", err) } } // 记录历史 _, err := AddPlanHistory(c, apictx, _planId) if err != nil { fmt.Println("记录历史失败:", err) } return true, nil } // 记录历史 func AddPlanHistory(c *gin.Context, apictx *ApiSession, planId string, ht ...string) (interface{}, error) { htype := "update" if len(ht) > 0 { htype = ht[0] } // 获取更新后的数据并记录历史 newPlan := &model.ProductPlan{} found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{ CollectName: repo.CollectionProductPlan, Query: repo.Map{"_id": planId}, }, newPlan) if !found { return false, errors.New("数据未找到") } if err != nil { return false, err } newPlanBytes, _ := json.Marshal(newPlan) userId, _ := primitive.ObjectIDFromHex(apictx.User.ID) userInfo, err := getUserById(apictx, userId) if err != nil { return false, err } history := &model.History{ Userinfo: userInfo, TargetId: planId, Path: c.Request.URL.Path, Collection: repo.CollectionProductPlan, Type: htype, Content: string(newPlanBytes), CreateTime: time.Now(), } _, err = repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionPlanHistory, history) if err != nil { return false, err } return true, nil }