stages.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. package api
  2. import (
  3. "box-cost/db/model"
  4. "box-cost/db/repo"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "math"
  9. "time"
  10. "github.com/gin-gonic/gin"
  11. "go.mongodb.org/mongo-driver/bson"
  12. "go.mongodb.org/mongo-driver/bson/primitive"
  13. "go.mongodb.org/mongo-driver/mongo"
  14. "go.mongodb.org/mongo-driver/mongo/options"
  15. )
  16. // StageRequest 用于添加和更新stage的请求结构
  17. type StageRequest struct {
  18. PlanId string `json:"planId"`
  19. // PackId string `json:"packId"`
  20. CompId string `json:"componentId"`
  21. StageId string `json:"stageId,omitempty"`
  22. Stages []*model.ComponentStage `json:"stages"`
  23. }
  24. // 更新计划和部件的价格
  25. func updatePrices(c *gin.Context, db *mongo.Collection, planId primitive.ObjectID, compId string) error {
  26. // 1. 获取计划详情
  27. var plan model.ProductPlan
  28. err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&plan)
  29. if err != nil {
  30. return err
  31. }
  32. // 2. 遍历所有组件,找到目标组件并重新计算价格
  33. var totalPrice float64 = 0
  34. for _, comp := range plan.Pack.Components {
  35. compPrice := 0.0
  36. // 计算组件的所有stage总价
  37. for _, stage := range comp.Stages {
  38. stagePrice := stage.OrderPrice * float64(stage.OrderCount)
  39. compPrice += stagePrice
  40. }
  41. // 截断组件总价到两位小数
  42. _compPrice := math.Trunc(compPrice*1000) / 1000
  43. // 更新组件总价
  44. if comp.Id == compId {
  45. _, err = db.UpdateOne(c.Request.Context(),
  46. bson.M{"_id": planId, "pack.components.id": compId},
  47. bson.M{"$set": bson.M{"pack.components.$.totalPrice": _compPrice}})
  48. if err != nil {
  49. return err
  50. }
  51. }
  52. totalPrice += compPrice
  53. }
  54. // 3. 更新计划总价(截断到两位小数)
  55. totalPrice = math.Trunc(totalPrice*1000) / 1000
  56. _, err = db.UpdateOne(c.Request.Context(),
  57. bson.M{"_id": planId},
  58. bson.M{"$set": bson.M{"totalPrice": &totalPrice}})
  59. return err
  60. }
  61. // 增加stage
  62. // 根据planId packId compentId定位到到计划中的compent
  63. // 注意更新计划价格 组件价格
  64. func AddStage(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  65. var req StageRequest
  66. if err := c.ShouldBindJSON(&req); err != nil {
  67. return nil, err
  68. }
  69. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  70. if err != nil {
  71. return nil, err
  72. }
  73. // // 更新数据库,添加多个stage
  74. // update := bson.M{
  75. // "$push": bson.M{
  76. // "pack.components.$[components].stages": bson.M{
  77. // "$each": req.Stages,
  78. // },
  79. // },
  80. // "$set": bson.M{
  81. // "updateTime": time.Now(),
  82. // },
  83. // }
  84. // if req.Stages == nil {
  85. // update = bson.M{
  86. // "$push": bson.M{
  87. // "pack.components.$[components].stages": bson.M{
  88. // "$each": []string{},
  89. // },
  90. // },
  91. // }
  92. // }
  93. // arrayFilters := []interface{}{
  94. // bson.M{"components.id": req.CompId},
  95. // }
  96. // opts := options.Update().SetArrayFilters(options.ArrayFilters{
  97. // Filters: arrayFilters,
  98. // })
  99. // db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  100. // result, err := db.UpdateOne(c.Request.Context(),
  101. // bson.M{"_id": planId},
  102. // update,
  103. // opts,
  104. // )
  105. // 构建聚合管道更新
  106. pipeline := bson.A{
  107. bson.M{
  108. "$set": bson.M{
  109. "pack.components": bson.M{
  110. "$map": bson.M{
  111. "input": "$pack.components",
  112. "as": "component",
  113. "in": bson.M{
  114. "$cond": bson.A{
  115. // 通过条件直接匹配 CompId,无需 arrayFilters
  116. bson.M{"$eq": bson.A{"$$component.id", req.CompId}},
  117. // 匹配时合并 stages
  118. bson.M{
  119. "$mergeObjects": bson.A{
  120. "$$component",
  121. bson.M{
  122. "stages": bson.M{
  123. "$concatArrays": bson.A{
  124. // 处理 null 或不存在的情况
  125. bson.M{"$ifNull": bson.A{"$$component.stages", bson.A{}}},
  126. req.Stages,
  127. },
  128. },
  129. },
  130. },
  131. },
  132. // 不匹配时保留原组件
  133. "$$component",
  134. },
  135. },
  136. },
  137. },
  138. "updateTime": time.Now(),
  139. },
  140. },
  141. }
  142. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  143. // 移除 arrayFilters 和 opts
  144. result, err := db.UpdateOne(
  145. c.Request.Context(),
  146. bson.M{"_id": planId}, // 直接通过 _id 匹配文档
  147. pipeline,
  148. )
  149. // arrayFilters := options.ArrayFilters{
  150. // Filters: []interface{}{
  151. // bson.M{"components.id": req.CompId},
  152. // },
  153. // }
  154. // opts := options.Update().SetArrayFilters(arrayFilters)
  155. // db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  156. // result, err := db.UpdateOne(c.Request.Context(),
  157. // bson.M{"_id": planId},
  158. // pipeline,
  159. // opts,
  160. // )
  161. if err != nil {
  162. return nil, err
  163. }
  164. // 更新价格
  165. err = updatePrices(c, db, planId, req.CompId)
  166. if err != nil {
  167. return nil, err
  168. }
  169. _, err = AddPlanHistory(c, apictx, req.PlanId)
  170. if err != nil {
  171. fmt.Println("记录历史失败:", err)
  172. }
  173. return result, nil
  174. }
  175. // 删除stage
  176. // 根据planId packId compentId stageId定位到到计划中的stage
  177. // 注意更新计划价格 组件价格
  178. func DeleteStage(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  179. var req StageRequest
  180. if err := c.ShouldBindJSON(&req); err != nil {
  181. return nil, err
  182. }
  183. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  184. if err != nil {
  185. return nil, err
  186. }
  187. if req.StageId == "" || req.CompId == "" {
  188. return nil, errors.New("stageId and compId are required")
  189. }
  190. // 更新数据库
  191. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  192. update := bson.M{
  193. "$pull": bson.M{
  194. "pack.components.$[components].stages": bson.M{
  195. "id": req.StageId,
  196. },
  197. },
  198. }
  199. arrayFilters := []interface{}{
  200. bson.M{"components.id": req.CompId},
  201. }
  202. opts := options.Update().SetArrayFilters(options.ArrayFilters{
  203. Filters: arrayFilters,
  204. })
  205. result, err := db.UpdateOne(c.Request.Context(),
  206. bson.M{"_id": planId},
  207. update,
  208. opts,
  209. )
  210. if err != nil {
  211. return nil, err
  212. }
  213. // 更新价格
  214. err = updatePrices(c, db, planId, req.CompId)
  215. if err != nil {
  216. return nil, err
  217. }
  218. _, err = AddPlanHistory(c, apictx, req.PlanId)
  219. if err != nil {
  220. fmt.Println("记录历史失败:", err)
  221. }
  222. return result, nil
  223. }
  224. // 根据planId compentId stageId定位到到计划中的stage
  225. // 注意更新计划价格 组件价格
  226. // 根据planId compentId stageId定位到到计划中的stage
  227. // 注意更新计划价格 组件价格
  228. func updateStage(c *gin.Context, db *mongo.Collection, req *StageRequest) (interface{}, error) {
  229. if len(req.PlanId) == 0 || len(req.CompId) == 0 || len(req.StageId) == 0 {
  230. return nil, fmt.Errorf("planId, componentId and stageId are required")
  231. }
  232. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  233. if err != nil {
  234. return nil, fmt.Errorf("invalid planId: %v", err)
  235. }
  236. if len(req.Stages) == 0 {
  237. return nil, fmt.Errorf("stages array cannot be empty")
  238. }
  239. // 构建更新操作
  240. update := bson.M{
  241. "$set": bson.M{
  242. "pack.components.$[comp].stages.$[stg]": req.Stages[0],
  243. "updateTime": time.Now(),
  244. },
  245. }
  246. // 使用正确的数组过滤器
  247. arrayFilters := options.ArrayFilters{
  248. Filters: []interface{}{
  249. bson.M{"comp.id": req.CompId},
  250. bson.M{"stg.id": req.StageId},
  251. },
  252. }
  253. // 执行更新
  254. opts := options.FindOneAndUpdate().
  255. SetArrayFilters(arrayFilters).
  256. SetReturnDocument(options.After)
  257. var updatedDoc bson.M
  258. err = db.FindOneAndUpdate(
  259. c.Request.Context(),
  260. bson.M{"_id": planId},
  261. update,
  262. opts,
  263. ).Decode(&updatedDoc)
  264. if err != nil {
  265. if err == mongo.ErrNoDocuments {
  266. // 打印更详细的错误信息
  267. fmt.Printf("Debug - Failed to update. PlanId: %s, CompId: %s, StageId: %s\n", planId.Hex(), req.CompId, req.StageId)
  268. return nil, fmt.Errorf("plan or stage not found, planId: %s, compId: %s, stageId: %s", req.PlanId, req.CompId, req.StageId)
  269. }
  270. return nil, fmt.Errorf("failed to update stage: %v", err)
  271. }
  272. fmt.Printf("Debug - Update successful\n")
  273. // 更新价格
  274. err = updatePrices(c, db, planId, req.CompId)
  275. if err != nil {
  276. return nil, fmt.Errorf("failed to update prices: %v", err)
  277. }
  278. return true, nil
  279. }
  280. // 更新stages
  281. // 根据planId packId compentId stageId定位到到计划中的stage
  282. // 注意更新计划价格 组件价格
  283. func UpdateStages(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  284. var req StageRequest
  285. if err := c.ShouldBindJSON(&req); err != nil {
  286. return nil, err
  287. }
  288. // 参数检查
  289. if len(req.PlanId) == 0 || len(req.CompId) == 0 || len(req.Stages) == 0 {
  290. return nil, fmt.Errorf("invalid parameters: planId, componentId and stages are required")
  291. }
  292. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  293. if err != nil {
  294. return nil, fmt.Errorf("invalid planId: %v", err)
  295. }
  296. // 获取更新前的数据用于验证
  297. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  298. ctx := c.Request.Context()
  299. var plan model.ProductPlan
  300. err = db.FindOne(ctx, bson.M{"_id": planId}).Decode(&plan)
  301. if err != nil {
  302. return nil, fmt.Errorf("failed to find plan: %v", err)
  303. }
  304. // 验证组件是否存在
  305. componentExists := false
  306. if plan.Pack != nil {
  307. for _, comp := range plan.Pack.Components {
  308. if comp.Id == req.CompId {
  309. componentExists = true
  310. break
  311. }
  312. }
  313. }
  314. if !componentExists {
  315. return nil, fmt.Errorf("component %s not found in plan", req.CompId)
  316. }
  317. // 准备批量更新操作
  318. var models []mongo.WriteModel
  319. now := time.Now()
  320. for _, stage := range req.Stages {
  321. if stage.Id == "" {
  322. continue // 跳过没有ID的stage
  323. }
  324. update := bson.M{
  325. "$set": bson.M{
  326. "pack.components.$[components].stages.$[stage]": stage,
  327. "updateTime": now,
  328. },
  329. }
  330. arrayFilters := options.ArrayFilters{
  331. Filters: []interface{}{
  332. bson.M{"components.id": req.CompId},
  333. bson.M{"stage.id": stage.Id},
  334. },
  335. }
  336. model := mongo.NewUpdateOneModel().
  337. SetFilter(bson.M{"_id": planId}).
  338. SetUpdate(update).
  339. SetArrayFilters(arrayFilters)
  340. models = append(models, model)
  341. }
  342. if len(models) == 0 {
  343. return nil, fmt.Errorf("no valid stages to update")
  344. }
  345. // 执行批量更新
  346. opts := options.BulkWrite().SetOrdered(false) // 允许并行执行
  347. result, err := db.BulkWrite(c.Request.Context(), models, opts)
  348. if err != nil {
  349. return nil, fmt.Errorf("failed to update stages: %v", err)
  350. }
  351. // 更新价格
  352. err = updatePrices(c, db, planId, req.CompId)
  353. if err != nil {
  354. return nil, fmt.Errorf("failed to update prices: %v", err)
  355. }
  356. // 记录历史
  357. _, err = AddPlanHistory(c, apictx, req.PlanId)
  358. if err != nil {
  359. fmt.Println("记录历史失败:", err)
  360. }
  361. return result, nil
  362. }
  363. type UpdatePlanFieldsReq struct {
  364. PlanId string `json:"planId"`
  365. Total int `json:"total,omitempty"`
  366. CreateUser string `json:"createUser,omitempty"`
  367. Name string `json:"name,omitempty"`
  368. }
  369. // 单独更新计划字段
  370. func UpdatePlanfileds(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  371. var req UpdatePlanFieldsReq
  372. if err := c.ShouldBindJSON(&req); err != nil {
  373. return nil, err
  374. }
  375. planId, _ := primitive.ObjectIDFromHex(req.PlanId)
  376. if planId.IsZero() {
  377. return nil, fmt.Errorf("invalid planId")
  378. }
  379. result, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, req.PlanId, &model.ProductPlan{
  380. Total: req.Total,
  381. CreateUser: req.CreateUser,
  382. Name: req.Name,
  383. UpdateTime: time.Now(),
  384. })
  385. if err != nil {
  386. return nil, err
  387. }
  388. // 记录历史
  389. _, err = AddPlanHistory(c, apictx, req.PlanId)
  390. if err != nil {
  391. fmt.Println("记录历史失败:", err)
  392. }
  393. return result, nil
  394. }
  395. // 新增组件
  396. func AddCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  397. _planId := c.Param("id")
  398. planId, _ := primitive.ObjectIDFromHex(_planId)
  399. if planId.IsZero() {
  400. return nil, fmt.Errorf("planId is invalid")
  401. }
  402. var component model.PackComponent
  403. if err := c.ShouldBindJSON(&component); err != nil {
  404. return nil, err
  405. }
  406. // 计算新的总价
  407. newTotalPrice := component.TotalPrice
  408. oldPlan := &model.ProductPlan{}
  409. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  410. if err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&oldPlan); err != nil {
  411. return nil, err
  412. } else if oldPlan.TotalPrice != nil {
  413. newTotalPrice += *oldPlan.TotalPrice
  414. }
  415. _newTotalPrice := math.Trunc(newTotalPrice*1000) / 1000
  416. update := bson.M{
  417. "$push": bson.M{
  418. "pack.components": component,
  419. },
  420. "$set": bson.M{
  421. "updateTime": time.Now(),
  422. "totalPrice": _newTotalPrice,
  423. },
  424. "$inc": bson.M{
  425. "pack.compCounts": 1,
  426. },
  427. }
  428. filter := bson.M{"_id": planId}
  429. result := db.FindOneAndUpdate(c.Request.Context(), filter, update)
  430. if result.Err() != nil {
  431. return nil, result.Err()
  432. }
  433. _, err := AddPlanHistory(c, apictx, _planId)
  434. if err != nil {
  435. fmt.Println("记录历史失败:", err)
  436. }
  437. return true, nil
  438. }
  439. func DeleteCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  440. _planId := c.Param("id")
  441. planId, _ := primitive.ObjectIDFromHex(_planId)
  442. if planId.IsZero() {
  443. return nil, fmt.Errorf("planId is invalid")
  444. }
  445. componentId := c.Param("componentId")
  446. if len(componentId) == 0 {
  447. return nil, fmt.Errorf("componentId is invalid")
  448. }
  449. oldPlan := &model.ProductPlan{}
  450. // Find the component to be deleted and its price
  451. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  452. var componentPrice float64
  453. if err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&oldPlan); err != nil {
  454. return nil, err
  455. } else if oldPlan.Pack != nil {
  456. for _, comp := range oldPlan.Pack.Components {
  457. if comp.Id == componentId {
  458. componentPrice = comp.TotalPrice
  459. break
  460. }
  461. }
  462. }
  463. var newTotalPrice float64
  464. if oldPlan.TotalPrice != nil {
  465. newTotalPrice = *oldPlan.TotalPrice - componentPrice
  466. if newTotalPrice < 0 {
  467. newTotalPrice = 0
  468. }
  469. }
  470. _newTotalPrice := math.Trunc(newTotalPrice*1000) / 1000
  471. update := bson.M{
  472. "$pull": bson.M{
  473. "pack.components": bson.M{"id": componentId},
  474. },
  475. "$set": bson.M{
  476. "updateTime": time.Now(),
  477. "totalPrice": _newTotalPrice,
  478. },
  479. "$inc": bson.M{
  480. "pack.compCounts": -1,
  481. },
  482. }
  483. filter := bson.M{"_id": planId}
  484. result := db.FindOneAndUpdate(c.Request.Context(), filter, update)
  485. if result.Err() != nil {
  486. return nil, result.Err()
  487. }
  488. _, err := AddPlanHistory(c, apictx, _planId)
  489. if err != nil {
  490. fmt.Println("记录历史失败:", err)
  491. }
  492. return true, nil
  493. }
  494. type UpdateCompnonetReq struct {
  495. Name string `json:"name,omitempty"`
  496. Stages []*model.ComponentStage `json:"stages,omitempty"`
  497. }
  498. func UpdateCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  499. _planId := c.Param("id")
  500. planId, _ := primitive.ObjectIDFromHex(_planId)
  501. if planId.IsZero() {
  502. return nil, fmt.Errorf("planId is invalid")
  503. }
  504. componentId := c.Param("componentId")
  505. if len(componentId) == 0 {
  506. return nil, fmt.Errorf("componentId is invalid")
  507. }
  508. var nameUpdate UpdateCompnonetReq
  509. if err := c.ShouldBindJSON(&nameUpdate); err != nil {
  510. return nil, err
  511. }
  512. // 检查是否有需要更新的字段
  513. if len(nameUpdate.Name) == 0 && len(nameUpdate.Stages) == 0 {
  514. return nil, fmt.Errorf("no fields to update")
  515. }
  516. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  517. update := bson.M{
  518. "$set": bson.M{
  519. "updateTime": time.Now(),
  520. },
  521. }
  522. if len(nameUpdate.Name) > 0 {
  523. update["$set"].(bson.M)["pack.components.$.name"] = nameUpdate.Name
  524. }
  525. if len(nameUpdate.Stages) > 0 {
  526. update["$set"].(bson.M)["pack.components.$.stages"] = nameUpdate.Stages
  527. }
  528. filter := bson.M{
  529. "_id": planId,
  530. "pack.components.id": componentId,
  531. }
  532. result := db.FindOneAndUpdate(c.Request.Context(), filter, update)
  533. if result.Err() != nil {
  534. return nil, result.Err()
  535. }
  536. // 如果更新了stages,需要更新价格
  537. if len(nameUpdate.Stages) > 0 {
  538. err := updatePrices(c, db, planId, componentId)
  539. if err != nil {
  540. return nil, fmt.Errorf("failed to update prices: %v", err)
  541. }
  542. }
  543. // 记录历史
  544. _, err := AddPlanHistory(c, apictx, _planId)
  545. if err != nil {
  546. fmt.Println("记录历史失败:", err)
  547. }
  548. return true, nil
  549. }
  550. // 记录历史
  551. func AddPlanHistory(c *gin.Context, apictx *ApiSession, planId string, ht ...string) (interface{}, error) {
  552. htype := "update"
  553. if len(ht) > 0 {
  554. htype = ht[0]
  555. }
  556. // 获取更新后的数据并记录历史
  557. newPlan := &model.ProductPlan{}
  558. found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
  559. CollectName: repo.CollectionProductPlan,
  560. Query: repo.Map{"_id": planId},
  561. }, newPlan)
  562. if !found {
  563. return false, errors.New("数据未找到")
  564. }
  565. if err != nil {
  566. return false, err
  567. }
  568. newPlanBytes, _ := json.Marshal(newPlan)
  569. userId, _ := primitive.ObjectIDFromHex(apictx.User.ID)
  570. userInfo, err := getUserById(apictx, userId)
  571. if err != nil {
  572. return false, err
  573. }
  574. history := &model.History{
  575. Userinfo: userInfo,
  576. TargetId: planId,
  577. Path: c.Request.URL.Path,
  578. Collection: repo.CollectionProductPlan,
  579. Type: htype,
  580. Content: string(newPlanBytes),
  581. CreateTime: time.Now(),
  582. }
  583. _, err = repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionPlanHistory, history)
  584. if err != nil {
  585. return false, err
  586. }
  587. return true, nil
  588. }