feat: ajouter les exports CSV individuels des equipements commandes pour le flocage (initiales et prenom/numero)
This commit is contained in:
@@ -42,6 +42,12 @@ public class Dotation {
|
|||||||
@Column(name = "date_commande")
|
@Column(name = "date_commande")
|
||||||
private java.time.LocalDateTime dateCommande;
|
private java.time.LocalDateTime dateCommande;
|
||||||
|
|
||||||
|
@Column(name = "flocage_exporte", nullable = false)
|
||||||
|
private Boolean flocageExporte = false;
|
||||||
|
|
||||||
|
@Column(name = "date_flocage")
|
||||||
|
private java.time.LocalDateTime dateFlocage;
|
||||||
|
|
||||||
// Getters and Setters
|
// Getters and Setters
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
@@ -77,6 +83,13 @@ public class Dotation {
|
|||||||
public java.time.LocalDateTime getDateCommande() { return dateCommande; }
|
public java.time.LocalDateTime getDateCommande() { return dateCommande; }
|
||||||
public void setDateCommande(java.time.LocalDateTime dateCommande) { this.dateCommande = dateCommande; }
|
public void setDateCommande(java.time.LocalDateTime dateCommande) { this.dateCommande = dateCommande; }
|
||||||
|
|
||||||
|
public Boolean getFlocageExporte() { return flocageExporte; }
|
||||||
|
public void setFlocageExporte(Boolean flocageExporte) { this.flocageExporte = flocageExporte; }
|
||||||
|
|
||||||
|
public java.time.LocalDateTime getDateFlocage() { return dateFlocage; }
|
||||||
|
public void setDateFlocage(java.time.LocalDateTime dateFlocage) { this.dateFlocage = dateFlocage; }
|
||||||
|
|
||||||
|
|
||||||
public static boolean isSizeMatch(String option, String adherentSize) {
|
public static boolean isSizeMatch(String option, String adherentSize) {
|
||||||
if (option == null || adherentSize == null) return false;
|
if (option == null || adherentSize == null) return false;
|
||||||
String opt = option.trim();
|
String opt = option.trim();
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ import com.astalange.core.entity.Saison;
|
|||||||
public interface DotationRepository extends JpaRepository<Dotation, Long>, JpaSpecificationExecutor<Dotation> {
|
public interface DotationRepository extends JpaRepository<Dotation, Long>, JpaSpecificationExecutor<Dotation> {
|
||||||
List<Dotation> findByLicence_SaisonAndChoisiTrueAndFourniFalse(Saison saison);
|
List<Dotation> findByLicence_SaisonAndChoisiTrueAndFourniFalse(Saison saison);
|
||||||
List<Dotation> findByLicence_SaisonAndChoisiTrueAndCommandeeFalse(Saison saison);
|
List<Dotation> findByLicence_SaisonAndChoisiTrueAndCommandeeFalse(Saison saison);
|
||||||
|
List<Dotation> findByLicence_SaisonAndChoisiTrueAndCommandeeTrueAndFlocageExporteFalse(Saison saison);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,106 @@ public class DotationService {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public String genererCsvFlocageInitiales(Saison saisonActive) {
|
||||||
|
List<Dotation> dotations = dotationRepository.findByLicence_SaisonAndChoisiTrueAndCommandeeTrueAndFlocageExporteFalse(saisonActive);
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
String dateExportFormatted = now.format(DATE_FORMATTER);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('\ufeff'); // BOM UTF-8 for Excel
|
||||||
|
sb.append("Saison;Catégorie;Nom;Prénom;Équipement;Référence;Taille;Initiales;Date Export Flocage\n");
|
||||||
|
|
||||||
|
for (Dotation d : dotations) {
|
||||||
|
if (d.getEquipement() == null || !d.getEquipement().isHasFlocageInitiales()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String initiales = d.getFlocage() != null ? d.getFlocage().trim() : "";
|
||||||
|
if (initiales.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String saison = d.getLicence().getSaison() != null ? d.getLicence().getSaison().getNom() : "";
|
||||||
|
String categorie = d.getLicence().getCategorie() != null ? d.getLicence().getCategorie().getNom() : "";
|
||||||
|
String nom = d.getLicence().getAdherent() != null ? d.getLicence().getAdherent().getNom() : "";
|
||||||
|
String prenom = d.getLicence().getAdherent() != null ? d.getLicence().getAdherent().getPrenom() : "";
|
||||||
|
String equipement = d.getEquipement().getNom();
|
||||||
|
String reference = d.getEquipement().getReference() != null ? d.getEquipement().getReference() : "";
|
||||||
|
String taille = d.getTaille() != null ? d.getTaille() : "";
|
||||||
|
|
||||||
|
sb.append(escapeCsv(saison)).append(";")
|
||||||
|
.append(escapeCsv(categorie)).append(";")
|
||||||
|
.append(escapeCsv(nom)).append(";")
|
||||||
|
.append(escapeCsv(prenom)).append(";")
|
||||||
|
.append(escapeCsv(equipement)).append(";")
|
||||||
|
.append(escapeCsv(reference)).append(";")
|
||||||
|
.append(escapeCsv(taille)).append(";")
|
||||||
|
.append(escapeCsv(initiales)).append(";")
|
||||||
|
.append(escapeCsv(dateExportFormatted)).append("\n");
|
||||||
|
|
||||||
|
d.setFlocageExporte(true);
|
||||||
|
d.setDateFlocage(now);
|
||||||
|
dotationRepository.save(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public String genererCsvFlocagePrenomNumero(Saison saisonActive) {
|
||||||
|
List<Dotation> dotations = dotationRepository.findByLicence_SaisonAndChoisiTrueAndCommandeeTrueAndFlocageExporteFalse(saisonActive);
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
String dateExportFormatted = now.format(DATE_FORMATTER);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('\ufeff'); // BOM UTF-8 for Excel
|
||||||
|
sb.append("Saison;Catégorie;Nom;Prénom;Équipement;Référence;Taille;Prénom Floqué;Numéro;Date Export Flocage\n");
|
||||||
|
|
||||||
|
for (Dotation d : dotations) {
|
||||||
|
if (d.getEquipement() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean handlesPrenom = d.getEquipement().isHasFlocagePrenom();
|
||||||
|
boolean handlesNumero = d.getEquipement().isHasFlocageNumero();
|
||||||
|
|
||||||
|
if (!handlesPrenom && !handlesNumero) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String prenomFloque = d.getFlocage() != null ? d.getFlocage().trim() : "";
|
||||||
|
String numero = d.getNumero() != null ? d.getNumero().trim() : "";
|
||||||
|
|
||||||
|
if (prenomFloque.isEmpty() && numero.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String saison = d.getLicence().getSaison() != null ? d.getLicence().getSaison().getNom() : "";
|
||||||
|
String categorie = d.getLicence().getCategorie() != null ? d.getLicence().getCategorie().getNom() : "";
|
||||||
|
String nom = d.getLicence().getAdherent() != null ? d.getLicence().getAdherent().getNom() : "";
|
||||||
|
String prenom = d.getLicence().getAdherent() != null ? d.getLicence().getAdherent().getPrenom() : "";
|
||||||
|
String equipement = d.getEquipement().getNom();
|
||||||
|
String reference = d.getEquipement().getReference() != null ? d.getEquipement().getReference() : "";
|
||||||
|
String taille = d.getTaille() != null ? d.getTaille() : "";
|
||||||
|
|
||||||
|
sb.append(escapeCsv(saison)).append(";")
|
||||||
|
.append(escapeCsv(categorie)).append(";")
|
||||||
|
.append(escapeCsv(nom)).append(";")
|
||||||
|
.append(escapeCsv(prenom)).append(";")
|
||||||
|
.append(escapeCsv(equipement)).append(";")
|
||||||
|
.append(escapeCsv(reference)).append(";")
|
||||||
|
.append(escapeCsv(taille)).append(";")
|
||||||
|
.append(escapeCsv(prenomFloque)).append(";")
|
||||||
|
.append(escapeCsv(numero)).append(";")
|
||||||
|
.append(escapeCsv(dateExportFormatted)).append("\n");
|
||||||
|
|
||||||
|
d.setFlocageExporte(true);
|
||||||
|
d.setDateFlocage(now);
|
||||||
|
dotationRepository.save(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private String escapeCsv(String value) {
|
private String escapeCsv(String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return "";
|
return "";
|
||||||
@@ -130,3 +230,4 @@ public class DotationService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE dotation ADD COLUMN flocage_exporte BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
|
ALTER TABLE dotation ADD COLUMN date_flocage TIMESTAMP NULL;
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.astalange.core.service;
|
||||||
|
|
||||||
|
import com.astalange.core.entity.*;
|
||||||
|
import com.astalange.core.repository.DotationRepository;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class DotationServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private DotationRepository dotationRepository;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private DotationService dotationService;
|
||||||
|
|
||||||
|
private Saison saison;
|
||||||
|
private Categorie categorie;
|
||||||
|
private Adherent adherent;
|
||||||
|
private Licence licence;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
saison = new Saison();
|
||||||
|
saison.setId(1L);
|
||||||
|
saison.setNom("2026-2027");
|
||||||
|
|
||||||
|
categorie = new Categorie();
|
||||||
|
categorie.setId(10L);
|
||||||
|
categorie.setNom("U15");
|
||||||
|
|
||||||
|
adherent = new Adherent();
|
||||||
|
adherent.setId(100L);
|
||||||
|
adherent.setNom("Dupont");
|
||||||
|
adherent.setPrenom("Jean");
|
||||||
|
|
||||||
|
licence = new Licence();
|
||||||
|
licence.setId(500L);
|
||||||
|
licence.setSaison(saison);
|
||||||
|
licence.setCategorie(categorie);
|
||||||
|
licence.setAdherent(adherent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Générer CSV Flocage Initiales - Uniquement équipements avec initiales et commandés")
|
||||||
|
public void testGenererCsvFlocageInitiales() {
|
||||||
|
Equipement eqVeste = new Equipement();
|
||||||
|
eqVeste.setId(1L);
|
||||||
|
eqVeste.setNom("Veste de sortie");
|
||||||
|
eqVeste.setReference("REF-VESTE");
|
||||||
|
eqVeste.setHasFlocageInitiales(true);
|
||||||
|
|
||||||
|
Dotation d1 = new Dotation();
|
||||||
|
d1.setId(1L);
|
||||||
|
d1.setLicence(licence);
|
||||||
|
d1.setEquipement(eqVeste);
|
||||||
|
d1.setTaille("M");
|
||||||
|
d1.setFlocage("JD");
|
||||||
|
d1.setChoisi(true);
|
||||||
|
d1.setCommandee(true);
|
||||||
|
d1.setFlocageExporte(false);
|
||||||
|
|
||||||
|
when(dotationRepository.findByLicence_SaisonAndChoisiTrueAndCommandeeTrueAndFlocageExporteFalse(saison))
|
||||||
|
.thenReturn(List.of(d1));
|
||||||
|
|
||||||
|
String csv = dotationService.genererCsvFlocageInitiales(saison);
|
||||||
|
|
||||||
|
assertNotNull(csv);
|
||||||
|
assertTrue(csv.contains("JD"));
|
||||||
|
assertTrue(csv.contains("Veste de sortie"));
|
||||||
|
assertTrue(csv.contains("Dupont"));
|
||||||
|
assertTrue(d1.getFlocageExporte());
|
||||||
|
assertNotNull(d1.getDateFlocage());
|
||||||
|
verify(dotationRepository, times(1)).save(d1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Générer CSV Flocage Prénom / Numéro - Uniquement équipements commandés avec prénom ou numéro")
|
||||||
|
public void testGenererCsvFlocagePrenomNumero() {
|
||||||
|
Equipement eqMaillot = new Equipement();
|
||||||
|
eqMaillot.setId(2L);
|
||||||
|
eqMaillot.setNom("Maillot de match");
|
||||||
|
eqMaillot.setReference("REF-MAILLOT");
|
||||||
|
eqMaillot.setHasFlocagePrenom(true);
|
||||||
|
eqMaillot.setHasFlocageNumero(true);
|
||||||
|
|
||||||
|
Dotation d2 = new Dotation();
|
||||||
|
d2.setId(2L);
|
||||||
|
d2.setLicence(licence);
|
||||||
|
d2.setEquipement(eqMaillot);
|
||||||
|
d2.setTaille("L");
|
||||||
|
d2.setFlocage("JEAN");
|
||||||
|
d2.setNumero("10");
|
||||||
|
d2.setChoisi(true);
|
||||||
|
d2.setCommandee(true);
|
||||||
|
d2.setFlocageExporte(false);
|
||||||
|
|
||||||
|
when(dotationRepository.findByLicence_SaisonAndChoisiTrueAndCommandeeTrueAndFlocageExporteFalse(saison))
|
||||||
|
.thenReturn(List.of(d2));
|
||||||
|
|
||||||
|
String csv = dotationService.genererCsvFlocagePrenomNumero(saison);
|
||||||
|
|
||||||
|
assertNotNull(csv);
|
||||||
|
assertTrue(csv.contains("JEAN"));
|
||||||
|
assertTrue(csv.contains("10"));
|
||||||
|
assertTrue(csv.contains("Maillot de match"));
|
||||||
|
assertTrue(d2.getFlocageExporte());
|
||||||
|
assertNotNull(d2.getDateFlocage());
|
||||||
|
verify(dotationRepository, times(1)).save(d2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -149,6 +149,37 @@ public class DotationController {
|
|||||||
return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK);
|
return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/admin/equipements/export-flocage-initiales")
|
||||||
|
public ResponseEntity<byte[]> exportFlocageInitiales() {
|
||||||
|
Saison saisonActive = saisonRepository.findByEstActiveTrue()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Aucune saison active"));
|
||||||
|
|
||||||
|
String csvContent = dotationService.genererCsvFlocageInitiales(saisonActive);
|
||||||
|
byte[] csvBytes = csvContent.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentDispositionFormData("attachment", "flocage_initiales_" + LocalDate.now() + ".csv");
|
||||||
|
headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
|
||||||
|
|
||||||
|
return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/admin/equipements/export-flocage-prenom-numero")
|
||||||
|
public ResponseEntity<byte[]> exportFlocagePrenomNumero() {
|
||||||
|
Saison saisonActive = saisonRepository.findByEstActiveTrue()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Aucune saison active"));
|
||||||
|
|
||||||
|
String csvContent = dotationService.genererCsvFlocagePrenomNumero(saisonActive);
|
||||||
|
byte[] csvBytes = csvContent.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentDispositionFormData("attachment", "flocage_prenom_numero_" + LocalDate.now() + ".csv");
|
||||||
|
headers.setContentType(MediaType.parseMediaType("text/csv; charset=UTF-8"));
|
||||||
|
|
||||||
|
return new ResponseEntity<>(csvBytes, headers, org.springframework.http.HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/dotations/{id}")
|
@PostMapping("/dotations/{id}")
|
||||||
public String updateDotation(
|
public String updateDotation(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
|
|||||||
@@ -53,20 +53,41 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center pt-2">
|
<div class="flex flex-wrap items-center justify-between gap-3 pt-2">
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<a th:href="@{/admin/equipements/export-commande}"
|
<a th:href="@{/admin/equipements/export-commande}"
|
||||||
onclick="return confirm('Exporter les équipements non encore commandés et les marquer comme commandés ?');"
|
onclick="return confirm('Exporter les équipements non encore commandés et les marquer comme commandés ?');"
|
||||||
class="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700 flex items-center space-x-1 shadow-sm">
|
class="bg-indigo-600 text-white px-3.5 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700 flex items-center space-x-1 shadow-sm transition-colors">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Exporter Commande Équipementier (Regroupé)
|
Exporter Commande Équipementier (Regroupé)
|
||||||
</a>
|
</a>
|
||||||
|
<a th:href="@{/admin/equipements/export-flocage-initiales}"
|
||||||
|
onclick="return confirm('Exporter la liste des équipements commandés pour le flocage des initiales et les marquer comme exportés ?');"
|
||||||
|
class="bg-purple-600 text-white px-3.5 py-2 rounded-lg text-sm font-medium hover:bg-purple-700 flex items-center space-x-1 shadow-sm transition-colors"
|
||||||
|
title="Exporter les articles commandés nécessitant le flocage des initiales">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h10M7 12h10M7 17h10"/>
|
||||||
|
</svg>
|
||||||
|
Exporter Flocage Initiales
|
||||||
|
</a>
|
||||||
|
<a th:href="@{/admin/equipements/export-flocage-prenom-numero}"
|
||||||
|
onclick="return confirm('Exporter la liste des équipements commandés pour le flocage du prénom/numéro et les marquer comme exportés ?');"
|
||||||
|
class="bg-teal-600 text-white px-3.5 py-2 rounded-lg text-sm font-medium hover:bg-teal-700 flex items-center space-x-1 shadow-sm transition-colors"
|
||||||
|
title="Exporter les articles commandés nécessitant le flocage prénom et/ou numéro">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||||
|
</svg>
|
||||||
|
Exporter Flocage Prénom / N°
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700">Rechercher</button>
|
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700">Rechercher</button>
|
||||||
<a th:href="@{/admin/equipements/recherche/export(equipementId=${equipementId},categorieId=${categorieId},fourni=${fourni},commandee=${commandee})}" class="bg-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-green-700">Exporter Recherche</a>
|
<a th:href="@{/admin/equipements/recherche/export(equipementId=${equipementId},categorieId=${categorieId},fourni=${fourni},commandee=${commandee})}" class="bg-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-green-700">Exporter Recherche</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user