| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- 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)
- }
|