diff --git a/internal/web/service/bulk_traffic_test.go b/internal/web/service/bulk_traffic_test.go index 959d88fbd..160fd4e29 100644 --- a/internal/web/service/bulk_traffic_test.go +++ b/internal/web/service/bulk_traffic_test.go @@ -119,6 +119,33 @@ func TestDelDepletedRemovesOnlyDepleted(t *testing.T) { } } +func TestClientDeleteKeepTrafficPreservesRowForAttachedClient(t *testing.T) { + setupBulkDB(t) + svc := &ClientService{} + inboundSvc := &InboundService{} + + email := "keepme@x" + c := model.Client{Email: email, ID: "11111111-1111-1111-1111-111111111111", SubID: email, Enable: true} + ib := mkInbound(t, 52030, model.VLESS, clientsSettings(t, []model.Client{c})) + if err := svc.SyncInbound(nil, ib.Id, []model.Client{c}); err != nil { + t.Fatalf("seed linkage: %v", err) + } + mkTraffic(t, ib.Id, email, 111, 222, 0, 0, true) + + rec := lookupClientRecord(t, email) + if _, err := svc.Delete(inboundSvc, rec.Id, true); err != nil { + t.Fatalf("Delete(keepTraffic): %v", err) + } + + var cnt int64 + if err := database.GetDB().Model(&xray.ClientTraffic{}).Where("email = ?", email).Count(&cnt).Error; err != nil { + t.Fatalf("count traffic: %v", err) + } + if cnt != 1 { + t.Fatalf("keepTraffic delete of an inbound-attached client must preserve its client_traffics row, found %d", cnt) + } +} + func TestGetClientTrafficByEmailReadsClientsTable(t *testing.T) { setupBulkDB(t) svc := &ClientService{} diff --git a/internal/web/service/client_bulk.go b/internal/web/service/client_bulk.go index c0cc32eec..635d951b2 100644 --- a/internal/web/service/client_bulk.go +++ b/internal/web/service/client_bulk.go @@ -823,7 +823,7 @@ func (s *ClientService) BulkDelete(inboundSvc *InboundService, emails []string, needRestart := false for inboundId, ibEmails := range emailsByInbound { - ibResult := s.bulkDelInboundClients(inboundSvc, inboundId, ibEmails, recordsByEmail, false) + ibResult := s.bulkDelInboundClients(inboundSvc, inboundId, ibEmails, recordsByEmail, keepTraffic) if ibResult.needRestart { needRestart = true } diff --git a/internal/web/service/client_crud.go b/internal/web/service/client_crud.go index b8a41b824..5da24296e 100644 --- a/internal/web/service/client_crud.go +++ b/internal/web/service/client_crud.go @@ -510,7 +510,7 @@ func (s *ClientService) Delete(inboundSvc *InboundService, id int, keepTraffic b if existing.Email == "" { continue } - nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, false, true) + nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, existing.Email, keepTraffic, true) if delErr != nil { // The client is already absent from this inbound (data drift or a // retried delete). Skip it — deletion stays idempotent. @@ -678,7 +678,7 @@ func (s *ClientService) DeleteByEmail(inboundSvc *InboundService, email string, needRestart := false var delErrs []error for _, ibId := range inboundIds { - nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, email, false, true) + nr, delErr := s.DelInboundClientByEmail(inboundSvc, ibId, email, keepTraffic, true) if delErr != nil { if errors.Is(delErr, ErrClientNotInInbound) { continue