screen.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package dao
  2. import (
  3. "context"
  4. "encoding/json"
  5. "go-common/app/service/openplatform/ticket-item/model"
  6. "go-common/library/cache/redis"
  7. "go-common/library/ecode"
  8. "go-common/library/log"
  9. "time"
  10. "github.com/jinzhu/gorm"
  11. )
  12. const (
  13. // TypeWithoutSeat 场次类型 站票
  14. TypeWithoutSeat = 2
  15. // TicketTypeElec 场次票类型 电子票
  16. TicketTypeElec = 2
  17. // scTypeNormal 普通场次
  18. scTypeNormal = 1
  19. // scTypePass 通票场次
  20. scTypePass = 2
  21. // scTypeAllPass 联票场次
  22. scTypeAllPass = 3
  23. )
  24. var scTypeName = map[int32]string{
  25. scTypeNormal: "普通场次",
  26. scTypePass: "通票",
  27. scTypeAllPass: "联票",
  28. }
  29. // RawScListByItem 批量取项目场次
  30. func (d *Dao) RawScListByItem(c context.Context, ids []int64) (info map[int64][]*model.Screen, err error) {
  31. info = make(map[int64][]*model.Screen)
  32. rows, err := d.db.Model(&model.Screen{}).Where("project_id in (?)", ids).Rows()
  33. if err != nil {
  34. log.Error("RawScListByItem(%v) error(%v)", ids, err)
  35. return
  36. }
  37. defer rows.Close()
  38. for rows.Next() {
  39. var sc model.Screen
  40. err = d.db.ScanRows(rows, &sc)
  41. if err != nil {
  42. log.Error("RawScListByItem(%v) scan error(%v)", ids, err)
  43. return
  44. }
  45. info[sc.ProjectID] = append(info[sc.ProjectID], &sc)
  46. }
  47. return
  48. }
  49. // CacheScListByItem 缓存取项目票价
  50. func (d *Dao) CacheScListByItem(c context.Context, ids []int64) (info map[int64][]*model.Screen, err error) {
  51. var keys []interface{}
  52. keyPidMap := make(map[string]int64, len(ids))
  53. for _, id := range ids {
  54. key := keyItemScreen(id)
  55. if _, ok := keyPidMap[key]; !ok {
  56. // duplicate mid
  57. keyPidMap[key] = id
  58. keys = append(keys, key)
  59. }
  60. }
  61. conn := d.redis.Get(c)
  62. defer conn.Close()
  63. var data [][]byte
  64. log.Info("MGET %v", model.JSONEncode(keys))
  65. if data, err = redis.ByteSlices(conn.Do("mget", keys...)); err != nil {
  66. log.Error("ItemScList MGET %v ERR: %v", model.JSONEncode(keys), err)
  67. return
  68. }
  69. info = make(map[int64][]*model.Screen)
  70. for _, d := range data {
  71. if d != nil {
  72. var scs []*model.Screen
  73. json.Unmarshal(d, &scs)
  74. info[scs[0].ProjectID] = scs
  75. }
  76. }
  77. return
  78. }
  79. // AddCacheScListByItem 为项目场次添加缓存
  80. func (d *Dao) AddCacheScListByItem(c context.Context, info map[int64][]*model.Screen) (err error) {
  81. conn := d.redis.Get(c)
  82. defer func() {
  83. conn.Flush()
  84. conn.Close()
  85. }()
  86. var data []interface{}
  87. var keys []string
  88. for k, v := range info {
  89. b, _ := json.Marshal(v)
  90. key := keyItemScreen(k)
  91. keys = append(keys, key)
  92. data = append(data, key, b)
  93. }
  94. log.Info("MSET %v", keys)
  95. if err = conn.Send("MSET", data...); err != nil {
  96. return
  97. }
  98. for i := 0; i < len(data); i += 2 {
  99. conn.Send("EXPIRE", data[i], CacheTimeout)
  100. }
  101. return
  102. }
  103. // RawScList 批量取场次
  104. func (d *Dao) RawScList(c context.Context, ids []int64) (info map[int64]*model.Screen, err error) {
  105. info = make(map[int64]*model.Screen)
  106. rows, err := d.db.Model(&model.Screen{}).Where("id in (?)", ids).Rows()
  107. if err != nil {
  108. log.Error("RawScList(%v) error(%v)", ids, err)
  109. return
  110. }
  111. defer rows.Close()
  112. for rows.Next() {
  113. var sc model.Screen
  114. err = d.db.ScanRows(rows, &sc)
  115. if err != nil {
  116. log.Error("RawScList(%v) scan error(%v)", ids, err)
  117. return
  118. }
  119. info[sc.ID] = &sc
  120. }
  121. return
  122. }
  123. // CacheScList 缓存批量取场次
  124. func (d *Dao) CacheScList(c context.Context, ids []int64) (info map[int64]*model.Screen, err error) {
  125. var keys []interface{}
  126. keyPidMap := make(map[string]int64, len(ids))
  127. for _, id := range ids {
  128. key := keyScreen(id)
  129. if _, ok := keyPidMap[key]; !ok {
  130. // duplicate id
  131. keyPidMap[key] = id
  132. keys = append(keys, key)
  133. }
  134. }
  135. conn := d.redis.Get(c)
  136. defer conn.Close()
  137. var data [][]byte
  138. log.Info("MGET %v", model.JSONEncode(keys))
  139. if data, err = redis.ByteSlices(conn.Do("mget", keys...)); err != nil {
  140. log.Error("ScList MGET %v ERR: %v", model.JSONEncode(keys), err)
  141. return
  142. }
  143. info = make(map[int64]*model.Screen)
  144. for _, d := range data {
  145. if d != nil {
  146. var sc model.Screen
  147. json.Unmarshal(d, &sc)
  148. info[sc.ID] = &sc
  149. }
  150. }
  151. return
  152. }
  153. // AddCacheScList 为场次添加缓存
  154. func (d *Dao) AddCacheScList(c context.Context, info map[int64]*model.Screen) (err error) {
  155. conn := d.redis.Get(c)
  156. defer func() {
  157. conn.Flush()
  158. conn.Close()
  159. }()
  160. var data []interface{}
  161. var keys []string
  162. for k, v := range info {
  163. b, _ := json.Marshal(v)
  164. key := keyScreen(k)
  165. keys = append(keys, key)
  166. data = append(data, key, b)
  167. }
  168. log.Info("MSET %v", keys)
  169. if err = conn.Send("MSET", data...); err != nil {
  170. return
  171. }
  172. for i := 0; i < len(data); i += 2 {
  173. conn.Send("EXPIRE", data[i], CacheTimeout)
  174. }
  175. return
  176. }
  177. // CreateOrUpdateScreen 创建或更新场次
  178. func (d *Dao) CreateOrUpdateScreen(c context.Context, tx *gorm.DB, screenInfo model.Screen) (model.Screen, error) {
  179. if screenInfo.ID == 0 {
  180. // create
  181. if err := tx.Create(&screenInfo).Error; err != nil {
  182. log.Error("创建场次失败:%s", err)
  183. tx.Rollback()
  184. return model.Screen{}, err
  185. }
  186. } else {
  187. // update
  188. if err := tx.Model(&model.Screen{}).Where("id = ?", screenInfo.ID).Updates(map[string]interface{}{
  189. "name": screenInfo.Name,
  190. "status": screenInfo.Status,
  191. "type": screenInfo.Type,
  192. "ticket_type": screenInfo.TicketType,
  193. "screen_type": screenInfo.ScreenType,
  194. "delivery_type": screenInfo.DeliveryType,
  195. "pick_seat": screenInfo.PickSeat,
  196. "start_time": screenInfo.StartTime,
  197. "end_time": screenInfo.EndTime,
  198. "project_id": screenInfo.ProjectID,
  199. }).Error; err != nil {
  200. log.Error("更新场次失败:%s", err)
  201. tx.Rollback()
  202. return model.Screen{}, err
  203. }
  204. }
  205. return screenInfo, nil
  206. }
  207. // GetOrUpdatePassSc 更新或新建通联票场次 返回id
  208. func (d *Dao) GetOrUpdatePassSc(c context.Context, tx *gorm.DB, pid int64, tksPass []TicketPass, scStartTimes map[int32]int32,
  209. scEndTimes map[int32]int32, scType int32, opType int32) (int64, error) {
  210. var passScID int64
  211. scTime, err := d.CalPassScTime(scStartTimes, scEndTimes, tksPass)
  212. if err != nil {
  213. tx.Rollback()
  214. return 0, err
  215. }
  216. var screen model.Screen
  217. if d.db.Where("project_id = ? and screen_type = ? and deleted_at = 0", pid, scType).First(&screen).RecordNotFound() {
  218. // 不存在场次并且有票种存在则新建一个
  219. if len(tksPass) > 0 {
  220. passScreen := model.Screen{
  221. ProjectID: pid,
  222. Name: scTypeName[scType],
  223. StartTime: scTime[0],
  224. EndTime: scTime[1],
  225. Type: TypeWithoutSeat, // 站票
  226. TicketType: TicketTypeElec, // 电子票
  227. DeliveryType: 1, // 默认不配送
  228. ScreenType: scType,
  229. Status: opType,
  230. }
  231. if err := tx.Create(&passScreen).Error; err != nil {
  232. log.Error("创建通票或联票场次失败:%s", err)
  233. tx.Rollback()
  234. return 0, err
  235. }
  236. passScID = passScreen.ID
  237. }
  238. } else {
  239. if len(tksPass) > 0 {
  240. // update
  241. if err := tx.Model(&model.Screen{}).Where("id = ?", screen.ID).Updates(map[string]interface{}{
  242. "status": opType,
  243. "start_time": scTime[0],
  244. "end_time": scTime[1],
  245. }).Error; err != nil {
  246. log.Error("更新通票联票场次失败:%s", err)
  247. tx.Rollback()
  248. return 0, err
  249. }
  250. passScID = screen.ID
  251. } else {
  252. // 如果没有通票/联票 删除通票/联票场次
  253. if err := d.DelScreen(c, tx, []int64{screen.ID}, nil, pid); err != nil {
  254. return 0, err
  255. }
  256. passScID = screen.ID
  257. }
  258. }
  259. return passScID, nil
  260. }
  261. // DelScreen 删除场次
  262. func (d *Dao) DelScreen(c context.Context, tx *gorm.DB, oldIDs []int64, newIDs []int64, pid int64) error {
  263. delIDs, _ := model.ClassifyIDs(oldIDs, newIDs)
  264. for _, delID := range delIDs {
  265. if !d.CanDelScreen(delID) {
  266. tx.Rollback()
  267. return ecode.TicketCannotDelSc
  268. }
  269. // 删除场次前把改场次下的票价一起删除
  270. priceIDs, err := d.GetPriceIDs(delID, 1)
  271. if err != nil {
  272. return err
  273. }
  274. if err := d.DelTicket(c, tx, priceIDs, nil, pid, true); err != nil {
  275. return err
  276. }
  277. // 删除场次
  278. if err := tx.Exec("UPDATE screen SET deleted_at=? WHERE id = ? AND project_id = ?", time.Now().Format("2006-01-02 15:04:05"), delID, pid).Error; err != nil {
  279. log.Error("删除场次失败:%s", err)
  280. tx.Rollback()
  281. return err
  282. }
  283. }
  284. return nil
  285. }
  286. // CalPassScTime 获取通联场次的开始结束时间, 取关联场次时间的极值。返回[0]=starttime [1]=endtime
  287. func (d *Dao) CalPassScTime(scStartTimes map[int32]int32, scEndTimes map[int32]int32, tksPass []TicketPass) ([]int32, error) {
  288. var startTimes, endTimes, currStartTimes, currEndTimes []int32
  289. for _, v := range tksPass {
  290. currStartTimes = []int32{}
  291. currEndTimes = []int32{}
  292. for _, v2 := range v.LinkScreens {
  293. if _, ok := scStartTimes[v2]; !ok {
  294. log.Error("关联的场次开始时间不存在")
  295. return []int32{}, ecode.TicketLkScTimeNotFound
  296. }
  297. if _, ok := scEndTimes[v2]; !ok {
  298. log.Error("关联的场次结束时间不存在")
  299. return []int32{}, ecode.TicketLkScTimeNotFound
  300. }
  301. currStartTimes = append(currStartTimes, scStartTimes[v2])
  302. currEndTimes = append(currEndTimes, scEndTimes[v2])
  303. }
  304. startTimes = append(startTimes, model.Min(currStartTimes))
  305. endTimes = append(endTimes, model.Max(currEndTimes))
  306. }
  307. return []int32{model.Min(startTimes), model.Max(endTimes)}, nil
  308. }
  309. // CanDelScreen 检查是否可以删除场次
  310. func (d *Dao) CanDelScreen(id int64) bool {
  311. var priceIDs []int64
  312. // 获取场次下 所有票价id
  313. var prices []model.TicketPrice
  314. if err := d.db.Select("id").Where("screen_id = ? and deleted_at = 0", id).Find(&prices).Error; err != nil {
  315. log.Error("获取场次下所有票价id失败:%s", err)
  316. return false
  317. }
  318. for _, v := range prices {
  319. priceIDs = append(priceIDs, v.ID)
  320. }
  321. if d.HasPromotion(priceIDs, 2) || d.StockChanged(priceIDs) {
  322. log.Error("场次的票价下存在拼团或者库存有变动")
  323. return false
  324. }
  325. return true
  326. }