Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,565 @@
|
||||
using System.Text.Json;
|
||||
using Omika.App.Local.Models;
|
||||
using Omika.App.Local.Infrastructure;
|
||||
using Omika.App.Local.Services;
|
||||
using Omika.Core.Contracts.Clients;
|
||||
using Omika.Core.Contracts.Common;
|
||||
using Omika.Core.Contracts.Interventions;
|
||||
using Omika.Core.Contracts.Sites;
|
||||
|
||||
namespace Omika.Tests.Mobile.Local;
|
||||
|
||||
/// <summary>
|
||||
/// Tests du service de lecture des sites / lieux depuis les données locales
|
||||
/// SQLite. Relations réellement démontrables localement :
|
||||
/// <list type="bullet">
|
||||
/// <item>Site → Client : relation explicite <c>SiteDetailsDto.ClientId</c>.</item>
|
||||
/// <item>Site → Contacts : liste explicite <c>SiteDetailsDto.ContactIds</c>, résolue vers les Personnes/Contacts locales.</item>
|
||||
/// <item>Site → Interventions : relation directe <c>InterventionDetailsDto.SiteId</c>.</item>
|
||||
/// </list>
|
||||
/// Aucune relation n'est déduite par commodité (nom, adresse, transitivité).
|
||||
/// Lecture seule : aucune écriture ni Outbox n'est créée.
|
||||
/// </summary>
|
||||
public class SiteLocalServiceTests : LocalTestBase
|
||||
{
|
||||
private readonly SiteLocalService _service;
|
||||
|
||||
public SiteLocalServiceTests()
|
||||
{
|
||||
_service = new SiteLocalService(
|
||||
SiteRepository, ClientRepository, PersonneRepository, RendezVousRepository,
|
||||
InterventionRepository, ContextInterventionCacheRepository, SiteStateRepository, ApiClient);
|
||||
}
|
||||
|
||||
private static string Serialize<T>(T value) => JsonSerializer.Serialize(value);
|
||||
|
||||
private static LocalSite CreateSite(
|
||||
Guid id,
|
||||
string nom,
|
||||
string? code = null,
|
||||
string? ville = null,
|
||||
string? type = null,
|
||||
Guid? clientId = null,
|
||||
IReadOnlyList<string>? contactIds = null)
|
||||
{
|
||||
var dto = new SiteDetailsDto
|
||||
{
|
||||
Id = id.ToString(),
|
||||
Nom = nom,
|
||||
Code = code,
|
||||
Type = type is null ? null : new ReferenceValueDto { Label = type },
|
||||
ClientId = clientId?.ToString(),
|
||||
Adresse = ville is null
|
||||
? null
|
||||
: new AddressDto { City = ville, OneLine = ville },
|
||||
ContactIds = contactIds ?? []
|
||||
};
|
||||
return new LocalSite
|
||||
{
|
||||
Id = id,
|
||||
ClientId = clientId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private static LocalClient CreateClient(Guid id, string nom, string? code = null)
|
||||
{
|
||||
var dto = new ClientDetailsDto
|
||||
{
|
||||
Id = id.ToString(),
|
||||
Nom = nom,
|
||||
Code = code
|
||||
};
|
||||
return new LocalClient
|
||||
{
|
||||
Id = id,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private static LocalPersonne CreatePersonne(Guid id, string nomAffiche, string? email = null)
|
||||
{
|
||||
var dto = new ContactDto
|
||||
{
|
||||
Id = id.ToString(),
|
||||
NomAffiche = nomAffiche,
|
||||
Coordonnees = email is null
|
||||
? null
|
||||
: new ContactDetailsDto { Email = email }
|
||||
};
|
||||
return new LocalPersonne
|
||||
{
|
||||
Id = id,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private static LocalIntervention CreateIntervention(
|
||||
Guid id,
|
||||
Guid siteId,
|
||||
string reference,
|
||||
string titre,
|
||||
DateTime? datePrevueDebut = null,
|
||||
string? etat = null)
|
||||
{
|
||||
var dto = new InterventionDetailsDto
|
||||
{
|
||||
Id = id.ToString(),
|
||||
Reference = reference,
|
||||
Titre = titre,
|
||||
SiteId = siteId.ToString(),
|
||||
DatePrevueDebut = datePrevueDebut,
|
||||
Etat = etat is null ? null : new ReferenceValueDto { Label = etat }
|
||||
};
|
||||
return new LocalIntervention
|
||||
{
|
||||
Id = id,
|
||||
SiteId = siteId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private async Task CacheSiteInterventionsAsync(Guid siteId, params LocalIntervention[] interventions)
|
||||
{
|
||||
var details = interventions
|
||||
.Select(i => JsonSerializer.Deserialize<InterventionDetailsDto>(i.PayloadJson)!)
|
||||
.ToList();
|
||||
await ContextInterventionCacheRepository.ReplaceAsync(InterventionContextTypes.Site, siteId, details);
|
||||
}
|
||||
|
||||
// ===================== LISTE =====================
|
||||
|
||||
[Fact]
|
||||
public async Task AucunSite_RetourneListeVide()
|
||||
{
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnSite_RetourneLeSite()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha", "S001"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert
|
||||
var item = Assert.Single(result);
|
||||
Assert.Equal(siteId, item.SiteGuid);
|
||||
Assert.Equal("Site Alpha", item.Nom);
|
||||
Assert.Equal("S001", item.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlusieursSites_RetourneTous()
|
||||
{
|
||||
// Arrange
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Alpha"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Beta"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Tri_DeterministeParNomPuisCode()
|
||||
{
|
||||
// Arrange
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Beta", "S002"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Alpha", "S001"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Alpha", "S003"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert : tri stable par nom (insensible à la casse) puis code.
|
||||
Assert.Equal("Alpha", result[0].Nom);
|
||||
Assert.Equal("S001", result[0].Code);
|
||||
Assert.Equal("Alpha", result[1].Nom);
|
||||
Assert.Equal("S003", result[1].Code);
|
||||
Assert.Equal("Beta", result[2].Nom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RechercheLocale_InsensibleALaCasse()
|
||||
{
|
||||
// Arrange
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Alpha", "S001", "Paris"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Beta", "S002", "Lyon"));
|
||||
|
||||
// Act : le service retourne tout ; la recherche est gérée par le ViewModel.
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RechercheSansResultat_ListeVide()
|
||||
{
|
||||
// Arrange
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Alpha"));
|
||||
|
||||
// Act : le service retourne tout ; la recherche est gérée par le ViewModel.
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PayloadInvalide_EntreeIgnoree()
|
||||
{
|
||||
// Arrange
|
||||
await SiteRepository.UpsertAsync(new LocalSite
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PayloadJson = "not-json",
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Valide"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert : l'entrée invalide est ignorée proprement.
|
||||
var item = Assert.Single(result);
|
||||
Assert.Equal("Valide", item.Nom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IdentitePublique_Conservee()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Alpha"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert : l'identité publique (GUID) est conservée.
|
||||
var item = Assert.Single(result);
|
||||
Assert.Equal(siteId, item.SiteGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FonctionneOffline_SansAppelApi()
|
||||
{
|
||||
// Arrange : aucune dépendance réseau, uniquement SQLite local.
|
||||
await SiteRepository.UpsertAsync(CreateSite(Guid.NewGuid(), "Alpha"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSitesAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
// ===================== DÉTAIL =====================
|
||||
|
||||
[Fact]
|
||||
public async Task Detail_SiteTrouve()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha", "S001"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSiteByIdAsync(siteId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(siteId, result.SiteGuid);
|
||||
Assert.Equal("Site Alpha", result.Nom);
|
||||
Assert.Equal("S001", result.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Detail_SiteInconnu_RetourneNull()
|
||||
{
|
||||
// Act
|
||||
var result = await _service.GetSiteByIdAsync(Guid.NewGuid());
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Detail_DonneesReellesChargees()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha", "S001", "Paris", "Entrepôt"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSiteByIdAsync(siteId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Site Alpha", result.Nom);
|
||||
Assert.Equal("S001", result.Code);
|
||||
Assert.Equal("Entrepôt", result.Type);
|
||||
Assert.True(result.HasAdresse);
|
||||
Assert.Equal("Paris", result.Adresse!.City);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Detail_ChampsOptionnelsAbsents()
|
||||
{
|
||||
// Arrange : site minimal sans adresse ni type.
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSiteByIdAsync(siteId);
|
||||
|
||||
// Assert : champs optionnels absents restent nuls sans erreur.
|
||||
Assert.NotNull(result);
|
||||
Assert.Null(result.Type);
|
||||
Assert.Null(result.Adresse);
|
||||
Assert.False(result.HasAdresse);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Detail_AucuneRelation_NeProvoquePasDErreur()
|
||||
{
|
||||
// Arrange : site sans client, contacts ni interventions.
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Alpha"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSiteByIdAsync(siteId);
|
||||
|
||||
// Assert : collections vides sans erreur.
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result.Clients);
|
||||
Assert.Empty(result.Contacts);
|
||||
Assert.Empty(result.Interventions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Detail_IdentitePublique_Conservee()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteId = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Alpha"));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSiteByIdAsync(siteId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(siteId, result.SiteGuid);
|
||||
}
|
||||
|
||||
// ===================== RELATION SITE → CONTACTS =====================
|
||||
|
||||
[Fact]
|
||||
public async Task SiteA_RecoitUniquementSesContacts()
|
||||
{
|
||||
// Arrange : Site A embarque 2 contacts, Site B en embarque 1.
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid siteB = Guid.NewGuid();
|
||||
Guid contactA1 = Guid.NewGuid();
|
||||
Guid contactA2 = Guid.NewGuid();
|
||||
Guid contactB = Guid.NewGuid();
|
||||
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A", contactIds: [contactA1.ToString(), contactA2.ToString()]));
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteB, "Site B", contactIds: [contactB.ToString()]));
|
||||
await PersonneRepository.UpsertAsync(CreatePersonne(contactA1, "Contact A1"));
|
||||
await PersonneRepository.UpsertAsync(CreatePersonne(contactA2, "Contact A2"));
|
||||
await PersonneRepository.UpsertAsync(CreatePersonne(contactB, "Contact B"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : A reçoit uniquement ses contacts.
|
||||
Assert.NotNull(resultA);
|
||||
Assert.Equal(2, resultA.Contacts.Count);
|
||||
Assert.Contains(resultA.Contacts, c => c.ContactGuid == contactA1);
|
||||
Assert.Contains(resultA.Contacts, c => c.ContactGuid == contactA2);
|
||||
Assert.DoesNotContain(resultA.Contacts, c => c.ContactGuid == contactB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContactDuMemeClientMaisAutreSite_Exclu()
|
||||
{
|
||||
// Arrange : Contact lié au Site B uniquement, même si le Client est partagé.
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid siteB = Guid.NewGuid();
|
||||
Guid contactB = Guid.NewGuid();
|
||||
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteB, "Site B", contactIds: [contactB.ToString()]));
|
||||
await PersonneRepository.UpsertAsync(CreatePersonne(contactB, "Contact B"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : A n'a aucun contact de B.
|
||||
Assert.NotNull(resultA);
|
||||
Assert.Empty(resultA.Contacts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContactNonRelie_Exclu()
|
||||
{
|
||||
// Arrange : un contact sans ContactIds sur le site.
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid contact = Guid.NewGuid();
|
||||
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A"));
|
||||
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Contact Libre"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : aucun contact non relié n'est ajouté.
|
||||
Assert.NotNull(resultA);
|
||||
Assert.Empty(resultA.Contacts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ContactGuid_PublicConserve()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid contactA = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A", contactIds: [contactA.ToString()]));
|
||||
await PersonneRepository.UpsertAsync(CreatePersonne(contactA, "Contact A"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : le GUID public du contact est conservé.
|
||||
Assert.NotNull(resultA);
|
||||
var contact = Assert.Single(resultA.Contacts);
|
||||
Assert.Equal(contactA, contact.ContactGuid);
|
||||
}
|
||||
|
||||
// ===================== RELATION SITE → INTERVENTIONS =====================
|
||||
|
||||
[Fact]
|
||||
public async Task InterventionSiteA_Incluse()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid interventionA = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A"));
|
||||
await CacheSiteInterventionsAsync(siteA, CreateIntervention(interventionA, siteA, "INT-A", "Intervention A"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(resultA);
|
||||
var intervention = Assert.Single(resultA.Interventions);
|
||||
Assert.Equal(interventionA, intervention.InterventionGuid);
|
||||
Assert.Equal("INT-A", intervention.Reference);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InterventionSiteB_Exclue()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid siteB = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteB, "Site B"));
|
||||
await CacheSiteInterventionsAsync(siteA, CreateIntervention(Guid.NewGuid(), siteB, "INT-B", "Intervention B"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : A n'a aucune intervention de B.
|
||||
Assert.NotNull(resultA);
|
||||
Assert.Empty(resultA.Interventions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InterventionSansSite_Exclue()
|
||||
{
|
||||
// Arrange : intervention sans SiteId.
|
||||
Guid siteA = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A"));
|
||||
await CacheSiteInterventionsAsync(siteA, new LocalIntervention
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PayloadJson = Serialize(new InterventionDetailsDto
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Reference = "INT-X",
|
||||
Titre = "Intervention X"
|
||||
}),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : aucune intervention sans site n'est ajoutée.
|
||||
Assert.NotNull(resultA);
|
||||
Assert.Empty(resultA.Interventions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AucuneDeductionViaClient()
|
||||
{
|
||||
// Arrange : intervention liée au Client A mais pas au Site A.
|
||||
Guid clientA = Guid.NewGuid();
|
||||
Guid siteA = Guid.NewGuid();
|
||||
await ClientRepository.UpsertAsync(CreateClient(clientA, "Client A"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A", clientId: clientA));
|
||||
await CacheSiteInterventionsAsync(siteA, new LocalIntervention
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ClientId = clientA,
|
||||
PayloadJson = Serialize(new InterventionDetailsDto
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Reference = "INT-A",
|
||||
Titre = "Intervention A",
|
||||
ClientId = clientA.ToString()
|
||||
}),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : l'intervention n'est pas déduite via le Client.
|
||||
Assert.NotNull(resultA);
|
||||
Assert.Empty(resultA.Interventions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InterventionGuid_PublicConserve()
|
||||
{
|
||||
// Arrange
|
||||
Guid siteA = Guid.NewGuid();
|
||||
Guid interventionA = Guid.NewGuid();
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteA, "Site A"));
|
||||
await CacheSiteInterventionsAsync(siteA, CreateIntervention(interventionA, siteA, "INT-A", "Intervention A"));
|
||||
|
||||
// Act
|
||||
var resultA = await _service.GetSiteByIdAsync(siteA);
|
||||
|
||||
// Assert : le GUID public de l'intervention est conservé.
|
||||
Assert.NotNull(resultA);
|
||||
var intervention = Assert.Single(resultA.Interventions);
|
||||
Assert.Equal(interventionA, intervention.InterventionGuid);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user