stages.go 15 KB

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