stages.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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. arrayFilters := []interface{}{
  85. bson.M{"components.id": req.CompId},
  86. }
  87. opts := options.Update().SetArrayFilters(options.ArrayFilters{
  88. Filters: arrayFilters,
  89. })
  90. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  91. result, err := db.UpdateOne(c.Request.Context(),
  92. bson.M{"_id": planId},
  93. update,
  94. opts,
  95. )
  96. if err != nil {
  97. return nil, err
  98. }
  99. // 更新价格
  100. err = updatePrices(c, db, planId, req.CompId)
  101. if err != nil {
  102. return nil, err
  103. }
  104. _, err = AddPlanHistory(c, apictx, req.PlanId)
  105. if err != nil {
  106. fmt.Println("记录历史失败:", err)
  107. }
  108. return result, nil
  109. }
  110. // 删除stage
  111. // 根据planId packId compentId stageId定位到到计划中的stage
  112. // 注意更新计划价格 组件价格
  113. func DeleteStage(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  114. var req StageRequest
  115. if err := c.ShouldBindJSON(&req); err != nil {
  116. return nil, err
  117. }
  118. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  119. if err != nil {
  120. return nil, err
  121. }
  122. if req.StageId == "" || req.CompId == "" {
  123. return nil, errors.New("stageId and compId are required")
  124. }
  125. // 更新数据库
  126. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  127. update := bson.M{
  128. "$pull": bson.M{
  129. "pack.components.$[components].stages": bson.M{
  130. "id": req.StageId,
  131. },
  132. },
  133. }
  134. arrayFilters := []interface{}{
  135. bson.M{"components.id": req.CompId},
  136. }
  137. opts := options.Update().SetArrayFilters(options.ArrayFilters{
  138. Filters: arrayFilters,
  139. })
  140. result, err := db.UpdateOne(c.Request.Context(),
  141. bson.M{"_id": planId},
  142. update,
  143. opts,
  144. )
  145. if err != nil {
  146. return nil, err
  147. }
  148. // 更新价格
  149. err = updatePrices(c, db, planId, req.CompId)
  150. if err != nil {
  151. return nil, err
  152. }
  153. _, err = AddPlanHistory(c, apictx, req.PlanId)
  154. if err != nil {
  155. fmt.Println("记录历史失败:", err)
  156. }
  157. return result, nil
  158. }
  159. // 根据planId compentId stageId定位到到计划中的stage
  160. // 注意更新计划价格 组件价格
  161. // 根据planId compentId stageId定位到到计划中的stage
  162. // 注意更新计划价格 组件价格
  163. func updateStage(c *gin.Context, db *mongo.Collection, req *StageRequest) (interface{}, error) {
  164. if len(req.PlanId) == 0 || len(req.CompId) == 0 || len(req.StageId) == 0 {
  165. return nil, fmt.Errorf("planId, componentId and stageId are required")
  166. }
  167. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  168. if err != nil {
  169. return nil, fmt.Errorf("invalid planId: %v", err)
  170. }
  171. // 构建更新操作
  172. update := bson.M{
  173. "$set": bson.M{
  174. "pack.components.$[comp].stages.$[stg]": req.Stages[0],
  175. "updateTime": time.Now(),
  176. },
  177. }
  178. // 使用正确的数组过滤器
  179. arrayFilters := options.ArrayFilters{
  180. Filters: []interface{}{
  181. bson.M{"comp.id": req.CompId},
  182. bson.M{"stg.id": req.StageId},
  183. },
  184. }
  185. // 执行更新
  186. opts := options.FindOneAndUpdate().
  187. SetArrayFilters(arrayFilters).
  188. SetReturnDocument(options.After)
  189. var updatedDoc bson.M
  190. err = db.FindOneAndUpdate(
  191. c.Request.Context(),
  192. bson.M{"_id": planId},
  193. update,
  194. opts,
  195. ).Decode(&updatedDoc)
  196. if err != nil {
  197. if err == mongo.ErrNoDocuments {
  198. // 打印更详细的错误信息
  199. fmt.Printf("Debug - Failed to update. PlanId: %s, CompId: %s, StageId: %s\n", planId.Hex(), req.CompId, req.StageId)
  200. return nil, fmt.Errorf("plan or stage not found, planId: %s, compId: %s, stageId: %s", req.PlanId, req.CompId, req.StageId)
  201. }
  202. return nil, fmt.Errorf("failed to update stage: %v", err)
  203. }
  204. fmt.Printf("Debug - Update successful\n")
  205. // 更新价格
  206. err = updatePrices(c, db, planId, req.CompId)
  207. if err != nil {
  208. return nil, fmt.Errorf("failed to update prices: %v", err)
  209. }
  210. return true, nil
  211. }
  212. // 更新stages
  213. // 根据planId packId compentId stageId定位到到计划中的stage
  214. // 注意更新计划价格 组件价格
  215. func UpdateStages(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  216. var req StageRequest
  217. if err := c.ShouldBindJSON(&req); err != nil {
  218. return nil, err
  219. }
  220. // 参数检查
  221. if len(req.PlanId) == 0 || len(req.CompId) == 0 || len(req.Stages) == 0 {
  222. return nil, fmt.Errorf("invalid parameters: planId, componentId and stages are required")
  223. }
  224. planId, err := primitive.ObjectIDFromHex(req.PlanId)
  225. if err != nil {
  226. return nil, fmt.Errorf("invalid planId: %v", err)
  227. }
  228. // 获取更新前的数据用于验证
  229. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  230. ctx := c.Request.Context()
  231. var plan model.ProductPlan
  232. err = db.FindOne(ctx, bson.M{"_id": planId}).Decode(&plan)
  233. if err != nil {
  234. return nil, fmt.Errorf("failed to find plan: %v", err)
  235. }
  236. // 验证组件是否存在
  237. componentExists := false
  238. if plan.Pack != nil {
  239. for _, comp := range plan.Pack.Components {
  240. if comp.Id == req.CompId {
  241. componentExists = true
  242. break
  243. }
  244. }
  245. }
  246. if !componentExists {
  247. return nil, fmt.Errorf("component %s not found in plan", req.CompId)
  248. }
  249. // 准备批量更新操作
  250. var models []mongo.WriteModel
  251. now := time.Now()
  252. for _, stage := range req.Stages {
  253. if stage.Id == "" {
  254. continue // 跳过没有ID的stage
  255. }
  256. update := bson.M{
  257. "$set": bson.M{
  258. "pack.components.$[components].stages.$[stage]": stage,
  259. "updateTime": now,
  260. },
  261. }
  262. arrayFilters := options.ArrayFilters{
  263. Filters: []interface{}{
  264. bson.M{"components.id": req.CompId},
  265. bson.M{"stage.id": stage.Id},
  266. },
  267. }
  268. model := mongo.NewUpdateOneModel().
  269. SetFilter(bson.M{"_id": planId}).
  270. SetUpdate(update).
  271. SetArrayFilters(arrayFilters)
  272. models = append(models, model)
  273. }
  274. if len(models) == 0 {
  275. return nil, fmt.Errorf("no valid stages to update")
  276. }
  277. // 执行批量更新
  278. opts := options.BulkWrite().SetOrdered(false) // 允许并行执行
  279. result, err := db.BulkWrite(c.Request.Context(), models, opts)
  280. if err != nil {
  281. return nil, fmt.Errorf("failed to update stages: %v", err)
  282. }
  283. // 更新价格
  284. err = updatePrices(c, db, planId, req.CompId)
  285. if err != nil {
  286. return nil, fmt.Errorf("failed to update prices: %v", err)
  287. }
  288. // 记录历史
  289. _, err = AddPlanHistory(c, apictx, req.PlanId)
  290. if err != nil {
  291. fmt.Println("记录历史失败:", err)
  292. }
  293. return result, nil
  294. }
  295. type UpdatePlanFieldsReq struct {
  296. PlanId string `json:"planId"`
  297. Total int `json:"total,omitempty"`
  298. CreateUser string `json:"createUser,omitempty"`
  299. Name string `json:"name,omitempty"`
  300. }
  301. // 单独更新计划字段
  302. func UpdatePlanfileds(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  303. var req UpdatePlanFieldsReq
  304. if err := c.ShouldBindJSON(&req); err != nil {
  305. return nil, err
  306. }
  307. planId, _ := primitive.ObjectIDFromHex(req.PlanId)
  308. if planId.IsZero() {
  309. return nil, fmt.Errorf("invalid planId")
  310. }
  311. result, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionProductPlan, req.PlanId, &model.ProductPlan{
  312. Total: req.Total,
  313. CreateUser: req.CreateUser,
  314. Name: req.Name,
  315. UpdateTime: time.Now(),
  316. })
  317. if err != nil {
  318. return nil, err
  319. }
  320. // 记录历史
  321. _, err = AddPlanHistory(c, apictx, req.PlanId)
  322. if err != nil {
  323. fmt.Println("记录历史失败:", err)
  324. }
  325. return result, nil
  326. }
  327. // 新增组件
  328. func AddCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  329. _planId := c.Param("id")
  330. planId, _ := primitive.ObjectIDFromHex(_planId)
  331. if planId.IsZero() {
  332. return nil, fmt.Errorf("planId is invalid")
  333. }
  334. var component model.PackComponent
  335. if err := c.ShouldBindJSON(&component); err != nil {
  336. return nil, err
  337. }
  338. // 计算新的总价
  339. newTotalPrice := component.TotalPrice
  340. oldPlan := &model.ProductPlan{}
  341. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  342. if err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&oldPlan); err != nil {
  343. return nil, err
  344. } else if oldPlan.TotalPrice != nil {
  345. newTotalPrice += *oldPlan.TotalPrice
  346. }
  347. _newTotalPrice := math.Trunc(newTotalPrice*1000) / 1000
  348. update := bson.M{
  349. "$push": bson.M{
  350. "pack.components": component,
  351. },
  352. "$set": bson.M{
  353. "updateTime": time.Now(),
  354. "totalPrice": _newTotalPrice,
  355. },
  356. "$inc": bson.M{
  357. "pack.compCounts": 1,
  358. },
  359. }
  360. filter := bson.M{"_id": planId}
  361. result := db.FindOneAndUpdate(c.Request.Context(), filter, update)
  362. if result.Err() != nil {
  363. return nil, result.Err()
  364. }
  365. _, err := AddPlanHistory(c, apictx, _planId)
  366. if err != nil {
  367. fmt.Println("记录历史失败:", err)
  368. }
  369. return true, nil
  370. }
  371. func DeleteCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  372. _planId := c.Param("id")
  373. planId, _ := primitive.ObjectIDFromHex(_planId)
  374. if planId.IsZero() {
  375. return nil, fmt.Errorf("planId is invalid")
  376. }
  377. componentId := c.Param("componentId")
  378. if len(componentId) == 0 {
  379. return nil, fmt.Errorf("componentId is invalid")
  380. }
  381. oldPlan := &model.ProductPlan{}
  382. // Find the component to be deleted and its price
  383. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  384. var componentPrice float64
  385. if err := db.FindOne(c.Request.Context(), bson.M{"_id": planId}).Decode(&oldPlan); err != nil {
  386. return nil, err
  387. } else if oldPlan.Pack != nil {
  388. for _, comp := range oldPlan.Pack.Components {
  389. if comp.Id == componentId {
  390. componentPrice = comp.TotalPrice
  391. break
  392. }
  393. }
  394. }
  395. var newTotalPrice float64
  396. if oldPlan.TotalPrice != nil {
  397. newTotalPrice = *oldPlan.TotalPrice - componentPrice
  398. if newTotalPrice < 0 {
  399. newTotalPrice = 0
  400. }
  401. }
  402. _newTotalPrice := math.Trunc(newTotalPrice*1000) / 1000
  403. update := bson.M{
  404. "$pull": bson.M{
  405. "pack.components": bson.M{"id": componentId},
  406. },
  407. "$set": bson.M{
  408. "updateTime": time.Now(),
  409. "totalPrice": _newTotalPrice,
  410. },
  411. "$inc": bson.M{
  412. "pack.compCounts": -1,
  413. },
  414. }
  415. filter := bson.M{"_id": planId}
  416. result := db.FindOneAndUpdate(c.Request.Context(), filter, update)
  417. if result.Err() != nil {
  418. return nil, result.Err()
  419. }
  420. _, err := AddPlanHistory(c, apictx, _planId)
  421. if err != nil {
  422. fmt.Println("记录历史失败:", err)
  423. }
  424. return true, nil
  425. }
  426. type UpdateCompnonetReq struct {
  427. Name string `json:"name,omitempty"`
  428. Stages []*model.ComponentStage `json:"stages,omitempty"`
  429. }
  430. func UpdateCompnonet(c *gin.Context, apictx *ApiSession) (interface{}, error) {
  431. _planId := c.Param("id")
  432. planId, _ := primitive.ObjectIDFromHex(_planId)
  433. if planId.IsZero() {
  434. return nil, fmt.Errorf("planId is invalid")
  435. }
  436. componentId := c.Param("componentId")
  437. if len(componentId) == 0 {
  438. return nil, fmt.Errorf("componentId is invalid")
  439. }
  440. var nameUpdate UpdateCompnonetReq
  441. if err := c.ShouldBindJSON(&nameUpdate); err != nil {
  442. return nil, err
  443. }
  444. // 检查是否有需要更新的字段
  445. if len(nameUpdate.Name) == 0 && len(nameUpdate.Stages) == 0 {
  446. return nil, fmt.Errorf("no fields to update")
  447. }
  448. db := apictx.Svc.Mongo.GetCollection(repo.CollectionProductPlan)
  449. update := bson.M{
  450. "$set": bson.M{
  451. "updateTime": time.Now(),
  452. },
  453. }
  454. if len(nameUpdate.Name) > 0 {
  455. update["$set"].(bson.M)["pack.components.$.name"] = nameUpdate.Name
  456. }
  457. if len(nameUpdate.Stages) > 0 {
  458. update["$set"].(bson.M)["pack.components.$.stages"] = nameUpdate.Stages
  459. }
  460. filter := bson.M{
  461. "_id": planId,
  462. "pack.components.id": componentId,
  463. }
  464. result := db.FindOneAndUpdate(c.Request.Context(), filter, update)
  465. if result.Err() != nil {
  466. return nil, result.Err()
  467. }
  468. // 如果更新了stages,需要更新价格
  469. if len(nameUpdate.Stages) > 0 {
  470. err := updatePrices(c, db, planId, componentId)
  471. if err != nil {
  472. return nil, fmt.Errorf("failed to update prices: %v", err)
  473. }
  474. }
  475. // 记录历史
  476. _, err := AddPlanHistory(c, apictx, _planId)
  477. if err != nil {
  478. fmt.Println("记录历史失败:", err)
  479. }
  480. return true, nil
  481. }
  482. // 记录历史
  483. func AddPlanHistory(c *gin.Context, apictx *ApiSession, planId string, ht ...string) (interface{}, error) {
  484. htype := "update"
  485. if len(ht) > 0 {
  486. htype = ht[0]
  487. }
  488. // 获取更新后的数据并记录历史
  489. newPlan := &model.ProductPlan{}
  490. found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
  491. CollectName: repo.CollectionProductPlan,
  492. Query: repo.Map{"_id": planId},
  493. }, newPlan)
  494. if !found {
  495. return false, errors.New("数据未找到")
  496. }
  497. if err != nil {
  498. return false, err
  499. }
  500. newPlanBytes, _ := json.Marshal(newPlan)
  501. userId, _ := primitive.ObjectIDFromHex(apictx.User.ID)
  502. userInfo, err := getUserById(apictx, userId)
  503. if err != nil {
  504. return false, err
  505. }
  506. history := &model.History{
  507. Userinfo: userInfo,
  508. TargetId: planId,
  509. Path: c.Request.URL.Path,
  510. Collection: repo.CollectionProductPlan,
  511. Type: htype,
  512. Content: string(newPlanBytes),
  513. CreateTime: time.Now(),
  514. }
  515. _, err = repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionPlanHistory, history)
  516. if err != nil {
  517. return false, err
  518. }
  519. return true, nil
  520. }