vehicle.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. )
  12. type VehicleService struct{}
  13. // CreateVehicle 创建车辆信息
  14. func (s *VehicleService) CreateVehicle(req request.VehicleCreate) error {
  15. // 检查车牌号是否已存在
  16. var existingVehicle dao.Vehicle
  17. result := global.GVA_DB.Where("plate_number = ? OR rfid_tag = ?", req.PlateNumber, req.RFIDTag).First(&existingVehicle)
  18. if result.RowsAffected > 0 {
  19. return errors.New("车牌号已存在")
  20. }
  21. // 检查车辆类型是否存在
  22. var vehicleType dao.VehicleType
  23. result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
  24. if result.RowsAffected == 0 {
  25. return errors.New("车辆类型不存在")
  26. }
  27. // 处理临时车辆的情况
  28. var ownerId *uint
  29. if req.OwnerId > 0 {
  30. ownerId = &req.OwnerId
  31. }
  32. vehicle := dao.Vehicle{
  33. PlateNumber: req.PlateNumber,
  34. RFIDTag: req.RFIDTag,
  35. VehicleTypeID: req.VehicleTypeID,
  36. VehicleBrand: req.VehicleBrand,
  37. VehicleColor: req.VehicleColor,
  38. OwnerId: ownerId,
  39. }
  40. return global.GVA_DB.Create(&vehicle).Error
  41. }
  42. // UpdateVehicle 更新车辆信息
  43. func (s *VehicleService) UpdateVehicle(req request.VehicleUpdate) error {
  44. var vehicle dao.Vehicle
  45. result := global.GVA_DB.First(&vehicle, req.ID)
  46. if result.RowsAffected == 0 {
  47. return errors.New("车辆不存在")
  48. }
  49. // 检查车牌号是否被其他车辆使用
  50. var existingVehicle dao.Vehicle
  51. result = global.GVA_DB.Where("(plate_number = ? OR rfid_tag = ?) AND id != ?", req.PlateNumber, req.RFIDTag, req.ID).First(&existingVehicle)
  52. fmt.Println("result", result.RowsAffected)
  53. if result.RowsAffected > 0 {
  54. return errors.New("车牌号已被其他车辆使用")
  55. }
  56. // 检查车辆类型是否存在
  57. var vehicleType dao.VehicleType
  58. result = global.GVA_DB.First(&vehicleType, req.VehicleTypeID)
  59. if result.RowsAffected == 0 {
  60. return errors.New("车辆类型不存在")
  61. }
  62. // 处理临时车辆的情况
  63. var ownerId *uint
  64. if req.OwnerId > 0 {
  65. ownerId = &req.OwnerId
  66. } else {
  67. ownerId = nil
  68. }
  69. err := global.GVA_DB.Model(&vehicle).Updates(map[string]interface{}{
  70. "plate_number": req.PlateNumber,
  71. "rfid_tag": req.RFIDTag,
  72. "vehicle_type_id": req.VehicleTypeID,
  73. "vehicle_brand": req.VehicleBrand,
  74. "vehicle_color": req.VehicleColor,
  75. "owner_id": ownerId,
  76. }).Error
  77. return err
  78. }
  79. // GetVehicleByID 根据ID获取车辆信息
  80. func (s *VehicleService) GetVehicleByID(id uint) (dao.Vehicle, error) {
  81. var vehicle dao.Vehicle
  82. result := global.GVA_DB.First(&vehicle, id)
  83. if result.RowsAffected == 0 {
  84. return dao.Vehicle{}, errors.New("车辆不存在")
  85. }
  86. return vehicle, nil
  87. }
  88. // GetVehicleByPlateNumber 根据车牌号或RFID获取车辆信息
  89. func (s *VehicleService) GetVehicleByPlateNumber(plateNumber, RFIDTag string) (dao.Vehicle, error) {
  90. var vehicle dao.Vehicle
  91. query := global.GVA_DB.Preload("VehicleType")
  92. if RFIDTag != "" {
  93. query = query.Where("plate_number = ? OR rfid_tag = ?", plateNumber, RFIDTag)
  94. } else {
  95. query = query.Where("plate_number = ?", plateNumber)
  96. }
  97. result := query.First(&vehicle)
  98. if result.RowsAffected == 0 {
  99. return dao.Vehicle{}, errors.New("车辆不存在")
  100. }
  101. return vehicle, nil
  102. }
  103. func (s *VehicleService) QueryAllVehicles() ([]dao.Vehicle, error) {
  104. var vehicles []dao.Vehicle
  105. err := global.GVA_DB.Find(&vehicles).Error
  106. if err != nil {
  107. return vehicles, err
  108. }
  109. return vehicles, nil
  110. }
  111. // ListVehicles 获取车辆列表
  112. func (s *VehicleService) ListVehicles(req request.VehicleQuery) (int64, []dao.Vehicle, error) {
  113. var vehicles []dao.Vehicle
  114. var total int64
  115. query := global.GVA_DB.Model(&dao.Vehicle{})
  116. if req.PlateNumber != "" {
  117. query = query.Where("plate_number LIKE ?", "%"+req.PlateNumber+"%")
  118. }
  119. if req.VehicleTypeID > 0 {
  120. query = query.Where("vehicle_type_id = ?", req.VehicleTypeID)
  121. }
  122. if req.OwnerId > 0 {
  123. query = query.Where("owner_id = ?", req.OwnerId)
  124. }
  125. // 计算总数
  126. query.Count(&total)
  127. // 分页查询
  128. offset := (req.Page - 1) * req.PageSize
  129. result := query.Offset(offset).Limit(req.PageSize).Preload("Owner").Preload("VehicleType").Find(&vehicles)
  130. if result.Error != nil {
  131. return 0, nil, result.Error
  132. }
  133. return total, vehicles, nil
  134. }
  135. // DeleteVehicle 删除车辆
  136. func (s *VehicleService) DeleteVehicle(id uint) error {
  137. result := global.GVA_DB.Delete(&dao.Vehicle{}, id)
  138. if result.RowsAffected == 0 {
  139. return errors.New("车辆不存在")
  140. }
  141. return result.Error
  142. }
  143. // VehicleEntry 车辆入场
  144. func (s *VehicleService) VehicleEntry(req request.VehicleEntry) (request.VehicleEntryResponse, error) {
  145. // 检查车辆是否存在
  146. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  147. if err != nil {
  148. // 如果车辆不存在,创建新车辆,自动归类为"临时车"
  149. var tempType dao.VehicleType
  150. if global.GVA_DB.Where("is_system = ?", true).First(&tempType).Error != nil {
  151. return request.VehicleEntryResponse{}, errors.New("系统错误:临时车类型未配置")
  152. }
  153. createReq := request.VehicleCreate{
  154. PlateNumber: req.PlateNumber,
  155. VehicleTypeID: tempType.ID,
  156. OwnerId: 0,
  157. }
  158. err = s.CreateVehicle(createReq)
  159. if err != nil {
  160. return request.VehicleEntryResponse{}, err
  161. }
  162. vehicle, _ = s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  163. }
  164. // 检查是否有未出场的记录
  165. var existingRecord dao.VehicleRecord
  166. result := global.GVA_DB.Where("(plate_number = ? OR rfid_tag = ?) AND exit_time IS NULL", req.PlateNumber, req.RFIDTag).First(&existingRecord)
  167. if result.RowsAffected > 0 {
  168. return request.VehicleEntryResponse{}, errors.New("车辆已入场,未出场")
  169. }
  170. // 创建入场记录
  171. entryTime := time.Now()
  172. record := dao.VehicleRecord{
  173. PlateNumber: req.PlateNumber,
  174. RFIDTag: req.RFIDTag,
  175. EntryTime: entryTime,
  176. ParkingLotID: req.ParkingLotID,
  177. ParkingSpaceID: req.ParkingSpaceID,
  178. EntryImage: req.EntryImage,
  179. PaymentStatus: "unpaid",
  180. }
  181. err = global.GVA_DB.Create(&record).Error
  182. if err != nil {
  183. return request.VehicleEntryResponse{}, err
  184. }
  185. // 更新车辆最近入场时间
  186. vehicle.LastEntryTime = &entryTime
  187. global.GVA_DB.Save(&vehicle)
  188. response := request.VehicleEntryResponse{
  189. Success: true,
  190. Message: "车辆入场成功",
  191. }
  192. response.Data.PlateNumber = req.PlateNumber
  193. response.Data.EntryTime = entryTime.Unix()
  194. response.Data.ParkingLotID = req.ParkingLotID
  195. response.Data.ParkingSpaceID = req.ParkingSpaceID
  196. return response, nil
  197. }
  198. // VehicleExit 车辆出场
  199. func (s *VehicleService) VehicleExit(req request.VehicleExit) (request.VehicleExitResponse, error) {
  200. // 1. 查询未出场记录
  201. var record dao.VehicleRecord
  202. if err := global.GVA_DB.Where("(plate_number = ? OR rfid_tag = ?) AND exit_time IS NULL", req.PlateNumber, req.RFIDTag).First(&record).Error; err != nil {
  203. return request.VehicleExitResponse{}, errors.New("车辆未入场或已出场")
  204. }
  205. // 2. 获取车辆信息
  206. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  207. if err != nil {
  208. return request.VehicleExitResponse{}, err
  209. }
  210. // 3. 计算停留时长 & 费用
  211. exitTime := time.Now()
  212. stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
  213. if stayTime < 0 {
  214. stayTime = 0
  215. }
  216. fee := s.calculateFee(&vehicle, stayTime)
  217. // 4. 更新出场记录
  218. record.ExitTime = &exitTime
  219. record.StayTime = stayTime
  220. record.Fee = fee
  221. record.ExitImage = req.ExitImage
  222. record.PaymentStatus = "paid"
  223. if err := global.GVA_DB.Save(&record).Error; err != nil {
  224. return request.VehicleExitResponse{}, err
  225. }
  226. // 5. 更新车辆统计信息
  227. vehicle.LastExitTime = &exitTime
  228. vehicle.TotalStayTime += stayTime
  229. vehicle.TotalFee += fee
  230. global.GVA_DB.Save(&vehicle)
  231. // 6. 完全匹配你的匿名结构体返回(正确写法)
  232. var response request.VehicleExitResponse
  233. response.Success = true
  234. response.Message = "车辆出场成功"
  235. response.Data.PlateNumber = record.PlateNumber
  236. response.Data.EntryTime = record.EntryTime.Unix()
  237. response.Data.ExitTime = exitTime.Unix()
  238. response.Data.StayTime = stayTime
  239. response.Data.Fee = fee
  240. response.Data.PaymentStatus = record.PaymentStatus
  241. return response, nil
  242. }
  243. // ExitPreview 费用预览(不修改数据库,仅查询+计算费用)
  244. func (s *VehicleService) ExitPreview(req request.VehicleEntry) (vehicleResponse.ExitPreviewResponse, error) {
  245. var record dao.VehicleRecord
  246. err := global.GVA_DB.Where("(plate_number = ? OR rfid_tag = ?) AND exit_time IS NULL",
  247. req.PlateNumber, req.RFIDTag).First(&record).Error
  248. if err != nil {
  249. return vehicleResponse.ExitPreviewResponse{}, errors.New("车辆未入场或已出场")
  250. }
  251. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  252. if err != nil {
  253. return vehicleResponse.ExitPreviewResponse{}, err
  254. }
  255. now := time.Now()
  256. stayTime := int64(now.Sub(record.EntryTime).Minutes())
  257. if stayTime < 0 {
  258. stayTime = 0
  259. }
  260. fee := s.calculateFee(&vehicle, stayTime)
  261. typeName := ""
  262. isTemp := false
  263. if vehicle.VehicleType != nil {
  264. typeName = vehicle.VehicleType.Name
  265. isTemp = vehicle.VehicleType.IsSystem
  266. }
  267. return vehicleResponse.ExitPreviewResponse{
  268. PlateNumber: vehicle.PlateNumber,
  269. EntryTime: record.EntryTime.Unix(),
  270. StayTime: stayTime,
  271. Fee: fee,
  272. VehicleType: typeName,
  273. IsTemp: isTemp,
  274. }, nil
  275. }
  276. // ExitConfirm 确认出场 + 支付(更新车辆记录 + 创建支付记录)
  277. func (s *VehicleService) ExitConfirm(req request.ExitConfirmRequest, operatorID uint) (vehicleResponse.ExitConfirmResponse, error) {
  278. var record dao.VehicleRecord
  279. err := global.GVA_DB.Where("(plate_number = ? OR rfid_tag = ?) AND exit_time IS NULL",
  280. req.PlateNumber, req.RFIDTag).First(&record).Error
  281. if err != nil {
  282. return vehicleResponse.ExitConfirmResponse{}, errors.New("车辆未入场或已出场")
  283. }
  284. vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
  285. if err != nil {
  286. return vehicleResponse.ExitConfirmResponse{}, err
  287. }
  288. exitTime := time.Now()
  289. stayTime := int64(exitTime.Sub(record.EntryTime).Minutes())
  290. if stayTime < 0 {
  291. stayTime = 0
  292. }
  293. fee := s.calculateFee(&vehicle, stayTime)
  294. record.ExitTime = &exitTime
  295. record.StayTime = stayTime
  296. record.Fee = fee
  297. record.PaymentMethod = req.PaymentMethod
  298. global.GVA_DB.Save(&record)
  299. vehicle.LastExitTime = &exitTime
  300. vehicle.TotalStayTime += stayTime
  301. vehicle.TotalFee += fee
  302. global.GVA_DB.Save(&vehicle)
  303. paymentSvc := paymentService.NewPaymentService()
  304. pr, err := paymentSvc.ProcessPayment(record.ID, req.PaymentMethod, fee, req.PaidAmount, operatorID)
  305. if err != nil {
  306. return vehicleResponse.ExitConfirmResponse{}, err
  307. }
  308. changeAmount := 0.0
  309. if pr != nil {
  310. changeAmount = pr.ChangeAmount
  311. }
  312. return vehicleResponse.ExitConfirmResponse{
  313. PlateNumber: vehicle.PlateNumber,
  314. EntryTime: record.EntryTime.Unix(),
  315. ExitTime: exitTime.Unix(),
  316. StayTime: stayTime,
  317. Fee: fee,
  318. ChangeAmount: changeAmount,
  319. PaymentMethod: req.PaymentMethod,
  320. }, nil
  321. }
  322. // calculateFee 计算停车费用
  323. func (s *VehicleService) calculateFee(vehicle *dao.Vehicle, stayTime int64) float64 {
  324. // 1. 根据车辆类型ID获取收费配置(唯一)
  325. var feeConfig dao.FeeConfig
  326. if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil {
  327. // 无配置 → 默认计费
  328. return float64(stayTime) * 0.1
  329. }
  330. // 2. 判断VIP有效性
  331. isVIP := false
  332. if vehicle.OwnerId != nil {
  333. var owner dao.Owner
  334. if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil {
  335. isVIP = owner.IsVip && time.Now().Before(owner.VipExpireTime)
  336. }
  337. }
  338. // 3. VIP 免费
  339. if isVIP && feeConfig.IsVIPFree {
  340. return 0
  341. }
  342. // ===================== 修复这里 =====================
  343. // 4. 在免费时长内 → 直接 0 元
  344. if stayTime <= int64(feeConfig.StartTime) {
  345. return 0
  346. }
  347. // 5. 超过免费时长 → 开始计费
  348. extraTime := stayTime - int64(feeConfig.StartTime)
  349. extraUnits := extraTime / int64(feeConfig.UnitTime)
  350. if extraTime%int64(feeConfig.UnitTime) > 0 {
  351. extraUnits++
  352. }
  353. // 起步费 + 超时费用
  354. fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee
  355. // 6. 每日封顶
  356. if feeConfig.DailyMaxFee > 0 && fee > feeConfig.DailyMaxFee {
  357. fee = feeConfig.DailyMaxFee
  358. }
  359. // 7. VIP 折扣
  360. if isVIP {
  361. fee *= feeConfig.VIPDiscount
  362. }
  363. return fee
  364. }