package service import ( "errors" "fmt" "time" "wails-app/internal/dao" "wails-app/internal/global" "wails-app/internal/modules/monthly/repository" paymentSvc "wails-app/internal/modules/payment/service" "gorm.io/gorm" ) type MonthlyCardService struct { repo *repository.MonthlyCardRepository } func NewMonthlyCardService() *MonthlyCardService { return &MonthlyCardService{repo: &repository.MonthlyCardRepository{}} } func cardDays(cardType string) int { switch cardType { case "month": return 30 case "quarter": return 90 case "year": return 365 default: return 30 } } // Create 办理月卡:月卡、白名单、支付流水在同一事务中,任一步失败整体回滚。 // Create 办理月卡。effectiveDate 可选,未传入时兼容旧调用并使用当天。 func (s *MonthlyCardService) Create(vehicleID uint, cardType string, fee float64, operatorID uint, remark string, effectiveDate ...time.Time) (*dao.MonthlyCard, error) { existing, err := s.repo.GetByVehicleID(vehicleID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("查询有效月卡失败: %w", err) } if existing != nil { return nil, errors.New("该车辆已有有效月卡,请先退卡或等过期后续费") } var vehicle dao.Vehicle if err := global.GVA_DB.First(&vehicle, vehicleID).Error; err != nil { return nil, errors.New("车辆不存在") } now := time.Now() startDate := normalizeDate(now) if len(effectiveDate) > 0 && !effectiveDate[0].IsZero() { startDate = normalizeDate(effectiveDate[0]) } if startDate.Before(normalizeDate(now)) { return nil, errors.New("生效日期不能早于今天") } endDate := startDate.AddDate(0, 0, cardDays(cardType)) card := &dao.MonthlyCard{ VehicleID: vehicleID, CardType: cardType, StartDate: startDate, EndDate: endDate, Fee: fee, PaymentStatus: "paid", OperatorID: operatorID, Remark: remark, } err = global.GVA_DB.Transaction(func(tx *gorm.DB) error { if err := tx.Create(card).Error; err != nil { return fmt.Errorf("创建月卡失败: %w", err) } shortlist := dao.Shortlist{ VehicleId: int(vehicleID), ListType: "白名单", ExpirationTime: &endDate, } if err := tx.Create(&shortlist).Error; err != nil { return fmt.Errorf("写入白名单失败: %w", err) } payRecord := dao.PaymentRecord{ RecordID: 0, PaymentEntry: paymentSvc.EntryCounter, PaymentMethod: paymentSvc.PaymentCash, Amount: fee, PaidAmount: fee, ChangeAmount: 0, OperatorID: operatorID, PaidAt: now, Remark: fmt.Sprintf("包月卡办理(%s)", cardType), } if err := tx.Create(&payRecord).Error; err != nil { return fmt.Errorf("创建支付流水失败: %w", err) } return nil }) if err != nil { return nil, err } return card, nil } // Renew 续费:未过期卡从原到期日顺延,已过期卡从今天起算; // 月卡、白名单、支付流水在同一事务中,任一步失败整体回滚。 // Renew 续费。生效日期必须不早于今天,且有效月卡不能早于原到期日续费。 func (s *MonthlyCardService) Renew(cardID uint, cardType string, fee float64, operatorID uint, effectiveDate ...time.Time) error { return global.GVA_DB.Transaction(func(tx *gorm.DB) error { var card dao.MonthlyCard if err := tx.First(&card, cardID).Error; err != nil { return errors.New("月卡记录不存在") } if card.PaymentStatus != "paid" { return errors.New("月卡已退卡,无法续费") } now := time.Now() startDate := normalizeDate(now) hasEffectiveDate := len(effectiveDate) > 0 && !effectiveDate[0].IsZero() if hasEffectiveDate { startDate = normalizeDate(effectiveDate[0]) if startDate.Before(normalizeDate(now)) { return errors.New("生效日期不能早于今天") } if card.EndDate.After(startDate) && card.EndDate.After(now) { return errors.New("有效月卡的生效日期不能早于原到期日") } } else { // 兼容旧的内部调用:有效卡从原到期日顺延,过期卡从当前时间起算。 startDate = card.EndDate if startDate.Before(now) { startDate = now } } newEnd := startDate.AddDate(0, 0, cardDays(cardType)) card.EndDate = newEnd if hasEffectiveDate { card.StartDate = startDate } card.Fee += fee if err := tx.Save(&card).Error; err != nil { return fmt.Errorf("更新月卡失败: %w", err) } if err := tx.Model(&dao.Shortlist{}). Where("vehicle_id = ? AND list_type = ?", card.VehicleID, "白名单"). Update("expiration_time", newEnd).Error; err != nil { return fmt.Errorf("更新白名单失败: %w", err) } payRecord := dao.PaymentRecord{ RecordID: 0, PaymentEntry: paymentSvc.EntryCounter, PaymentMethod: paymentSvc.PaymentCash, Amount: fee, PaidAmount: fee, ChangeAmount: 0, OperatorID: operatorID, PaidAt: now, Remark: fmt.Sprintf("包月卡续费(%s)", cardType), } if err := tx.Create(&payRecord).Error; err != nil { return fmt.Errorf("创建支付流水失败: %w", err) } return nil }) } // Refund 退卡:标记退款状态与移除白名单在同一事务中,任一步失败整体回滚。 func normalizeDate(value time.Time) time.Time { local := value.In(time.Local) return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, time.Local) } func (s *MonthlyCardService) Refund(cardID uint) error { return global.GVA_DB.Transaction(func(tx *gorm.DB) error { var card dao.MonthlyCard if err := tx.First(&card, cardID).Error; err != nil { return errors.New("月卡记录不存在") } card.PaymentStatus = "refunded" if err := tx.Save(&card).Error; err != nil { return fmt.Errorf("更新月卡状态失败: %w", err) } if err := tx.Where("vehicle_id = ? AND list_type = ?", card.VehicleID, "白名单"). Unscoped().Delete(&dao.Shortlist{}).Error; err != nil { return fmt.Errorf("移除白名单失败: %w", err) } return nil }) } func (s *MonthlyCardService) List(q repository.MonthlyCardQuery) ([]repository.MonthlyCardResult, int64, error) { return s.repo.List(q) }