vehicle.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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 := global.GVA_DB.Where("plate_number = ? OR rfid_tag = ?", 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 = global.GVA_DB.Where("(plate_number = ? OR rfid_tag = ?) AND id != ?", req.PlateNumber, req.RFIDTag, 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. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  155. if err != nil {
  156. // 如果车辆不存在,创建新车辆,自动归类为"临时车"
  157. var tempType dao.VehicleType
  158. if global.GVA_DB.Where("is_system = ?", true).First(&tempType).Error != nil {
  159. return request.VehicleEntryResponse{}, errors.New("系统错误:临时车类型未配置")
  160. }
  161. createReq := request.VehicleCreate{
  162. PlateNumber: req.PlateNumber,
  163. VehicleTypeID: tempType.ID,
  164. OwnerId: 0,
  165. }
  166. err = s.CreateVehicle(createReq)
  167. if err != nil {
  168. return request.VehicleEntryResponse{}, err
  169. }
  170. vehicle, _ = s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  171. }
  172. // 检查是否有未出场的记录
  173. var existingRecord dao.VehicleRecord
  174. result := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&existingRecord)
  175. if result.RowsAffected > 0 {
  176. return request.VehicleEntryResponse{}, errors.New("车辆已入场,未出场")
  177. }
  178. // 创建入场记录
  179. entryTime := time.Now()
  180. record := dao.VehicleRecord{
  181. PlateNumber: req.PlateNumber,
  182. RFIDTag: req.RFIDTag,
  183. EntryTime: entryTime,
  184. ParkingLotID: req.ParkingLotID,
  185. ParkingSpaceID: req.ParkingSpaceID,
  186. EntryImage: req.EntryImage,
  187. PaymentStatus: "unpaid",
  188. }
  189. err = global.GVA_DB.Create(&record).Error
  190. if err != nil {
  191. return request.VehicleEntryResponse{}, err
  192. }
  193. // 更新车辆最近入场时间
  194. vehicle.LastEntryTime = &entryTime
  195. global.GVA_DB.Save(&vehicle)
  196. response := request.VehicleEntryResponse{
  197. Success: true,
  198. Message: "车辆入场成功",
  199. }
  200. response.Data.PlateNumber = req.PlateNumber
  201. response.Data.EntryTime = entryTime.Unix()
  202. response.Data.ParkingLotID = req.ParkingLotID
  203. response.Data.ParkingSpaceID = req.ParkingSpaceID
  204. return response, nil
  205. }
  206. // VehicleExit 车辆出场
  207. func (s *VehicleService) VehicleExit(req request.VehicleExit) (request.VehicleExitResponse, error) {
  208. // 1. 查询未出场记录
  209. var record dao.VehicleRecord
  210. if err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error; err != nil {
  211. return request.VehicleExitResponse{}, errors.New("车辆未入场或已出场")
  212. }
  213. // 2. 获取车辆信息
  214. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  215. if err != nil {
  216. return request.VehicleExitResponse{}, err
  217. }
  218. // 3. 计算停留时长 & 费用
  219. exitTime := time.Now()
  220. stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
  221. if stayTime < 0 {
  222. stayTime = 0
  223. }
  224. fee := s.calculateFee(&vehicle, stayTime)
  225. // 4. 更新出场记录
  226. record.ExitTime = &exitTime
  227. record.StayTime = stayTime
  228. record.Fee = fee
  229. record.ExitImage = req.ExitImage
  230. record.PaymentStatus = "paid"
  231. if err := global.GVA_DB.Save(&record).Error; err != nil {
  232. return request.VehicleExitResponse{}, err
  233. }
  234. // 5. 更新车辆统计信息
  235. vehicle.LastExitTime = &exitTime
  236. vehicle.TotalStayTime += stayTime
  237. vehicle.TotalFee += fee
  238. global.GVA_DB.Save(&vehicle)
  239. // 6. 完全匹配你的匿名结构体返回(正确写法)
  240. var response request.VehicleExitResponse
  241. response.Success = true
  242. response.Message = "车辆出场成功"
  243. response.Data.PlateNumber = record.PlateNumber
  244. response.Data.EntryTime = record.EntryTime.Unix()
  245. response.Data.ExitTime = exitTime.Unix()
  246. response.Data.StayTime = stayTime
  247. response.Data.Fee = fee
  248. response.Data.PaymentStatus = record.PaymentStatus
  249. return response, nil
  250. }
  251. // ExitPreview 费用预览(不修改数据库,仅查询+计算费用)
  252. func (s *VehicleService) ExitPreview(req request.VehicleEntry) (vehicleResponse.ExitPreviewResponse, error) {
  253. var record dao.VehicleRecord
  254. err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error
  255. if err != nil {
  256. return vehicleResponse.ExitPreviewResponse{}, errors.New("车辆未入场或已出场")
  257. }
  258. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  259. if err != nil {
  260. return vehicleResponse.ExitPreviewResponse{}, err
  261. }
  262. now := time.Now()
  263. stayTime := int64(now.Sub(record.EntryTime).Minutes())
  264. if stayTime < 0 {
  265. stayTime = 0
  266. }
  267. fee := s.calculateFee(&vehicle, stayTime)
  268. typeName := ""
  269. isTemp := false
  270. if vehicle.VehicleType != nil {
  271. typeName = vehicle.VehicleType.Name
  272. isTemp = vehicle.VehicleType.IsSystem
  273. }
  274. return vehicleResponse.ExitPreviewResponse{
  275. PlateNumber: vehicle.PlateNumber,
  276. EntryTime: record.EntryTime.Unix(),
  277. StayTime: stayTime,
  278. Fee: fee,
  279. VehicleType: typeName,
  280. IsTemp: isTemp,
  281. }, nil
  282. }
  283. // ExitConfirm 确认出场 + 支付(更新车辆记录 + 创建支付记录)
  284. func (s *VehicleService) ExitConfirm(req request.ExitConfirmRequest, operatorID uint) (vehicleResponse.ExitConfirmResponse, error) {
  285. var record dao.VehicleRecord
  286. err := buildPlateOrRfidQuery(global.GVA_DB, req.PlateNumber, req.RFIDTag).Where("exit_time IS NULL").First(&record).Error
  287. if err != nil {
  288. return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆未入场或已出场")
  289. }
  290. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  291. if err != nil {
  292. return vehicleResponse.ExitConfirmResponse{}, err
  293. }
  294. exitTime := time.Now()
  295. stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
  296. if stayTime < 0 {
  297. stayTime = 0
  298. }
  299. fee := s.calculateFee(&vehicle, stayTime)
  300. record.ExitTime = &exitTime
  301. record.StayTime = stayTime
  302. record.Fee = fee
  303. record.PaymentMethod = req.PaymentMethod
  304. global.GVA_DB.Save(&record)
  305. vehicle.LastExitTime = &exitTime
  306. vehicle.TotalStayTime += stayTime
  307. vehicle.TotalFee += fee
  308. global.GVA_DB.Save(&vehicle)
  309. paymentSvc := paymentService.NewPaymentService()
  310. pr, err := paymentSvc.ProcessPayment(record.ID, req.PaymentMethod, fee, req.PaidAmount, operatorID)
  311. if err != nil {
  312. return vehicleResponse.ExitConfirmResponse{}, err
  313. }
  314. changeAmount := 0.0
  315. if pr != nil {
  316. changeAmount = pr.ChangeAmount
  317. }
  318. return vehicleResponse.ExitConfirmResponse{
  319. PlateNumber: vehicle.PlateNumber,
  320. EntryTime: record.EntryTime.Unix(),
  321. ExitTime: exitTime.Unix(),
  322. StayTime: stayTime,
  323. Fee: fee,
  324. ChangeAmount: changeAmount,
  325. PaymentMethod: req.PaymentMethod,
  326. }, nil
  327. }
  328. // calculateFee 计算停车费用
  329. func (s *VehicleService) calculateFee(vehicle *dao.Vehicle, stayTime int64) float64 {
  330. // 1. 根据车辆类型ID获取收费配置(唯一)
  331. var feeConfig dao.FeeConfig
  332. if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil {
  333. // 无配置 → 默认计费
  334. return float64(stayTime) * 0.1
  335. }
  336. // 2. 判断VIP有效性
  337. isVIP := false
  338. if vehicle.OwnerId != nil {
  339. var owner dao.Owner
  340. if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil {
  341. isVIP = owner.IsVip && time.Now().Before(owner.VipExpireTime)
  342. }
  343. }
  344. // 3. VIP 免费
  345. if isVIP && feeConfig.IsVIPFree {
  346. return 0
  347. }
  348. // ===================== 修复这里 =====================
  349. // 4. 在免费时长内 → 直接 0 元
  350. if stayTime <= int64(feeConfig.StartTime) {
  351. return 0
  352. }
  353. // 5. 超过免费时长 → 开始计费
  354. extraTime := stayTime - int64(feeConfig.StartTime)
  355. extraUnits := extraTime / int64(feeConfig.UnitTime)
  356. if extraTime%int64(feeConfig.UnitTime) > 0 {
  357. extraUnits++
  358. }
  359. // 起步费 + 超时费用
  360. fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee
  361. // 6. 每日封顶
  362. if feeConfig.DailyMaxFee > 0 && fee > feeConfig.DailyMaxFee {
  363. fee = feeConfig.DailyMaxFee
  364. }
  365. // 7. VIP 折扣
  366. if isVIP {
  367. fee *= feeConfig.VIPDiscount
  368. }
  369. return fee
  370. }