package vehicle import ( "errors" "fmt" "time" "wails-app/internal/dao" "wails-app/internal/global" "wails-app/internal/model/vehicle/request" vehicleResponse "wails-app/internal/model/vehicle/response" paymentService "wails-app/internal/modules/payment/service" "gorm.io/gorm" ) type VehicleService struct{} // buildPlateOrRfidQuery 按车牌或RFID查询,RFID为空时仅用车牌(避免 rfid_tag='' 匹配所有无标签车辆) func buildPlateOrRfidQuery(db *gorm.DB, plateNumber, rfidTag string) *gorm.DB { if rfidTag != "" { return db.Where("plate_number = ? OR rfid_tag = ?", plateNumber, rfidTag) } return db.Where("plate_number = ?", plateNumber) } // CreateVehicle 创建车辆信息 func (s *VehicleService) CreateVehicle(req request.VehicleCreate) error { // 检查车牌号是否已存在 var existingVehicle dao.Vehicle result := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).First(&existingVehicle) if result.RowsAffected > 0 { return errors.New("车牌号已存在") } // 检查车辆类型是否存在 var vehicleType dao.VehicleType result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID) if result.RowsAffected == 0 { return errors.New("车辆类型不存在") } // 处理临时车辆的情况 var ownerId *uint if req.OwnerId > 0 { ownerId = &req.OwnerId } vehicle := dao.Vehicle{ PlateNumber: req.PlateNumber, RFIDTag: req.RFIDTag, VehicleTypeID: req.VehicleTypeID, VehicleBrand: req.VehicleBrand, VehicleColor: req.VehicleColor, OwnerId: ownerId, } return global.GVA_DB.Create(&vehicle).Error } // UpdateVehicle 更新车辆信息 func (s *VehicleService) UpdateVehicle(req request.VehicleUpdate) error { var vehicle dao.Vehicle result := global.GVA_DB.First(&vehicle, req.ID) if result.RowsAffected == 0 { return errors.New("车辆不存在") } // 检查车牌号是否被其他车辆使用 var existingVehicle dao.Vehicle result = buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("id != ?", req.ID).First(&existingVehicle) fmt.Println("result", result.RowsAffected) if result.RowsAffected > 0 { return errors.New("车牌号已被其他车辆使用") } // 检查车辆类型是否存在 var vehicleType dao.VehicleType result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID) if result.RowsAffected == 0 { return errors.New("车辆类型不存在") } // 处理临时车辆的情况 var ownerId *uint if req.OwnerId > 0 { ownerId = &req.OwnerId } else { ownerId = nil } err := global.GVA_DB.Model(&vehicle).Updates(map[string]interface{}{ "plate_number": req.PlateNumber, "rfid_tag": req.RFIDTag, "vehicle_type_id": req.VehicleTypeID, "vehicle_brand": req.VehicleBrand, "vehicle_color": req.VehicleColor, "owner_id": ownerId, }).Error return err } // GetVehicleByID 根据ID获取车辆信息 func (s *VehicleService) GetVehicleByID(id uint) (dao.Vehicle, error) { var vehicle dao.Vehicle result := global.GVA_DB.First(&vehicle, id) if result.RowsAffected == 0 { return dao.Vehicle{}, errors.New("车辆不存在") } return vehicle, nil } // GetVehicleByPlateNumber 根据车牌号或RFID获取车辆信息 func (s *VehicleService) GetVehicleByPlateNumber(plateNumber, RFIDTag string) (dao.Vehicle, error) { var vehicle dao.Vehicle query := global.GVA_DB.Preload("VehicleType") if RFIDTag != "" { query = query.Where("plate_number = ? OR rfid_tag = ?", plateNumber, RFIDTag) } else { query = query.Where("plate_number = ?", plateNumber) } result := query.First(&vehicle) if result.RowsAffected == 0 { return dao.Vehicle{}, errors.New("车辆不存在") } return vehicle, nil } func (s *VehicleService) QueryAllVehicles() ([]dao.Vehicle, error) { var vehicles []dao.Vehicle err := global.GVA_DB.Find(&vehicles).Error if err != nil { return vehicles, err } return vehicles, nil } // ListVehicles 获取车辆列表 func (s *VehicleService) ListVehicles(req request.VehicleQuery) (int64, []dao.Vehicle, error) { var vehicles []dao.Vehicle var total int64 query := global.GVA_DB.Model(&dao.Vehicle{}) if req.PlateNumber != "" { query = query.Where("plate_number LIKE ?", "%"+req.PlateNumber+"%") } if req.VehicleTypeID > 0 { query = query.Where("vehicle_type_id = ?", req.VehicleTypeID) } if req.OwnerId > 0 { query = query.Where("owner_id = ?", req.OwnerId) } // 计算总数 query.Count(&total) // 分页查询 offset := (req.Page - 1) * req.PageSize result := query.Offset(offset).Limit(req.PageSize).Preload("Owner").Preload("VehicleType").Find(&vehicles) if result.Error != nil { return 0, nil, result.Error } return total, vehicles, nil } // DeleteVehicle 删除车辆 func (s *VehicleService) DeleteVehicle(id uint) error { result := global.GVA_DB.Delete(&dao.Vehicle{}, id) if result.RowsAffected == 0 { return errors.New("车辆不存在") } return result.Error } // VehicleEntry 车辆入场 func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.VehicleEntryResponse, error) { // 黑名单检查 var shortlistSvc ShortlistService if isBlack, _ := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag); isBlack { return request.VehicleEntryResponse{}, errors.New("车辆在黑名单中,禁止入场") } // 检查车辆是否存在 vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag) if err != nil { // 如果车辆不存在,创建新车辆,自动归类为"临时车" var tempType dao.VehicleType if global.GVA_DB.Where("is_system = ?", true).First(&tempType).Error != nil { return request.VehicleEntryResponse{}, errors.New("系统错误:临时车类型未配置") } createReq := request.VehicleCreate{ PlateNumber: req.PlateNumber, VehicleTypeID: tempType.ID, OwnerId: 0, } err = s.CreateVehicle(createReq) if err != nil { return request.VehicleEntryResponse{}, err } vehicle, _ = s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag) } // 检查是否有未出场的记录 var existingRecord dao.VehicleRecord result := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&existingRecord) if result.RowsAffected > 0 { return request.VehicleEntryResponse{}, errors.New("车辆已入场,未出场") } // 创建入场记录 entryTime := time.Now() record := dao.VehicleRecord{ PlateNumber: req.PlateNumber, RFIDTag: req.RFIDTag, EntryTime: entryTime, ParkingLotID: req.ParkingLotID, ParkingSpaceID: req.ParkingSpaceID, EntryImage: req.EntryImage, PaymentStatus: "unpaid", } err = global.GVA_DB.Create(&record).Error if err != nil { return request.VehicleEntryResponse{}, err } // 更新车辆最近入场时间 vehicle.LastEntryTime = &entryTime global.GVA_DB.Save(&vehicle) response := request.VehicleEntryResponse{ Success: true, Message: "车辆入场成功", } response.Data.PlateNumber = req.PlateNumber response.Data.EntryTime = entryTime.Unix() response.Data.ParkingLotID = req.ParkingLotID response.Data.ParkingSpaceID = req.ParkingSpaceID return response, nil } // VehicleExit 车辆出场 func (s *VehicleService) VehicleExit(req request.VehicleExit) (request.VehicleExitResponse, error) { // 1. 查询未出场记录 var record dao.VehicleRecord if err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error; err != nil { return request.VehicleExitResponse{}, errors.New("车辆未入场或已出场") } // 2. 获取车辆信息 vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag) if err != nil { return request.VehicleExitResponse{}, err } // 3. 计算停留时长 & 费用 exitTime := time.Now() stayTime := int64(exitTime.Sub(record.EntryTime).Minutes()) if stayTime < 0 { stayTime = 0 } fee := s.calculateFee(&vehicle, stayTime) // 4. 更新出场记录 record.ExitTime = &exitTime record.StayTime = stayTime record.Fee = fee record.ExitImage = req.ExitImage record.PaymentStatus = "paid" if err := global.GVA_DB.Save(&record).Error; err != nil { return request.VehicleExitResponse{}, err } // 5. 更新车辆统计信息 vehicle.LastExitTime = &exitTime vehicle.TotalStayTime += stayTime vehicle.TotalFee += fee global.GVA_DB.Save(&vehicle) // 6. 完全匹配你的匿名结构体返回(正确写法) var response request.VehicleExitResponse response.Success = true response.Message = "车辆出场成功" response.Data.PlateNumber = record.PlateNumber response.Data.EntryTime = record.EntryTime.Unix() response.Data.ExitTime = exitTime.Unix() response.Data.StayTime = stayTime response.Data.Fee = fee response.Data.PaymentStatus = record.PaymentStatus return response, nil } // ExitPreview 费用预览(不修改数据库,仅查询+计算费用) func (s *VehicleService) ExitPreview(req request.VehicleEntry) (vehicleResponse.ExitPreviewResponse, error) { var record dao.VehicleRecord err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error if err != nil { return vehicleResponse.ExitPreviewResponse{}, errors.New("车辆未入场或已出场") } vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag) if err != nil { return vehicleResponse.ExitPreviewResponse{}, err } now := time.Now() stayTime := int64(now.Sub(record.EntryTime).Minutes()) if stayTime < 0 { stayTime = 0 } fee := s.calculateFee(&vehicle, stayTime) typeName := "" isTemp := false if vehicle.VehicleType != nil { typeName = vehicle.VehicleType.Name isTemp = vehicle.VehicleType.IsSystem } return vehicleResponse.ExitPreviewResponse{ PlateNumber: vehicle.PlateNumber, EntryTime: record.EntryTime.Unix(), StayTime: stayTime, Fee: fee, VehicleType: typeName, IsTemp: isTemp, }, nil } // ExitConfirm 确认出场 + 支付(更新车辆记录 + 创建支付记录) func (s *VehicleService) ExitConfirm(req request.ExitConfirmRequest, operatorID uint) (vehicleResponse.ExitConfirmResponse, error) { // 黑名单检查 var shortlistSvc ShortlistService if isBlack, _ := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag); isBlack { return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆在黑名单中,禁止出场") } var record dao.VehicleRecord err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error if err != nil { return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆未入场或已出场") } vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag) if err != nil { return vehicleResponse.ExitConfirmResponse{}, err } exitTime := time.Now() stayTime := int64(exitTime.Sub(record.EntryTime).Minutes()) if stayTime < 0 { stayTime = 0 } fee := s.calculateFee(&vehicle, stayTime) record.ExitTime = &exitTime record.StayTime = stayTime record.Fee = fee record.PaymentMethod = req.PaymentMethod global.GVA_DB.Save(&record) vehicle.LastExitTime = &exitTime vehicle.TotalStayTime += stayTime vehicle.TotalFee += fee global.GVA_DB.Save(&vehicle) paymentSvc := paymentService.NewPaymentService() pr, err := paymentSvc.ProcessPayment(record.ID, req.PaymentMethod, fee, req.PaidAmount, operatorID) if err != nil { return vehicleResponse.ExitConfirmResponse{}, err } changeAmount := 0.0 if pr != nil { changeAmount = pr.ChangeAmount } return vehicleResponse.ExitConfirmResponse{ PlateNumber: vehicle.PlateNumber, EntryTime: record.EntryTime.Unix(), ExitTime: exitTime.Unix(), StayTime: stayTime, Fee: fee, ChangeAmount: changeAmount, PaymentMethod: req.PaymentMethod, }, nil } // calculateFee 计算停车费用 func (s *VehicleService) calculateFee(vehicle *dao.Vehicle, stayTime int64) float64 { // 1. 根据车辆类型ID获取收费配置(唯一) var feeConfig dao.FeeConfig if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil { // 无配置 → 默认计费:0.1元/分钟 return float64(stayTime) * 0.1 } // 配置存在但全为 0 → 同样回退默认费率 if feeConfig.UnitFee == 0 && feeConfig.StartFee == 0 && feeConfig.DailyMaxFee == 0 { return float64(stayTime) * 0.1 } // 2. 判断VIP有效性(必须IsVip=true、过期时间未到、且过期时间非零值) isVIP := false if vehicle.OwnerId != nil { var owner dao.Owner if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil { now := time.Now() isVIP = owner.IsVip && owner.VipExpireTime.After(now) && !owner.VipExpireTime.IsZero() } } // 3. VIP 免费 if isVIP && feeConfig.IsVIPFree { return 0 } // ===================== 修复这里 ===================== // 4. 在免费时长内 → 直接 0 元 if stayTime <= int64(feeConfig.StartTime) { return 0 } // 5. 超过免费时长 → 开始计费 extraTime := stayTime - int64(feeConfig.StartTime) extraUnits := extraTime / int64(feeConfig.UnitTime) if extraTime%int64(feeConfig.UnitTime) > 0 { extraUnits++ } // 起步费 + 超时费用 fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee // 6. 每日封顶 if feeConfig.DailyMaxFee > 0 && fee > feeConfig.DailyMaxFee { fee = feeConfig.DailyMaxFee } // 7. VIP 折扣 if isVIP { fee *= feeConfig.VIPDiscount } return fee } // InParkVehicle 在场车辆信息 type InParkVehicle struct { RecordID uint `json:"record_id"` PlateNumber string `json:"plate_number"` VehicleType string `json:"vehicle_type"` OwnerName string `json:"owner_name"` OwnerPhone string `json:"owner_phone"` EntryTime string `json:"entry_time"` StayMinutes int64 `json:"stay_minutes"` ParkingLot string `json:"parking_lot"` ParkingSpace string `json:"parking_space"` } // GetInParkVehicles 获取所有在场车辆 func (s *VehicleService) GetInParkVehicles(lotID uint, plateNumber string) ([]InParkVehicle, error) { var results []InParkVehicle now := time.Now() query := global.GVA_DB.Table("vehicle_record"). Select(`vehicle_record.id as record_id, vehicle_record.plate_number, vehicle_type.name as vehicle_type, owner.owner_name, owner.owner_phone, vehicle_record.entry_time, parking_lot.lot_name as parking_lot`). Joins("LEFT JOIN vehicle ON vehicle.plate_number = vehicle_record.plate_number"). Joins("LEFT JOIN vehicle_type ON vehicle_type.id = vehicle.vehicle_type_id"). Joins("LEFT JOIN owner ON owner.id = vehicle.owner_id"). Joins("LEFT JOIN parking_lot ON parking_lot.id = vehicle_record.parking_lot_id"). Where("vehicle_record.exit_time IS NULL") if lotID > 0 { query = query.Where("vehicle_record.parking_lot_id = ?", lotID) } if plateNumber != "" { query = query.Where("vehicle_record.plate_number LIKE ?", "%"+plateNumber+"%") } err := query.Order("vehicle_record.entry_time ASC").Scan(&results).Error if err != nil { return nil, err } for i := range results { // Parse entry time and calculate stay duration t, parseErr := time.Parse("2006-01-02T15:04:05-07:00", results[i].EntryTime) if parseErr != nil { t, _ = time.Parse("2006-01-02 15:04:05", results[i].EntryTime) } results[i].StayMinutes = int64(now.Sub(t).Minutes()) if results[i].StayMinutes < 0 { results[i].StayMinutes = 0 } } return results, nil }