|
|
@@ -0,0 +1,198 @@
|
|
|
+package vehicle
|
|
|
+
|
|
|
+import (
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/stretchr/testify/require"
|
|
|
+ "wails-app/internal/dao"
|
|
|
+ "wails-app/internal/global"
|
|
|
+ "wails-app/internal/model/vehicle/request"
|
|
|
+ "wails-app/internal/modules/digital-ticket/service"
|
|
|
+ paymentService "wails-app/internal/modules/payment/service"
|
|
|
+)
|
|
|
+
|
|
|
+// createPaidTicketFixture 构造"已支付票 + 已支付流水 + 会话已支付"的出场场景。
|
|
|
+// paidAgoMinutes 控制支付时间距今多久(用于超时判定)。
|
|
|
+func createPaidTicketFixture(t *testing.T, paidAgoMinutes int) (*dao.VehicleRecord, *dao.Vehicle, *dao.DigitalTicket) {
|
|
|
+ t.Helper()
|
|
|
+ record, vehicle := createVehiclePaymentFixture(t, true)
|
|
|
+
|
|
|
+ ticketSvc := service.NewTicketService()
|
|
|
+ ticket, err := ticketSvc.GetByVehicleRecordIDTx(global.GVA_DB, record.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ paidAt := time.Now().Add(-time.Duration(paidAgoMinutes) * time.Minute)
|
|
|
+ require.NoError(t, global.GVA_DB.Model(ticket).Updates(map[string]interface{}{
|
|
|
+ "state": service.StatePaid,
|
|
|
+ "fee": 10.0,
|
|
|
+ "paid_amount": 10.0,
|
|
|
+ "paid_at": paidAt,
|
|
|
+ "payment_entry": paymentService.EntryCounter,
|
|
|
+ "payment_method": paymentService.PaymentCash,
|
|
|
+ }).Error)
|
|
|
+
|
|
|
+ require.NoError(t, global.GVA_DB.Create(&dao.PaymentRecord{
|
|
|
+ RecordID: record.ID, PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentCash,
|
|
|
+ Amount: 10, PaidAmount: 10, ChangeAmount: 0, PaidAt: paidAt, OperatorID: 1,
|
|
|
+ }).Error)
|
|
|
+ require.NoError(t, global.GVA_DB.Model(record).Update("payment_status", "paid").Error)
|
|
|
+
|
|
|
+ ticket, err = ticketSvc.GetByVehicleRecordIDTx(global.GVA_DB, record.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+ return record, vehicle, ticket
|
|
|
+}
|
|
|
+
|
|
|
+// setFreeExitMinutes 设置该车辆类型的缴费后免费离场时限。
|
|
|
+func setFreeExitMinutes(t *testing.T, vehicleTypeID uint, minutes int) {
|
|
|
+ t.Helper()
|
|
|
+ require.NoError(t, global.GVA_DB.Model(&dao.FeeConfig{}).
|
|
|
+ Where("vehicle_type_id = ?", vehicleTypeID).Update("free_exit_minutes", minutes).Error)
|
|
|
+}
|
|
|
+
|
|
|
+func TestPaidTicketOvertimeNotExceeded(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ _, vehicle, ticket := createPaidTicketFixture(t, 3) // 3 分钟前支付
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+
|
|
|
+ overtime, newFee, diff, err := svc.paidTicketOvertime(vehicle, ticket, 120)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.False(t, overtime)
|
|
|
+ require.Zero(t, newFee)
|
|
|
+ require.Zero(t, diff)
|
|
|
+}
|
|
|
+
|
|
|
+func TestPaidTicketOvertimeExceeded(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ _, vehicle, ticket := createPaidTicketFixture(t, 20) // 20 分钟前支付
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+
|
|
|
+ // 停留 1200 分钟:fixture 配置 UnitTime=60/UnitFee=1 → 新费用 20 元,已付 10 → 补缴 10
|
|
|
+ overtime, newFee, diff, err := svc.paidTicketOvertime(vehicle, ticket, 1200)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.True(t, overtime)
|
|
|
+ require.Equal(t, 20.0, newFee)
|
|
|
+ require.Equal(t, 10.0, diff)
|
|
|
+}
|
|
|
+
|
|
|
+func TestPaidTicketOvertimeDisabledAndNilPaidAt(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ _, vehicle, ticket := createPaidTicketFixture(t, 20)
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 0) // 0=不限
|
|
|
+
|
|
|
+ overtime, _, _, err := svc.paidTicketOvertime(vehicle, ticket, 1200)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.False(t, overtime)
|
|
|
+
|
|
|
+ // PaidAt 为空(历史票)视为未超时
|
|
|
+ require.NoError(t, global.GVA_DB.Model(ticket).Update("paid_at", nil).Error)
|
|
|
+ ticket.PaidAt = nil
|
|
|
+ overtime, _, _, err = svc.paidTicketOvertime(vehicle, ticket, 1200)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.False(t, overtime)
|
|
|
+}
|
|
|
+
|
|
|
+func TestExitConfirmOvertimeRequiresTopUp(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ record, vehicle, _ := createPaidTicketFixture(t, 20)
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+ // 停留 20 小时 → 费用 20 元,补缴 10 元
|
|
|
+ require.NoError(t, global.GVA_DB.Model(record).Update("entry_time", time.Now().Add(-20*time.Hour)).Error)
|
|
|
+
|
|
|
+ // 未携带补缴实收 → 拒绝
|
|
|
+ _, err := svc.ExitConfirm(request.ExitConfirmRequest{
|
|
|
+ SessionID: record.ID, PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentCash,
|
|
|
+ }, 1)
|
|
|
+ require.Error(t, err)
|
|
|
+ require.Contains(t, err.Error(), "需补缴差额")
|
|
|
+
|
|
|
+ // 会话仍未关闭
|
|
|
+ var after dao.VehicleRecord
|
|
|
+ require.NoError(t, global.GVA_DB.First(&after, record.ID).Error)
|
|
|
+ require.Nil(t, after.ExitTime)
|
|
|
+}
|
|
|
+
|
|
|
+func TestExitConfirmOvertimeFreeMethodRejected(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ record, vehicle, _ := createPaidTicketFixture(t, 20)
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+ require.NoError(t, global.GVA_DB.Model(record).Update("entry_time", time.Now().Add(-20*time.Hour)).Error)
|
|
|
+
|
|
|
+ _, err := svc.ExitConfirm(request.ExitConfirmRequest{
|
|
|
+ SessionID: record.ID, PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentFree,
|
|
|
+ PaidAmount: 10,
|
|
|
+ }, 1)
|
|
|
+ require.Error(t, err)
|
|
|
+ require.Contains(t, err.Error(), "免密放行")
|
|
|
+}
|
|
|
+
|
|
|
+func TestExitConfirmOvertimeTopUpSucceeds(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ record, vehicle, ticket := createPaidTicketFixture(t, 20)
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+ require.NoError(t, global.GVA_DB.Model(record).Update("entry_time", time.Now().Add(-20*time.Hour)).Error)
|
|
|
+
|
|
|
+ result, err := svc.ExitConfirm(request.ExitConfirmRequest{
|
|
|
+ SessionID: record.ID, PlateNumber: vehicle.PlateNumber,
|
|
|
+ PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentCash,
|
|
|
+ PaidAmount: 10,
|
|
|
+ }, 2)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Equal(t, 20.0, result.Fee) // 总费用
|
|
|
+
|
|
|
+ // 会话已关闭且费用为总费用
|
|
|
+ var after dao.VehicleRecord
|
|
|
+ require.NoError(t, global.GVA_DB.First(&after, record.ID).Error)
|
|
|
+ require.NotNil(t, after.ExitTime)
|
|
|
+ require.Equal(t, 20.0, after.Fee)
|
|
|
+
|
|
|
+ // 两条流水:原 10 元 + 补缴 10 元
|
|
|
+ var payments []dao.PaymentRecord
|
|
|
+ require.NoError(t, global.GVA_DB.Where("record_id = ?", record.ID).Find(&payments).Error)
|
|
|
+ require.Len(t, payments, 2)
|
|
|
+ require.Equal(t, 20.0, payments[0].Amount+payments[1].Amount)
|
|
|
+
|
|
|
+ // 票面累计 + PaidAt 刷新
|
|
|
+ var updatedTicket dao.DigitalTicket
|
|
|
+ require.NoError(t, global.GVA_DB.First(&updatedTicket, ticket.ID).Error)
|
|
|
+ require.Equal(t, 20.0, updatedTicket.Fee)
|
|
|
+ require.Equal(t, 20.0, updatedTicket.PaidAmount)
|
|
|
+ require.NotNil(t, updatedTicket.PaidAt)
|
|
|
+ require.True(t, updatedTicket.PaidAt.After(time.Now().Add(-time.Minute)), "PaidAt 应刷新为补缴时间")
|
|
|
+}
|
|
|
+
|
|
|
+func TestExitConfirmOvertimeNoDiffPasses(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ record, vehicle, _ := createPaidTicketFixture(t, 20)
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+ // 停留仅 2 小时:新费用 2 元 < 已付 10 元 → 差额 0,无需补缴直接放行
|
|
|
+ require.NoError(t, global.GVA_DB.Model(record).Update("entry_time", time.Now().Add(-2*time.Hour)).Error)
|
|
|
+
|
|
|
+ result, err := svc.ExitConfirm(request.ExitConfirmRequest{
|
|
|
+ SessionID: record.ID, PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentCash,
|
|
|
+ }, 1)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Equal(t, 2.0, result.Fee)
|
|
|
+
|
|
|
+ var after dao.VehicleRecord
|
|
|
+ require.NoError(t, global.GVA_DB.First(&after, record.ID).Error)
|
|
|
+ require.NotNil(t, after.ExitTime)
|
|
|
+}
|
|
|
+
|
|
|
+func TestExitConfirmPaidNotOvertimeRegresses(t *testing.T) {
|
|
|
+ svc := setupVehiclePaymentTest(t)
|
|
|
+ record, vehicle, _ := createPaidTicketFixture(t, 3)
|
|
|
+ setFreeExitMinutes(t, vehicle.VehicleTypeID, 15)
|
|
|
+ require.NoError(t, global.GVA_DB.Model(record).Update("entry_time", time.Now().Add(-2*time.Hour)).Error)
|
|
|
+
|
|
|
+ // 未超时:消费旧流水(10 元)放行,不重算不补缴
|
|
|
+ result, err := svc.ExitConfirm(request.ExitConfirmRequest{
|
|
|
+ SessionID: record.ID, PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentCash,
|
|
|
+ }, 1)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Equal(t, 10.0, result.Fee)
|
|
|
+
|
|
|
+ var payments []dao.PaymentRecord
|
|
|
+ require.NoError(t, global.GVA_DB.Where("record_id = ?", record.ID).Find(&payments).Error)
|
|
|
+ require.Len(t, payments, 1)
|
|
|
+}
|