vehicle.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. package vehicle
  2. import (
  3. "errors"
  4. "fmt"
  5. "time"
  6. "wails-app/internal/dao"
  7. "wails-app/internal/global"
  8. "wails-app/internal/model/vehicle/request"
  9. vehicleResponse "wails-app/internal/model/vehicle/response"
  10. paymentService "wails-app/internal/modules/payment/service"
  11. "gorm.io/gorm"
  12. )
  13. type VehicleService struct{}
  14. // buildPlateOrRfidQuery 按车牌或RFID查询,RFID为空时仅用车牌(避免 rfid_tag='' 匹配所有无标签车辆)
  15. func buildPlateOrRfidQuery(db *gorm.DB, plateNumber, rfidTag string) *gorm.DB {
  16. if rfidTag != "" {
  17. return db.Where("plate_number = ? OR rfid_tag = ?", plateNumber, rfidTag)
  18. }
  19. return db.Where("plate_number = ?", plateNumber)
  20. }
  21. // CreateVehicle 创建车辆信息
  22. func (s *VehicleService) CreateVehicle(req request.VehicleCreate) error {
  23. // 检查车牌号是否已存在
  24. var existingVehicle dao.Vehicle
  25. result := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).First(&existingVehicle)
  26. if result.RowsAffected > 0 {
  27. return errors.New("车牌号已存在")
  28. }
  29. // 检查车辆类型是否存在
  30. var vehicleType dao.VehicleType
  31. result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
  32. if result.RowsAffected == 0 {
  33. return errors.New("车辆类型不存在")
  34. }
  35. // 处理临时车辆的情况
  36. var ownerId *uint
  37. if req.OwnerId > 0 {
  38. ownerId = &req.OwnerId
  39. }
  40. vehicle := dao.Vehicle{
  41. PlateNumber: req.PlateNumber,
  42. RFIDTag: req.RFIDTag,
  43. VehicleTypeID: req.VehicleTypeID,
  44. VehicleBrand: req.VehicleBrand,
  45. VehicleColor: req.VehicleColor,
  46. OwnerId: ownerId,
  47. }
  48. return global.GVA_DB.Create(&vehicle).Error
  49. }
  50. // UpdateVehicle 更新车辆信息
  51. func (s *VehicleService) UpdateVehicle(req request.VehicleUpdate) error {
  52. var vehicle dao.Vehicle
  53. result := global.GVA_DB.First(&vehicle, req.ID)
  54. if result.RowsAffected == 0 {
  55. return errors.New("车辆不存在")
  56. }
  57. // 检查车牌号是否被其他车辆使用
  58. var existingVehicle dao.Vehicle
  59. result = buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("id != ?", req.ID).First(&existingVehicle)
  60. fmt.Println("result", result.RowsAffected)
  61. if result.RowsAffected > 0 {
  62. return errors.New("车牌号已被其他车辆使用")
  63. }
  64. // 检查车辆类型是否存在
  65. var vehicleType dao.VehicleType
  66. result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
  67. if result.RowsAffected == 0 {
  68. return errors.New("车辆类型不存在")
  69. }
  70. // 处理临时车辆的情况
  71. var ownerId *uint
  72. if req.OwnerId > 0 {
  73. ownerId = &req.OwnerId
  74. } else {
  75. ownerId = nil
  76. }
  77. err := global.GVA_DB.Model(&vehicle).Updates(map[string]interface{}{
  78. "plate_number": req.PlateNumber,
  79. "rfid_tag": req.RFIDTag,
  80. "vehicle_type_id": req.VehicleTypeID,
  81. "vehicle_brand": req.VehicleBrand,
  82. "vehicle_color": req.VehicleColor,
  83. "owner_id": ownerId,
  84. }).Error
  85. return err
  86. }
  87. // GetVehicleByID 根据ID获取车辆信息
  88. func (s *VehicleService) GetVehicleByID(id uint) (dao.Vehicle, error) {
  89. var vehicle dao.Vehicle
  90. result := global.GVA_DB.First(&vehicle, id)
  91. if result.RowsAffected == 0 {
  92. return dao.Vehicle{}, errors.New("车辆不存在")
  93. }
  94. return vehicle, nil
  95. }
  96. // GetVehicleByPlateNumber 根据车牌号或RFID获取车辆信息
  97. func (s *VehicleService) GetVehicleByPlateNumber(plateNumber, RFIDTag string) (dao.Vehicle, error) {
  98. var vehicle dao.Vehicle
  99. query := global.GVA_DB.Preload("VehicleType")
  100. if RFIDTag != "" {
  101. query = query.Where("plate_number = ? OR rfid_tag = ?", plateNumber, RFIDTag)
  102. } else {
  103. query = query.Where("plate_number = ?", plateNumber)
  104. }
  105. result := query.First(&vehicle)
  106. if result.RowsAffected == 0 {
  107. return dao.Vehicle{}, errors.New("车辆不存在")
  108. }
  109. return vehicle, nil
  110. }
  111. func (s *VehicleService) QueryAllVehicles() ([]dao.Vehicle, error) {
  112. var vehicles []dao.Vehicle
  113. err := global.GVA_DB.Find(&vehicles).Error
  114. if err != nil {
  115. return vehicles, err
  116. }
  117. return vehicles, nil
  118. }
  119. // ListVehicles 获取车辆列表
  120. func (s *VehicleService) ListVehicles(req request.VehicleQuery) (int64, []dao.Vehicle, error) {
  121. var vehicles []dao.Vehicle
  122. var total int64
  123. query := global.GVA_DB.Model(&dao.Vehicle{})
  124. if req.PlateNumber != "" {
  125. query = query.Where("plate_number LIKE ?", "%"+req.PlateNumber+"%")
  126. }
  127. if req.VehicleTypeID > 0 {
  128. query = query.Where("vehicle_type_id = ?", req.VehicleTypeID)
  129. }
  130. if req.OwnerId > 0 {
  131. query = query.Where("owner_id = ?", req.OwnerId)
  132. }
  133. // 计算总数
  134. query.Count(&total)
  135. // 分页查询
  136. offset := (req.Page - 1) * req.PageSize
  137. result := query.Offset(offset).Limit(req.PageSize).Preload("Owner").Preload("VehicleType").Find(&vehicles)
  138. if result.Error != nil {
  139. return 0, nil, result.Error
  140. }
  141. return total, vehicles, nil
  142. }
  143. // DeleteVehicle 删除车辆
  144. func (s *VehicleService) DeleteVehicle(id uint) error {
  145. result := global.GVA_DB.Delete(&dao.Vehicle{}, id)
  146. if result.RowsAffected == 0 {
  147. return errors.New("车辆不存在")
  148. }
  149. return result.Error
  150. }
  151. // VehicleEntry 车辆入场
  152. func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.VehicleEntryResponse, error) {
  153. // 黑名单检查
  154. var shortlistSvc ShortlistService
  155. if isBlack, _ := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag); isBlack {
  156. return request.VehicleEntryResponse{}, errors.New("车辆在黑名单中,禁止入场")
  157. }
  158. // 检查车辆是否存在
  159. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  160. if err != nil {
  161. // 如果车辆不存在,创建新车辆,自动归类为"临时车"
  162. var tempType dao.VehicleType
  163. if global.GVA_DB.Where("is_system = ?", true).First(&tempType).Error != nil {
  164. return request.VehicleEntryResponse{}, errors.New("系统错误:临时车类型未配置")
  165. }
  166. createReq := request.VehicleCreate{
  167. PlateNumber: req.PlateNumber,
  168. VehicleTypeID: tempType.ID,
  169. OwnerId: 0,
  170. }
  171. err = s.CreateVehicle(createReq)
  172. if err != nil {
  173. return request.VehicleEntryResponse{}, err
  174. }
  175. vehicle, _ = s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  176. }
  177. // 检查是否有未出场的记录
  178. var existingRecord dao.VehicleRecord
  179. result := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&existingRecord)
  180. if result.RowsAffected > 0 {
  181. return request.VehicleEntryResponse{}, errors.New("车辆已入场,未出场")
  182. }
  183. // 创建入场记录
  184. entryTime := time.Now()
  185. record := dao.VehicleRecord{
  186. PlateNumber: req.PlateNumber,
  187. RFIDTag: req.RFIDTag,
  188. EntryTime: entryTime,
  189. ParkingLotID: req.ParkingLotID,
  190. ParkingSpaceID: req.ParkingSpaceID,
  191. EntryImage: req.EntryImage,
  192. PaymentStatus: "unpaid",
  193. }
  194. err = global.GVA_DB.Create(&record).Error
  195. if err != nil {
  196. return request.VehicleEntryResponse{}, err
  197. }
  198. // 更新车辆最近入场时间
  199. vehicle.LastEntryTime = &entryTime
  200. global.GVA_DB.Save(&vehicle)
  201. response := request.VehicleEntryResponse{
  202. Success: true,
  203. Message: "车辆入场成功",
  204. }
  205. response.Data.PlateNumber = req.PlateNumber
  206. response.Data.EntryTime = entryTime.Unix()
  207. response.Data.ParkingLotID = req.ParkingLotID
  208. response.Data.ParkingSpaceID = req.ParkingSpaceID
  209. return response, nil
  210. }
  211. // VehicleExit 车辆出场
  212. func (s *VehicleService) VehicleExit(req request.VehicleExit) (request.VehicleExitResponse, error) {
  213. // 1. 查询未出场记录
  214. var record dao.VehicleRecord
  215. if err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error; err != nil {
  216. return request.VehicleExitResponse{}, errors.New("车辆未入场或已出场")
  217. }
  218. // 2. 获取车辆信息
  219. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  220. if err != nil {
  221. return request.VehicleExitResponse{}, err
  222. }
  223. // 3. 计算停留时长 & 费用
  224. exitTime := time.Now()
  225. stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
  226. if stayTime < 0 {
  227. stayTime = 0
  228. }
  229. fee := s.calculateFee(&vehicle, stayTime)
  230. // 4. 更新出场记录
  231. record.ExitTime = &exitTime
  232. record.StayTime = stayTime
  233. record.Fee = fee
  234. record.ExitImage = req.ExitImage
  235. record.PaymentStatus = "paid"
  236. if err := global.GVA_DB.Save(&record).Error; err != nil {
  237. return request.VehicleExitResponse{}, err
  238. }
  239. // 5. 更新车辆统计信息
  240. vehicle.LastExitTime = &exitTime
  241. vehicle.TotalStayTime += stayTime
  242. vehicle.TotalFee += fee
  243. global.GVA_DB.Save(&vehicle)
  244. // 6. 完全匹配你的匿名结构体返回(正确写法)
  245. var response request.VehicleExitResponse
  246. response.Success = true
  247. response.Message = "车辆出场成功"
  248. response.Data.PlateNumber = record.PlateNumber
  249. response.Data.EntryTime = record.EntryTime.Unix()
  250. response.Data.ExitTime = exitTime.Unix()
  251. response.Data.StayTime = stayTime
  252. response.Data.Fee = fee
  253. response.Data.PaymentStatus = record.PaymentStatus
  254. return response, nil
  255. }
  256. // ExitPreview 费用预览(不修改数据库,仅查询+计算费用)
  257. func (s *VehicleService) ExitPreview(req request.VehicleEntry) (vehicleResponse.ExitPreviewResponse, error) {
  258. var record dao.VehicleRecord
  259. err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error
  260. if err != nil {
  261. return vehicleResponse.ExitPreviewResponse{}, errors.New("车辆未入场或已出场")
  262. }
  263. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  264. if err != nil {
  265. return vehicleResponse.ExitPreviewResponse{}, err
  266. }
  267. now := time.Now()
  268. stayTime := int64(now.Sub(record.EntryTime).Minutes())
  269. if stayTime < 0 {
  270. stayTime = 0
  271. }
  272. fee := s.calculateFee(&vehicle, stayTime)
  273. typeName := ""
  274. isTemp := false
  275. if vehicle.VehicleType != nil {
  276. typeName = vehicle.VehicleType.Name
  277. isTemp = vehicle.VehicleType.IsSystem
  278. }
  279. return vehicleResponse.ExitPreviewResponse{
  280. PlateNumber: vehicle.PlateNumber,
  281. EntryTime: record.EntryTime.Unix(),
  282. StayTime: stayTime,
  283. Fee: fee,
  284. VehicleType: typeName,
  285. IsTemp: isTemp,
  286. }, nil
  287. }
  288. // ExitConfirm 确认出场 + 支付(更新车辆记录 + 创建支付记录)
  289. func (s *VehicleService) ExitConfirm(req request.ExitConfirmRequest, operatorID uint) (vehicleResponse.ExitConfirmResponse, error) {
  290. // 黑名单检查
  291. var shortlistSvc ShortlistService
  292. if isBlack, _ := shortlistSvc.CheckVehicleShortlist(req.PlateNumber, req.RFIDTag); isBlack {
  293. return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆在黑名单中,禁止出场")
  294. }
  295. var record dao.VehicleRecord
  296. err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error
  297. if err != nil {
  298. return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆未入场或已出场")
  299. }
  300. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  301. if err != nil {
  302. return vehicleResponse.ExitConfirmResponse{}, err
  303. }
  304. exitTime := time.Now()
  305. stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
  306. if stayTime < 0 {
  307. stayTime = 0
  308. }
  309. fee := s.calculateFee(&vehicle, stayTime)
  310. record.ExitTime = &exitTime
  311. record.StayTime = stayTime
  312. record.Fee = fee
  313. record.PaymentMethod = req.PaymentMethod
  314. global.GVA_DB.Save(&record)
  315. vehicle.LastExitTime = &exitTime
  316. vehicle.TotalStayTime += stayTime
  317. vehicle.TotalFee += fee
  318. global.GVA_DB.Save(&vehicle)
  319. paymentSvc := paymentService.NewPaymentService()
  320. pr, err := paymentSvc.ProcessPayment(record.ID, req.PaymentMethod, fee, req.PaidAmount, operatorID)
  321. if err != nil {
  322. return vehicleResponse.ExitConfirmResponse{}, err
  323. }
  324. changeAmount := 0.0
  325. if pr != nil {
  326. changeAmount = pr.ChangeAmount
  327. }
  328. return vehicleResponse.ExitConfirmResponse{
  329. PlateNumber: vehicle.PlateNumber,
  330. EntryTime: record.EntryTime.Unix(),
  331. ExitTime: exitTime.Unix(),
  332. StayTime: stayTime,
  333. Fee: fee,
  334. ChangeAmount: changeAmount,
  335. PaymentMethod: req.PaymentMethod,
  336. }, nil
  337. }
  338. // calculateFee 计算停车费用
  339. func (s *VehicleService) calculateFee(vehicle *dao.Vehicle, stayTime int64) float64 {
  340. // 1. 根据车辆类型ID获取收费配置(唯一)
  341. var feeConfig dao.FeeConfig
  342. if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil {
  343. // 无配置 → 默认计费:0.1元/分钟
  344. return float64(stayTime) * 0.1
  345. }
  346. // 配置存在但全为 0 → 同样回退默认费率
  347. if feeConfig.UnitFee == 0 && feeConfig.StartFee == 0 && feeConfig.DailyMaxFee == 0 {
  348. return float64(stayTime) * 0.1
  349. }
  350. // 2. 判断VIP有效性(必须IsVip=true、过期时间未到、且过期时间非零值)
  351. isVIP := false
  352. if vehicle.OwnerId != nil {
  353. var owner dao.Owner
  354. if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil {
  355. now := time.Now()
  356. isVIP = owner.IsVip && owner.VipExpireTime.After(now) && !owner.VipExpireTime.IsZero()
  357. }
  358. }
  359. // 3. VIP 免费
  360. if isVIP && feeConfig.IsVIPFree {
  361. return 0
  362. }
  363. // ===================== 修复这里 =====================
  364. // 4. 在免费时长内 → 直接 0 元
  365. if stayTime <= int64(feeConfig.StartTime) {
  366. return 0
  367. }
  368. // 5. 超过免费时长 → 开始计费
  369. extraTime := stayTime - int64(feeConfig.StartTime)
  370. extraUnits := extraTime / int64(feeConfig.UnitTime)
  371. if extraTime%int64(feeConfig.UnitTime) > 0 {
  372. extraUnits++
  373. }
  374. // 起步费 + 超时费用
  375. fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee
  376. // 6. 每日封顶
  377. if feeConfig.DailyMaxFee > 0 && fee > feeConfig.DailyMaxFee {
  378. fee = feeConfig.DailyMaxFee
  379. }
  380. // 7. VIP 折扣
  381. if isVIP {
  382. fee *= feeConfig.VIPDiscount
  383. }
  384. return fee
  385. }
  386. // InParkVehicle 在场车辆信息
  387. type InParkVehicle struct {
  388. RecordID uint `json:"record_id"`
  389. PlateNumber string `json:"plate_number"`
  390. VehicleType string `json:"vehicle_type"`
  391. OwnerName string `json:"owner_name"`
  392. OwnerPhone string `json:"owner_phone"`
  393. EntryTime string `json:"entry_time"`
  394. StayMinutes int64 `json:"stay_minutes"`
  395. ParkingLot string `json:"parking_lot"`
  396. ParkingSpace string `json:"parking_space"`
  397. }
  398. // GetInParkVehicles 获取所有在场车辆
  399. func (s *VehicleService) GetInParkVehicles(lotID uint, plateNumber string) ([]InParkVehicle, error) {
  400. var results []InParkVehicle
  401. now := time.Now()
  402. query := global.GVA_DB.Table("vehicle_record").
  403. Select(`vehicle_record.id as record_id, vehicle_record.plate_number, vehicle_type.name as vehicle_type,
  404. owner.owner_name, owner.owner_phone, vehicle_record.entry_time,
  405. parking_lot.lot_name as parking_lot`).
  406. Joins("LEFT JOIN vehicle ON vehicle.plate_number = vehicle_record.plate_number").
  407. Joins("LEFT JOIN vehicle_type ON vehicle_type.id = vehicle.vehicle_type_id").
  408. Joins("LEFT JOIN owner ON owner.id = vehicle.owner_id").
  409. Joins("LEFT JOIN parking_lot ON parking_lot.id = vehicle_record.parking_lot_id").
  410. Where("vehicle_record.exit_time IS NULL")
  411. if lotID > 0 {
  412. query = query.Where("vehicle_record.parking_lot_id = ?", lotID)
  413. }
  414. if plateNumber != "" {
  415. query = query.Where("vehicle_record.plate_number LIKE ?", "%"+plateNumber+"%")
  416. }
  417. err := query.Order("vehicle_record.entry_time ASC").Scan(&results).Error
  418. if err != nil {
  419. return nil, err
  420. }
  421. for i := range results {
  422. // Parse entry time and calculate stay duration
  423. t, parseErr := time.Parse("2006-01-02T15:04:05-07:00", results[i].EntryTime)
  424. if parseErr != nil {
  425. t, _ = time.Parse("2006-01-02 15:04:05", results[i].EntryTime)
  426. }
  427. results[i].StayMinutes = int64(now.Sub(t).Minutes())
  428. if results[i].StayMinutes < 0 {
  429. results[i].StayMinutes = 0
  430. }
  431. }
  432. return results, nil
  433. }