| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- 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"
- ticketService "wails-app/internal/modules/digital-ticket/service"
- 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("车辆已入场,未出场")
- }
- // Create the vehicle record and its digital ticket atomically. A vehicle
- // entry without a ticket would make every later payment flow ambiguous.
- entryTime := time.Now()
- plateNumber := vehicle.PlateNumber
- if plateNumber == "" {
- plateNumber = req.PlateNumber
- }
- record := dao.VehicleRecord{
- PlateNumber: plateNumber,
- RFIDTag: req.RFIDTag,
- EntryTime: entryTime,
- ParkingLotID: req.ParkingLotID,
- ParkingSpaceID: req.ParkingSpaceID,
- EntryImage: req.EntryImage,
- PaymentStatus: "unpaid",
- }
- ticketSvc := ticketService.NewTicketService()
- triggerMode := "manual"
- if req.RFIDTag != "" {
- triggerMode = "rfid"
- }
- err = global.GVA_DB.Transaction(func(tx *gorm.DB) error {
- var activeRecord dao.VehicleRecord
- if result := buildPlateOrRfidQuery(tx, req.PlateNumber, req.RFIDTag).
- Where("exit_time IS NULL").First(&activeRecord); result.RowsAffected > 0 {
- return errors.New("车辆已入场,未出场")
- }
- if err := tx.Create(&record).Error; err != nil {
- return err
- }
- if _, err := ticketSvc.CreateTx(tx, plateNumber, triggerMode, record.ID); err != nil {
- return fmt.Errorf("创建数字票失败: %w", err)
- }
- return tx.Model(&dao.Vehicle{}).Where("id = ?", vehicle.ID).
- Update("last_entry_time", entryTime).Error
- })
- if err != nil {
- return request.VehicleEntryResponse{}, err
- }
- response := request.VehicleEntryResponse{
- Success: true,
- Message: "车辆入场成功",
- }
- response.Data.PlateNumber = 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)
- var shortlistSvc ShortlistService
- _, isWhite := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag)
- if isWhite {
- fee = 0
- }
- 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
- isBlack, isWhite := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag)
- if isBlack {
- return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆在黑名单中,禁止出场")
- }
- paymentEntry := req.PaymentEntry
- if paymentEntry == "" {
- paymentEntry = paymentService.EntryCounter
- }
- var result vehicleResponse.ExitConfirmResponse
- err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
- var record dao.VehicleRecord
- if err := buildPlateOrRfidQuery(tx, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error; err != nil {
- return errors.New("车辆未入场或已出场")
- }
- var vehicle dao.Vehicle
- query := tx.Preload("VehicleType")
- query = buildPlateOrRfidQuery(query, req.PlateNumber, req.RFIDTag)
- if err := query.First(&vehicle).Error; err != nil {
- return errors.New("车辆不存在")
- }
- exitTime := time.Now()
- stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
- if stayTime < 0 {
- stayTime = 0
- }
- fee := s.calculateFee(&vehicle, stayTime)
- if isWhite {
- fee = 0
- }
- isTemp := vehicle.VehicleType != nil && vehicle.VehicleType.IsSystem
- if isTemp && !isWhite && req.PaymentMethod == paymentService.PaymentFree {
- return errors.New("临时车辆不能使用免密放行")
- }
- paymentSvc := paymentService.NewPaymentService()
- if _, _, err := paymentSvc.ValidatePayment(tx, req.PaymentMethod, paymentEntry, fee, req.PaidAmount); err != nil {
- return err
- }
- record.ExitTime = &exitTime
- record.StayTime = stayTime
- record.Fee = fee
- record.PaymentEntry = paymentEntry
- record.PaymentMethod = req.PaymentMethod
- if err := tx.Save(&record).Error; err != nil {
- return err
- }
- vehicle.LastExitTime = &exitTime
- vehicle.TotalStayTime += stayTime
- vehicle.TotalFee += fee
- if err := tx.Save(&vehicle).Error; err != nil {
- return err
- }
- pr, err := paymentSvc.ProcessPaymentTxAtEntry(tx, record.ID, paymentEntry, req.PaymentMethod, fee, req.PaidAmount, operatorID)
- if err != nil {
- return err
- }
- ticketSvc := ticketService.NewTicketService()
- if err := ticketSvc.MarkPaidAndExitedTx(tx, record.ID, paymentEntry, req.PaymentMethod, "", pr.PaidAmount, fee); err != nil {
- return err
- }
- result = vehicleResponse.ExitConfirmResponse{
- PlateNumber: vehicle.PlateNumber, EntryTime: record.EntryTime.Unix(), ExitTime: exitTime.Unix(),
- StayTime: stayTime, Fee: fee, ChangeAmount: pr.ChangeAmount, PaymentEntry: paymentEntry, PaymentMethod: req.PaymentMethod,
- }
- return nil
- })
- if err != nil {
- return vehicleResponse.ExitConfirmResponse{}, err
- }
- return result, nil
- }
- // calculateFee 计算停车费用
- func (s *VehicleService) calculateFee(vehicle *dao.Vehicle, stayTime int64) float64 {
- return ticketService.CalculateFee(vehicle, stayTime)
- }
- // 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
- }
|