service.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "time"
  6. "wails-app/internal/dao"
  7. "wails-app/internal/global"
  8. "wails-app/internal/modules/monthly/repository"
  9. paymentSvc "wails-app/internal/modules/payment/service"
  10. "gorm.io/gorm"
  11. )
  12. type MonthlyCardService struct {
  13. repo *repository.MonthlyCardRepository
  14. }
  15. func NewMonthlyCardService() *MonthlyCardService {
  16. return &MonthlyCardService{repo: &repository.MonthlyCardRepository{}}
  17. }
  18. func cardDays(cardType string) int {
  19. switch cardType {
  20. case "month":
  21. return 30
  22. case "quarter":
  23. return 90
  24. case "year":
  25. return 365
  26. default:
  27. return 30
  28. }
  29. }
  30. // Create 办理月卡:月卡、白名单、支付流水在同一事务中,任一步失败整体回滚。
  31. // Create 办理月卡。effectiveDate 可选,未传入时兼容旧调用并使用当天。
  32. func (s *MonthlyCardService) Create(vehicleID uint, cardType string, fee float64, operatorID uint, remark string, effectiveDate ...time.Time) (*dao.MonthlyCard, error) {
  33. existing, err := s.repo.GetByVehicleID(vehicleID)
  34. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  35. return nil, fmt.Errorf("查询有效月卡失败: %w", err)
  36. }
  37. if existing != nil {
  38. return nil, errors.New("该车辆已有有效月卡,请先退卡或等过期后续费")
  39. }
  40. var vehicle dao.Vehicle
  41. if err := global.GVA_DB.First(&vehicle, vehicleID).Error; err != nil {
  42. return nil, errors.New("车辆不存在")
  43. }
  44. now := time.Now()
  45. startDate := normalizeDate(now)
  46. if len(effectiveDate) > 0 && !effectiveDate[0].IsZero() {
  47. startDate = normalizeDate(effectiveDate[0])
  48. }
  49. if startDate.Before(normalizeDate(now)) {
  50. return nil, errors.New("生效日期不能早于今天")
  51. }
  52. endDate := startDate.AddDate(0, 0, cardDays(cardType))
  53. card := &dao.MonthlyCard{
  54. VehicleID: vehicleID, CardType: cardType,
  55. StartDate: startDate, EndDate: endDate,
  56. Fee: fee, PaymentStatus: "paid", OperatorID: operatorID, Remark: remark,
  57. }
  58. err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
  59. if err := tx.Create(card).Error; err != nil {
  60. return fmt.Errorf("创建月卡失败: %w", err)
  61. }
  62. shortlist := dao.Shortlist{
  63. VehicleId: int(vehicleID),
  64. ListType: "白名单",
  65. ExpirationTime: &endDate,
  66. }
  67. if err := tx.Create(&shortlist).Error; err != nil {
  68. return fmt.Errorf("写入白名单失败: %w", err)
  69. }
  70. payRecord := dao.PaymentRecord{
  71. RecordID: 0, PaymentEntry: paymentSvc.EntryCounter, PaymentMethod: paymentSvc.PaymentCash,
  72. Amount: fee, PaidAmount: fee, ChangeAmount: 0,
  73. OperatorID: operatorID, PaidAt: now,
  74. Remark: fmt.Sprintf("包月卡办理(%s)", cardType),
  75. }
  76. if err := tx.Create(&payRecord).Error; err != nil {
  77. return fmt.Errorf("创建支付流水失败: %w", err)
  78. }
  79. return nil
  80. })
  81. if err != nil {
  82. return nil, err
  83. }
  84. return card, nil
  85. }
  86. // Renew 续费:未过期卡从原到期日顺延,已过期卡从今天起算;
  87. // 月卡、白名单、支付流水在同一事务中,任一步失败整体回滚。
  88. // Renew 续费。生效日期必须不早于今天,且有效月卡不能早于原到期日续费。
  89. func (s *MonthlyCardService) Renew(cardID uint, cardType string, fee float64, operatorID uint, effectiveDate ...time.Time) error {
  90. return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
  91. var card dao.MonthlyCard
  92. if err := tx.First(&card, cardID).Error; err != nil {
  93. return errors.New("月卡记录不存在")
  94. }
  95. if card.PaymentStatus != "paid" {
  96. return errors.New("月卡已退卡,无法续费")
  97. }
  98. now := time.Now()
  99. startDate := normalizeDate(now)
  100. hasEffectiveDate := len(effectiveDate) > 0 && !effectiveDate[0].IsZero()
  101. if hasEffectiveDate {
  102. startDate = normalizeDate(effectiveDate[0])
  103. if startDate.Before(normalizeDate(now)) {
  104. return errors.New("生效日期不能早于今天")
  105. }
  106. if card.EndDate.After(startDate) && card.EndDate.After(now) {
  107. return errors.New("有效月卡的生效日期不能早于原到期日")
  108. }
  109. } else {
  110. // 兼容旧的内部调用:有效卡从原到期日顺延,过期卡从当前时间起算。
  111. startDate = card.EndDate
  112. if startDate.Before(now) {
  113. startDate = now
  114. }
  115. }
  116. newEnd := startDate.AddDate(0, 0, cardDays(cardType))
  117. card.EndDate = newEnd
  118. if hasEffectiveDate {
  119. card.StartDate = startDate
  120. }
  121. card.Fee += fee
  122. if err := tx.Save(&card).Error; err != nil {
  123. return fmt.Errorf("更新月卡失败: %w", err)
  124. }
  125. if err := tx.Model(&dao.Shortlist{}).
  126. Where("vehicle_id = ? AND list_type = ?", card.VehicleID, "白名单").
  127. Update("expiration_time", newEnd).Error; err != nil {
  128. return fmt.Errorf("更新白名单失败: %w", err)
  129. }
  130. payRecord := dao.PaymentRecord{
  131. RecordID: 0, PaymentEntry: paymentSvc.EntryCounter, PaymentMethod: paymentSvc.PaymentCash,
  132. Amount: fee, PaidAmount: fee, ChangeAmount: 0,
  133. OperatorID: operatorID, PaidAt: now,
  134. Remark: fmt.Sprintf("包月卡续费(%s)", cardType),
  135. }
  136. if err := tx.Create(&payRecord).Error; err != nil {
  137. return fmt.Errorf("创建支付流水失败: %w", err)
  138. }
  139. return nil
  140. })
  141. }
  142. // Refund 退卡:标记退款状态与移除白名单在同一事务中,任一步失败整体回滚。
  143. func normalizeDate(value time.Time) time.Time {
  144. local := value.In(time.Local)
  145. return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, time.Local)
  146. }
  147. func (s *MonthlyCardService) Refund(cardID uint) error {
  148. return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
  149. var card dao.MonthlyCard
  150. if err := tx.First(&card, cardID).Error; err != nil {
  151. return errors.New("月卡记录不存在")
  152. }
  153. card.PaymentStatus = "refunded"
  154. if err := tx.Save(&card).Error; err != nil {
  155. return fmt.Errorf("更新月卡状态失败: %w", err)
  156. }
  157. if err := tx.Where("vehicle_id = ? AND list_type = ?", card.VehicleID, "白名单").
  158. Unscoped().Delete(&dao.Shortlist{}).Error; err != nil {
  159. return fmt.Errorf("移除白名单失败: %w", err)
  160. }
  161. return nil
  162. })
  163. }
  164. func (s *MonthlyCardService) List(q repository.MonthlyCardQuery) ([]repository.MonthlyCardResult, int64, error) {
  165. return s.repo.List(q)
  166. }