251 lines
10 KiB
C#
251 lines
10 KiB
C#
using System.Text.Json;
|
||
using Omika.App.Local.Models;
|
||
using Omika.App.Local.Services;
|
||
using Omika.Core.Contracts.Common;
|
||
using Omika.Core.Contracts.Planning;
|
||
|
||
namespace Omika.Tests.Mobile.Local;
|
||
|
||
/// <summary>
|
||
/// Rendez-vous de groupe : l'unité affichée est l'AFFECTATION
|
||
/// (<c>RDV_Collaborateur</c>), jamais le noyau du rendez-vous. Une carte par
|
||
/// collaborateur et par jour, avec la date, les heures et le collaborateur de la
|
||
/// ligne réelle ; la fiche conserve l'affectation ouverte et expose, à titre
|
||
/// informatif, les jours et les collaborateurs du rendez-vous.
|
||
/// </summary>
|
||
public class AgendaServiceAffectationTests : LocalTestBase
|
||
{
|
||
private static readonly DateTime Jour1 = new(2026, 8, 12);
|
||
private static readonly DateTime Jour2 = new(2026, 8, 13);
|
||
|
||
private static readonly Guid CollaborateurA = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||
private static readonly Guid CollaborateurB = Guid.Parse("22222222-2222-2222-2222-222222222222");
|
||
|
||
private readonly AgendaService _service;
|
||
|
||
public AgendaServiceAffectationTests()
|
||
{
|
||
_service = new AgendaService(
|
||
RendezVousRepository,
|
||
ClientRepository,
|
||
SiteRepository,
|
||
InterventionRepository,
|
||
CollaborateurRepository,
|
||
ApiClient,
|
||
SessionService);
|
||
}
|
||
|
||
private static AffectationCollaborateurDto Affectation(
|
||
Guid affectationId,
|
||
Guid collaborateurId,
|
||
string nom,
|
||
DateTime debut,
|
||
DateTime fin)
|
||
=> new()
|
||
{
|
||
Id = affectationId.ToString("D"),
|
||
CollaborateurId = collaborateurId.ToString("D"),
|
||
NomAffiche = nom,
|
||
PeriodeDebut = debut,
|
||
PeriodeFin = fin
|
||
};
|
||
|
||
/// <summary>
|
||
/// Rendez-vous groupé sur DEUX jours avec DEUX collaborateurs : quatre
|
||
/// affectations, chacune avec son créneau.
|
||
/// </summary>
|
||
private async Task<(Guid RendezVousId, Guid AffectationAJour2, Guid AffectationBJour2)> PreparerGroupeAsync()
|
||
{
|
||
Guid rendezVousId = Guid.NewGuid();
|
||
Guid aJour1 = Guid.NewGuid();
|
||
Guid aJour2 = Guid.NewGuid();
|
||
Guid bJour1 = Guid.NewGuid();
|
||
Guid bJour2 = Guid.NewGuid();
|
||
|
||
var dto = new RendezVousDetailsDto
|
||
{
|
||
Id = rendezVousId.ToString(),
|
||
Titre = "Chantier groupé",
|
||
Debut = Jour1.AddHours(8),
|
||
Fin = Jour2.AddHours(17),
|
||
Statut = new ReferenceValueDto { Label = "Planifié" },
|
||
EstGroupe = true,
|
||
Collaborateurs =
|
||
[
|
||
Affectation(aJour1, CollaborateurA, "Alice Martin", Jour1.AddHours(8), Jour1.AddHours(12)),
|
||
Affectation(aJour2, CollaborateurA, "Alice Martin", Jour2.AddHours(8), Jour2.AddHours(12)),
|
||
Affectation(bJour1, CollaborateurB, "Bruno Petit", Jour1.AddHours(13), Jour1.AddHours(17)),
|
||
Affectation(bJour2, CollaborateurB, "Bruno Petit", Jour2.AddHours(13), Jour2.AddHours(17))
|
||
]
|
||
};
|
||
|
||
await RendezVousRepository.UpsertAsync(new LocalRendezVous
|
||
{
|
||
Id = rendezVousId,
|
||
PayloadJson = JsonSerializer.Serialize(dto),
|
||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||
});
|
||
|
||
return (rendezVousId, aJour2, bJour2);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Agenda_UneCarteParAffectation_AvecLeBonCollaborateurEtLeBonCreneau()
|
||
{
|
||
// Arrange : aucune portée connue → toutes les affectations sont présentées.
|
||
(Guid rendezVousId, _, _) = await PreparerGroupeAsync();
|
||
|
||
// Act
|
||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForRangeAsync(Jour1, Jour2.AddDays(1));
|
||
|
||
// Assert : une carte par affectation (2 collaborateurs × 2 jours).
|
||
Assert.Equal(4, result.Count);
|
||
|
||
// La carte d'Alice au 2e jour porte BIEN le créneau du 2e jour (et non
|
||
// celui du premier rendez-vous du groupe).
|
||
AgendaItem aliceJour2 = Assert.Single(result, i =>
|
||
i.CollaborateurNom == "Alice Martin" && i.Debut.Date == Jour2);
|
||
Assert.Equal(Jour2.AddHours(8), aliceJour2.Debut);
|
||
Assert.Equal(Jour2.AddHours(12), aliceJour2.Fin);
|
||
|
||
AgendaItem brunoJour2 = Assert.Single(result, i =>
|
||
i.CollaborateurNom == "Bruno Petit" && i.Debut.Date == Jour2);
|
||
Assert.Equal(Jour2.AddHours(13), brunoJour2.Debut);
|
||
Assert.Equal(Jour2.AddHours(17), brunoJour2.Fin);
|
||
|
||
// Toutes les cartes se rattachent au même rendez-vous (noyau).
|
||
Assert.All(result, item => Assert.Equal(rendezVousId, item.RendezVousGuid));
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Agenda_PorteeCollaborateur_NePresenteQueSesPropresCreneaux()
|
||
{
|
||
// Arrange : agenda personnel d'Alice (bootstrap local).
|
||
await PreparerGroupeAsync();
|
||
SessionService.Bootstrap = new LocalBootstrapState
|
||
{
|
||
Collaborateur = new Omika.Core.Contracts.Organisation.CollaborateurDetailsDto
|
||
{
|
||
Id = CollaborateurA.ToString("D"),
|
||
Nom = "Martin",
|
||
Prenom = "Alice"
|
||
}
|
||
};
|
||
|
||
// Act
|
||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForRangeAsync(Jour1, Jour2.AddDays(1));
|
||
|
||
// Assert : uniquement les deux créneaux d'Alice (un par jour).
|
||
Assert.Equal(2, result.Count);
|
||
Assert.All(result, item => Assert.Equal("Alice Martin", item.CollaborateurNom));
|
||
Assert.Equal([Jour1.AddHours(8), Jour2.AddHours(8)], result.Select(i => i.Debut).OrderBy(d => d).ToList());
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Fiche_AffectationPrecise_GardeLeCreneauEtLeCollaborateurDeLaCarte()
|
||
{
|
||
// Arrange : la fiche est ouverte depuis la carte « Bruno – 2e jour ».
|
||
(Guid rendezVousId, _, Guid affectationBJour2) = await PreparerGroupeAsync();
|
||
|
||
// Act
|
||
AgendaItem? item = await _service.GetRendezVousByIdAsync(
|
||
rendezVousId,
|
||
default,
|
||
affectationBJour2.ToString("D"));
|
||
|
||
// Assert : créneau et collaborateur de l'affectation ouverte.
|
||
Assert.NotNull(item);
|
||
Assert.Equal(Jour2.AddHours(13), item!.Debut);
|
||
Assert.Equal(Jour2.AddHours(17), item.Fin);
|
||
Assert.Equal("Bruno Petit", item.CollaborateurNom);
|
||
Assert.Equal(affectationBJour2.ToString("D"), item.AffectationId);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Fiche_Groupe_ExposeLesJoursEtTousLesCollaborateurs()
|
||
{
|
||
// Arrange
|
||
(Guid rendezVousId, Guid affectationAJour2, _) = await PreparerGroupeAsync();
|
||
|
||
// Act
|
||
AgendaItem? item = await _service.GetRendezVousByIdAsync(
|
||
rendezVousId,
|
||
default,
|
||
affectationAJour2.ToString("D"));
|
||
|
||
// Assert : les deux jours du rendez-vous et les deux collaborateurs sont
|
||
// exposés pour le bloc informatif de la fiche, avec pour chaque journée le
|
||
// créneau réellement occupé (du premier démarrage à la dernière fin).
|
||
Assert.NotNull(item);
|
||
Assert.Equal([Jour1, Jour2], item!.JoursDuGroupe);
|
||
Assert.Equal(2, item.JoursAvecHoraires.Count);
|
||
Assert.Equal(Jour1.AddHours(8), item.JoursAvecHoraires[0].Debut);
|
||
Assert.Equal(Jour1.AddHours(17), item.JoursAvecHoraires[0].Fin);
|
||
Assert.Equal(Jour2.AddHours(8), item.JoursAvecHoraires[1].Debut);
|
||
Assert.Equal(Jour2.AddHours(17), item.JoursAvecHoraires[1].Fin);
|
||
Assert.Equal(2, item.Collaborateurs.Count);
|
||
Assert.Contains("Alice Martin", item.Collaborateurs);
|
||
Assert.Contains("Bruno Petit", item.Collaborateurs);
|
||
Assert.True(item.AfficheBlocPeriode);
|
||
|
||
// Le collaborateur de l'affectation OUVERTE est identifié : c'est lui qui
|
||
// sera exclu du bloc (sa propre fiche) et dont les journées sont présentées.
|
||
Assert.Equal(CollaborateurA, item.CollaborateurOccurrenceGuid);
|
||
|
||
// Journées PAR collaborateur : Alice n'a que son créneau du matin, Bruno
|
||
// celui de l'après-midi — aucun des deux n'hérite des heures de l'autre.
|
||
Assert.Equal(2, item.JoursParCollaborateur.Count);
|
||
JoursParCollaborateur joursAlice = Assert.Single(
|
||
item.JoursParCollaborateur, j => j.CollaborateurGuid == CollaborateurA);
|
||
Assert.Equal([Jour1, Jour2], joursAlice.Jours.Select(j => j.Jour).ToList());
|
||
Assert.All(joursAlice.Jours, j =>
|
||
{
|
||
Assert.Equal(j.Jour.AddHours(8), j.Debut);
|
||
Assert.Equal(j.Jour.AddHours(12), j.Fin);
|
||
});
|
||
|
||
JoursParCollaborateur joursBruno = Assert.Single(
|
||
item.JoursParCollaborateur, j => j.CollaborateurGuid == CollaborateurB);
|
||
Assert.Equal([Jour1, Jour2], joursBruno.Jours.Select(j => j.Jour).ToList());
|
||
Assert.All(joursBruno.Jours, j =>
|
||
{
|
||
Assert.Equal(j.Jour.AddHours(13), j.Debut);
|
||
Assert.Equal(j.Jour.AddHours(17), j.Fin);
|
||
});
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Fiche_RendezVousSimple_NAffichePasLeBlocGroupe()
|
||
{
|
||
// Arrange : rendez-vous d'une journée, un seul collaborateur.
|
||
Guid rendezVousId = Guid.NewGuid();
|
||
Guid affectationId = Guid.NewGuid();
|
||
var dto = new RendezVousDetailsDto
|
||
{
|
||
Id = rendezVousId.ToString(),
|
||
Titre = "Visite",
|
||
Debut = Jour1.AddHours(9),
|
||
Fin = Jour1.AddHours(10),
|
||
Collaborateurs =
|
||
[
|
||
Affectation(affectationId, CollaborateurA, "Alice Martin", Jour1.AddHours(9), Jour1.AddHours(10))
|
||
]
|
||
};
|
||
|
||
await RendezVousRepository.UpsertAsync(new LocalRendezVous
|
||
{
|
||
Id = rendezVousId,
|
||
PayloadJson = JsonSerializer.Serialize(dto),
|
||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||
});
|
||
|
||
// Act
|
||
AgendaItem? item = await _service.GetRendezVousByIdAsync(rendezVousId, default, affectationId.ToString("D"));
|
||
|
||
// Assert : bloc informatif masqué (un jour, un collaborateur).
|
||
Assert.NotNull(item);
|
||
Assert.False(item!.AfficheBlocPeriode);
|
||
Assert.Single(item.JoursDuGroupe);
|
||
}
|
||
}
|