Explorar o código

feat: 包月车管理后端——DAO/repo/service,办卡=白名单+支付记录

lq hai 1 mes
pai
achega
01b2b626cc

+ 21 - 0
internal/dao/monthly_card.go

@@ -0,0 +1,21 @@
+package dao
+
+import (
+	"time"
+	"wails-app/internal/global"
+)
+
+type MonthlyCard struct {
+	global.GVA_MODEL
+	VehicleID     uint      `gorm:"index" json:"vehicle_id"`
+	Vehicle       *Vehicle  `gorm:"foreignKey:VehicleID" json:"vehicle"`
+	CardType      string    `gorm:"size:20" json:"card_type"`
+	StartDate     time.Time `json:"start_date"`
+	EndDate       time.Time `json:"end_date"`
+	Fee           float64   `json:"fee"`
+	PaymentStatus string    `gorm:"size:20;default:paid" json:"payment_status"`
+	OperatorID    uint      `json:"operator_id"`
+	Remark        string    `gorm:"size:200" json:"remark"`
+}
+
+func (MonthlyCard) TableName() string { return "monthly_card" }

+ 1 - 0
internal/initialize/gorm.go

@@ -59,6 +59,7 @@ func RegisterTables() {
 		dao.UHFReader{},
 		dao.Camera{},
 		dao.Shortlist{},
+		dao.MonthlyCard{},
 	)
 	if err != nil {
 		global.GVA_LOG.Error("register table failed", zap.Error(err))

+ 70 - 0
internal/modules/monthly/repository/repo.go

@@ -0,0 +1,70 @@
+package repository
+
+import (
+	"wails-app/internal/dao"
+	"wails-app/internal/global"
+)
+
+type MonthlyCardRepository struct{}
+
+func (r *MonthlyCardRepository) Create(card *dao.MonthlyCard) error {
+	return global.GVA_DB.Create(card).Error
+}
+
+func (r *MonthlyCardRepository) GetByVehicleID(vehicleID uint) (*dao.MonthlyCard, error) {
+	var card dao.MonthlyCard
+	err := global.GVA_DB.Where("vehicle_id = ? AND payment_status = ?", vehicleID, "paid").Preload("Vehicle").First(&card).Error
+	if err != nil {
+		return nil, err
+	}
+	return &card, nil
+}
+
+func (r *MonthlyCardRepository) Update(card *dao.MonthlyCard) error {
+	return global.GVA_DB.Save(card).Error
+}
+
+type MonthlyCardQuery struct {
+	PlateNumber string `form:"plate_number"`
+	CardType    string `form:"card_type"`
+	Page        int    `form:"page"`
+	PageSize    int    `form:"page_size"`
+}
+
+type MonthlyCardResult struct {
+	ID            uint    `json:"id"`
+	PlateNumber   string  `json:"plate_number"`
+	CardType      string  `json:"card_type"`
+	StartDate     string  `json:"start_date"`
+	EndDate       string  `json:"end_date"`
+	Fee           float64 `json:"fee"`
+	PaymentStatus string  `json:"payment_status"`
+}
+
+func (r *MonthlyCardRepository) List(q MonthlyCardQuery) ([]MonthlyCardResult, int64, error) {
+	db := global.GVA_DB.Table("monthly_card").
+		Select("monthly_card.id, vehicle.plate_number, monthly_card.card_type, monthly_card.start_date, monthly_card.end_date, monthly_card.fee, monthly_card.payment_status").
+		Joins("LEFT JOIN vehicle ON vehicle.id = monthly_card.vehicle_id")
+
+	if q.PlateNumber != "" {
+		db = db.Where("vehicle.plate_number LIKE ?", "%"+q.PlateNumber+"%")
+	}
+	if q.CardType != "" {
+		db = db.Where("monthly_card.card_type = ?", q.CardType)
+	}
+
+	var total int64
+	db.Count(&total)
+
+	if q.Page <= 0 {
+		q.Page = 1
+	}
+	if q.PageSize <= 0 {
+		q.PageSize = 10
+	}
+	offset := (q.Page - 1) * q.PageSize
+
+	var results []MonthlyCardResult
+	err := db.Order("monthly_card.id DESC").Offset(offset).Limit(q.PageSize).Scan(&results).Error
+	return results, total, err
+}

+ 128 - 0
internal/modules/monthly/service/service.go

@@ -0,0 +1,128 @@
+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"
+)
+
+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 办理月卡:创建 monthly_card + shortlist 白名单 + payment_record
+func (s *MonthlyCardService) Create(vehicleID uint, cardType string, fee float64, operatorID uint, remark string) (*dao.MonthlyCard, error) {
+	existing, _ := s.repo.GetByVehicleID(vehicleID)
+	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()
+	endDate := now.AddDate(0, 0, cardDays(cardType))
+
+	card := &dao.MonthlyCard{
+		VehicleID: vehicleID, CardType: cardType,
+		StartDate: now, EndDate: endDate,
+		Fee: fee, PaymentStatus: "paid", OperatorID: operatorID, Remark: remark,
+	}
+	if err := s.repo.Create(card); err != nil {
+		return nil, fmt.Errorf("创建月卡失败: %w", err)
+	}
+
+	// 写入白名单
+	shortlist := dao.Shortlist{
+		VehicleId:      int(vehicleID),
+		ListType:       "白名单",
+		ExpirationTime: &endDate,
+	}
+	global.GVA_DB.Create(&shortlist)
+
+	// 创建 payment_record
+	payRecord := dao.PaymentRecord{
+		RecordID: 0, PaymentMethod: paymentSvc.PaymentCash,
+		Amount: fee, PaidAmount: fee, ChangeAmount: 0,
+		OperatorID: operatorID, PaidAt: now,
+		Remark: fmt.Sprintf("包月卡办理(%s)", cardType),
+	}
+	global.GVA_DB.Create(&payRecord)
+
+	return card, nil
+}
+
+// Renew 续费:延长到期日 + 更新 shortlist
+func (s *MonthlyCardService) Renew(cardID uint, cardType string, fee float64, operatorID uint) error {
+	var card dao.MonthlyCard
+	if err := global.GVA_DB.First(&card, cardID).Error; err != nil {
+		return errors.New("月卡记录不存在")
+	}
+	if card.PaymentStatus != "paid" {
+		return errors.New("月卡已退卡,无法续费")
+	}
+
+	newEnd := card.EndDate.AddDate(0, 0, cardDays(cardType))
+	card.EndDate = newEnd
+	card.Fee += fee
+	global.GVA_DB.Save(&card)
+
+	// 更新 shortlist 到期日
+	global.GVA_DB.Model(&dao.Shortlist{}).Where("vehicle_id = ? AND list_type = ?", card.VehicleID, "白名单").
+		Update("expiration_time", newEnd)
+
+	// 创建 payment_record
+	now := time.Now()
+	payRecord := dao.PaymentRecord{
+		RecordID: 0, PaymentMethod: paymentSvc.PaymentCash,
+		Amount: fee, PaidAmount: fee, ChangeAmount: 0,
+		OperatorID: operatorID, PaidAt: now,
+		Remark: fmt.Sprintf("包月卡续费(%s)", cardType),
+	}
+	global.GVA_DB.Create(&payRecord)
+
+	return nil
+}
+
+// Refund 退卡:标记 refunded + 移除 shortlist
+func (s *MonthlyCardService) Refund(cardID uint) error {
+	var card dao.MonthlyCard
+	if err := global.GVA_DB.First(&card, cardID).Error; err != nil {
+		return errors.New("月卡记录不存在")
+	}
+
+	card.PaymentStatus = "refunded"
+	global.GVA_DB.Save(&card)
+
+	// 移除白名单
+	global.GVA_DB.Where("vehicle_id = ? AND list_type = ?", card.VehicleID, "白名单").Unscoped().Delete(&dao.Shortlist{})
+
+	return nil
+}
+
+func (s *MonthlyCardService) List(q repository.MonthlyCardQuery) ([]repository.MonthlyCardResult, int64, error) {
+	return s.repo.List(q)
+}