entry : audit.getCategoryScores().entrySet()) {
+ String category = getCategoryDisplayName(entry.getKey());
+ Integer score = entry.getValue();
+ // Calcul du pourcentage (approximatif)
+ double percentage = (double) score / 100 * 100; // À ajuster selon le scoring réel
+ String level = getMaturityLevel(percentage);
+
+ addTableRow(table, category, score.toString() + " - " + level);
+ }
+
+ document.add(table);
+ }
+
+ private void addRecommendations(Document document, AuditResponse audit) throws DocumentException {
+ Paragraph section = new Paragraph("RECOMMANDATIONS PRIORITAIRES", HEADER_FONT);
+ section.setSpacingBefore(15);
+ section.setSpacingAfter(10);
+ document.add(section);
+
+ Paragraph recommendations = new Paragraph(audit.getRecommendations(), NORMAL_FONT);
+ recommendations.setSpacingAfter(10);
+ document.add(recommendations);
+
+ if (audit.getPriorityActions() != null && !audit.getPriorityActions().isEmpty()) {
+ Paragraph actions = new Paragraph("Actions prioritaires: " + audit.getPriorityActions(), NORMAL_FONT);
+ actions.setSpacingAfter(15);
+ document.add(actions);
+ }
+ }
+
+ private void addBudgetEstimation(Document document, AuditResponse audit) throws DocumentException {
+ if (audit.getEstimatedBudgetMin() != null && audit.getEstimatedBudgetMax() != null) {
+ Paragraph section = new Paragraph("ESTIMATION BUDGÉTAIRE", HEADER_FONT);
+ section.setSpacingBefore(15);
+ section.setSpacingAfter(10);
+ document.add(section);
+
+ String budgetText = String.format("Investissement estimé pour la digitalisation: %,.0f - %,.0f FCFA",
+ audit.getEstimatedBudgetMin(), audit.getEstimatedBudgetMax());
+ Paragraph budget = new Paragraph(budgetText, NORMAL_FONT);
+ budget.setSpacingAfter(10);
+ document.add(budget);
+
+ Paragraph note = new Paragraph("* Estimation basée sur votre niveau de maturité actuel. " +
+ "Un devis personnalisé sera établi après analyse détaillée.", SMALL_FONT);
+ note.setSpacingAfter(15);
+ document.add(note);
+ }
+ }
+
+ private void addNextSteps(Document document) throws DocumentException {
+ Paragraph section = new Paragraph("PROCHAINES ÉTAPES", HEADER_FONT);
+ section.setSpacingBefore(15);
+ section.setSpacingAfter(10);
+ document.add(section);
+
+ List list = new List(List.ORDERED);
+ list.add(new ListItem("Rendez-vous diagnostic approfondi (gratuit)", NORMAL_FONT));
+ list.add(new ListItem("Analyse détaillée de vos processus métier", NORMAL_FONT));
+ list.add(new ListItem("Proposition de solution personnalisée", NORMAL_FONT));
+ list.add(new ListItem("Planification du déploiement", NORMAL_FONT));
+ list.add(new ListItem("Formation de vos équipes", NORMAL_FONT));
+
+ document.add(list);
+ }
+
+ private void addFooter(Document document) throws DocumentException {
+ Paragraph footer = new Paragraph("\nLions Dev - Solutions Digitales Innovantes\n" +
+ "Abidjan, Côte d'Ivoire | +225 01 01 75 95 25 | contact@lions.dev", SMALL_FONT);
+ footer.setAlignment(Element.ALIGN_CENTER);
+ footer.setSpacingBefore(20);
+ document.add(footer);
+ }
+
+ /**
+ * Envoie le rapport par email
+ */
+ public void sendAuditReportByEmail(AuditResponse audit, byte[] pdfReport) {
+ try {
+ Mail mail = Mail.withHtml(audit.getEmail(),
+ "Votre Audit de Maturité Digitale - Lions Dev",
+ generateEmailContent(audit))
+ .addAttachment("audit-" + audit.getCompanyName() + ".pdf",
+ pdfReport, "application/pdf");
+
+ mailer.send(mail);
+ } catch (Exception e) {
+ // Log l'erreur mais ne fait pas échouer le processus
+ System.err.println("Erreur envoi email: " + e.getMessage());
+ }
+ }
+
+ private String generateEmailContent(AuditResponse audit) {
+ return String.format("""
+ Bonjour %s,
+
+ Merci d'avoir réalisé l'audit de maturité digitale avec Lions Dev.
+
+ Votre score global: %.1f%%
+
+ Vous trouverez en pièce jointe votre rapport détaillé avec nos recommandations personnalisées.
+
+ Notre équipe vous contactera dans les 24h pour planifier un rendez-vous diagnostic gratuit.
+
+ Cordialement,
+ L'équipe Lions Dev
+ +225 01 01 75 95 25
+ """, audit.getContactName(), audit.getMaturityPercentage());
+ }
+
+ // Méthodes utilitaires
+ private void addTableRow(PdfPTable table, String label, String value) {
+ table.addCell(new PdfPCell(new Phrase(label, NORMAL_FONT)));
+ table.addCell(new PdfPCell(new Phrase(value != null ? value : "-", NORMAL_FONT)));
+ }
+
+ private void addTableHeader(PdfPTable table, String header) {
+ PdfPCell cell = new PdfPCell(new Phrase(header, HEADER_FONT));
+ cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
+ table.addCell(cell);
+ }
+
+ private String getMaturityLevel(double percentage) {
+ if (percentage < 30) return "Débutant";
+ if (percentage < 60) return "Intermédiaire";
+ if (percentage < 80) return "Avancé";
+ return "Expert";
+ }
+
+ private BaseColor getScoreColor(double percentage) {
+ if (percentage < 30) return BaseColor.RED;
+ if (percentage < 60) return BaseColor.ORANGE;
+ if (percentage < 80) return BaseColor.BLUE;
+ return BaseColor.GREEN;
+ }
+
+ private String getCategoryDisplayName(String category) {
+ return switch (category) {
+ case "commercial" -> "Gestion Commerciale";
+ case "stock" -> "Gestion des Stocks";
+ case "comptabilite" -> "Comptabilité";
+ case "rh" -> "Ressources Humaines";
+ case "infrastructure" -> "Infrastructure IT";
+ default -> category;
+ };
+ }
+
+ private void addCategoryChart(Document document, AuditResponse audit) throws DocumentException {
+ // Graphique simple en texte (pour une vraie implémentation, utiliser JFreeChart)
+ Paragraph chart = new Paragraph("Répartition des scores par domaine:", NORMAL_FONT);
+ chart.setSpacingAfter(5);
+ document.add(chart);
+
+ for (Map.Entry entry : audit.getCategoryScores().entrySet()) {
+ String category = getCategoryDisplayName(entry.getKey());
+ Integer score = entry.getValue();
+ String bar = "█".repeat(Math.max(1, score / 10)) + " " + score + "%";
+
+ Paragraph barChart = new Paragraph(category + ": " + bar, SMALL_FONT);
+ document.add(barChart);
+ }
+
+ document.add(Chunk.NEWLINE);
+ }
+}
diff --git a/src/main/java/dev/lions/audit/AuditResource.java b/src/main/java/dev/lions/audit/AuditResource.java
new file mode 100644
index 0000000..2204ab3
--- /dev/null
+++ b/src/main/java/dev/lions/audit/AuditResource.java
@@ -0,0 +1,198 @@
+package dev.lions.audit;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * API REST pour l'outil d'audit de maturité digitale
+ */
+@Path("/api/audit")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class AuditResource {
+
+ @Inject
+ AuditService auditService;
+
+ @Inject
+ AuditReportService reportService;
+
+ /**
+ * Récupère toutes les questions d'audit par catégorie
+ */
+ @GET
+ @Path("/questions")
+ public Response getQuestions(@QueryParam("lang") @DefaultValue("fr") String language) {
+ try {
+ Map> questions = auditService.getQuestionsByCategory();
+ return Response.ok(questions).build();
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("error", "Erreur lors du chargement des questions"))
+ .build();
+ }
+ }
+
+ /**
+ * Soumet les réponses d'audit et génère le rapport
+ */
+ @POST
+ @Path("/submit")
+ public Response submitAudit(AuditSubmissionDTO submission) {
+ try {
+ // Validation des données
+ if (submission.getCompanyName() == null || submission.getCompanyName().trim().isEmpty()) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(Map.of("error", "Le nom de l'entreprise est requis"))
+ .build();
+ }
+
+ if (submission.getEmail() == null || !isValidEmail(submission.getEmail())) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(Map.of("error", "Email valide requis"))
+ .build();
+ }
+
+ // Création de la réponse d'audit
+ AuditResponse response = new AuditResponse();
+ response.setCompanyName(submission.getCompanyName());
+ response.setContactName(submission.getContactName());
+ response.setEmail(submission.getEmail());
+ response.setPhone(submission.getPhone());
+ response.setSector(submission.getSector());
+ response.setEmployeeCount(submission.getEmployeeCount());
+ response.setTurnover(submission.getTurnover());
+ response.setAnswers(submission.getAnswers());
+
+ // Traitement de l'audit
+ AuditResponse processedResponse = auditService.processAuditResponse(response);
+
+ // Génération du rapport PDF
+ byte[] pdfReport = reportService.generateAuditReport(processedResponse);
+
+ // Envoi par email
+ reportService.sendAuditReportByEmail(processedResponse, pdfReport);
+
+ // Réponse avec résumé
+ AuditResultDTO result = new AuditResultDTO();
+ result.setAuditId(processedResponse.getId());
+ result.setMaturityPercentage(processedResponse.getMaturityPercentage());
+ result.setCategoryScores(processedResponse.getCategoryScores());
+ result.setRecommendations(processedResponse.getRecommendations());
+ result.setPriorityActions(processedResponse.getPriorityActions());
+ result.setEstimatedBudgetMin(processedResponse.getEstimatedBudgetMin());
+ result.setEstimatedBudgetMax(processedResponse.getEstimatedBudgetMax());
+
+ return Response.ok(result).build();
+
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("error", "Erreur lors du traitement de l'audit: " + e.getMessage()))
+ .build();
+ }
+ }
+
+ /**
+ * Télécharge le rapport PDF d'un audit
+ */
+ @GET
+ @Path("/report/{auditId}")
+ @Produces("application/pdf")
+ public Response downloadReport(@PathParam("auditId") Long auditId) {
+ try {
+ AuditResponse audit = auditService.getAuditById(auditId);
+ if (audit == null) {
+ return Response.status(Response.Status.NOT_FOUND)
+ .entity("Audit non trouvé")
+ .build();
+ }
+
+ byte[] pdfReport = reportService.generateAuditReport(audit);
+
+ return Response.ok(pdfReport)
+ .header("Content-Disposition",
+ "attachment; filename=\"audit-" + audit.getCompanyName() + ".pdf\"")
+ .build();
+
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity("Erreur lors de la génération du rapport")
+ .build();
+ }
+ }
+
+ /**
+ * Récupère les statistiques d'audit pour le dashboard admin
+ */
+ @GET
+ @Path("/stats")
+ public Response getAuditStats() {
+ try {
+ Map stats = auditService.getAuditStatistics();
+ return Response.ok(stats).build();
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("error", "Erreur lors du chargement des statistiques"))
+ .build();
+ }
+ }
+
+ /**
+ * Récupère les audits non contactés (pour équipe commerciale)
+ */
+ @GET
+ @Path("/leads")
+ public Response getUncontactedLeads() {
+ try {
+ List leads = auditService.getUncontactedAudits();
+ return Response.ok(leads).build();
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("error", "Erreur lors du chargement des leads"))
+ .build();
+ }
+ }
+
+ /**
+ * Marque un audit comme contacté
+ */
+ @PUT
+ @Path("/contact/{auditId}")
+ public Response markAsContacted(@PathParam("auditId") Long auditId,
+ Map notes) {
+ try {
+ auditService.markAsContacted(auditId, notes.get("notes"));
+ return Response.ok(Map.of("success", true)).build();
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("error", "Erreur lors de la mise à jour"))
+ .build();
+ }
+ }
+
+ /**
+ * Demande de rendez-vous après audit
+ */
+ @POST
+ @Path("/request-meeting")
+ public Response requestMeeting(MeetingRequestDTO request) {
+ try {
+ auditService.processMeetingRequest(request);
+ return Response.ok(Map.of("success", true,
+ "message", "Demande de rendez-vous enregistrée"))
+ .build();
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+ .entity(Map.of("error", "Erreur lors de l'enregistrement"))
+ .build();
+ }
+ }
+
+ private boolean isValidEmail(String email) {
+ return email != null && email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
+ }
+}
diff --git a/src/main/java/dev/lions/audit/AuditResponse.java b/src/main/java/dev/lions/audit/AuditResponse.java
new file mode 100644
index 0000000..980835b
--- /dev/null
+++ b/src/main/java/dev/lions/audit/AuditResponse.java
@@ -0,0 +1,145 @@
+package dev.lions.audit;
+
+import jakarta.persistence.*;
+import java.time.LocalDateTime;
+import java.util.Map;
+
+/**
+ * Réponse d'audit d'une PME avec scoring et recommandations
+ */
+@Entity
+@Table(name = "audit_responses")
+public class AuditResponse {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ // Informations entreprise
+ @Column(nullable = false)
+ private String companyName;
+
+ @Column(nullable = false)
+ private String contactName;
+
+ @Column(nullable = false)
+ private String email;
+
+ private String phone;
+ private String sector; // Secteur d'activité
+ private Integer employeeCount;
+ private String turnover; // Chiffre d'affaires
+
+ // Audit
+ @ElementCollection
+ @CollectionTable(name = "audit_answers")
+ @MapKeyColumn(name = "question_id")
+ @Column(name = "answer_index")
+ private Map answers; // questionId -> index de réponse
+
+ @Column(nullable = false)
+ private LocalDateTime submittedAt;
+
+ // Scoring
+ private Integer totalScore;
+ private Integer maxPossibleScore;
+ private Double maturityPercentage;
+
+ @ElementCollection
+ @CollectionTable(name = "audit_category_scores")
+ @MapKeyColumn(name = "category")
+ @Column(name = "score")
+ private Map categoryScores;
+
+ // Recommandations
+ @Column(length = 2000)
+ private String recommendations;
+
+ @Column(length = 1000)
+ private String priorityActions;
+
+ // Estimation budgétaire
+ private Double estimatedBudgetMin;
+ private Double estimatedBudgetMax;
+
+ // Suivi commercial
+ private Boolean contacted = false;
+ private LocalDateTime contactedAt;
+ private String salesNotes;
+
+ // Constructeurs
+ public AuditResponse() {
+ this.submittedAt = LocalDateTime.now();
+ }
+
+ public AuditResponse(String companyName, String contactName, String email) {
+ this();
+ this.companyName = companyName;
+ this.contactName = contactName;
+ this.email = email;
+ }
+
+ // Getters et Setters
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+
+ public String getCompanyName() { return companyName; }
+ public void setCompanyName(String companyName) { this.companyName = companyName; }
+
+ public String getContactName() { return contactName; }
+ public void setContactName(String contactName) { this.contactName = contactName; }
+
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+
+ public String getPhone() { return phone; }
+ public void setPhone(String phone) { this.phone = phone; }
+
+ public String getSector() { return sector; }
+ public void setSector(String sector) { this.sector = sector; }
+
+ public Integer getEmployeeCount() { return employeeCount; }
+ public void setEmployeeCount(Integer employeeCount) { this.employeeCount = employeeCount; }
+
+ public String getTurnover() { return turnover; }
+ public void setTurnover(String turnover) { this.turnover = turnover; }
+
+ public Map getAnswers() { return answers; }
+ public void setAnswers(Map answers) { this.answers = answers; }
+
+ public LocalDateTime getSubmittedAt() { return submittedAt; }
+ public void setSubmittedAt(LocalDateTime submittedAt) { this.submittedAt = submittedAt; }
+
+ public Integer getTotalScore() { return totalScore; }
+ public void setTotalScore(Integer totalScore) { this.totalScore = totalScore; }
+
+ public Integer getMaxPossibleScore() { return maxPossibleScore; }
+ public void setMaxPossibleScore(Integer maxPossibleScore) { this.maxPossibleScore = maxPossibleScore; }
+
+ public Double getMaturityPercentage() { return maturityPercentage; }
+ public void setMaturityPercentage(Double maturityPercentage) { this.maturityPercentage = maturityPercentage; }
+
+ public Map getCategoryScores() { return categoryScores; }
+ public void setCategoryScores(Map categoryScores) { this.categoryScores = categoryScores; }
+
+ public String getRecommendations() { return recommendations; }
+ public void setRecommendations(String recommendations) { this.recommendations = recommendations; }
+
+ public String getPriorityActions() { return priorityActions; }
+ public void setPriorityActions(String priorityActions) { this.priorityActions = priorityActions; }
+
+ public Double getEstimatedBudgetMin() { return estimatedBudgetMin; }
+ public void setEstimatedBudgetMin(Double estimatedBudgetMin) { this.estimatedBudgetMin = estimatedBudgetMin; }
+
+ public Double getEstimatedBudgetMax() { return estimatedBudgetMax; }
+ public void setEstimatedBudgetMax(Double estimatedBudgetMax) { this.estimatedBudgetMax = estimatedBudgetMax; }
+
+ public Boolean getContacted() { return contacted; }
+ public void setContacted(Boolean contacted) { this.contacted = contacted; }
+
+ public LocalDateTime getContactedAt() { return contactedAt; }
+ public void setContactedAt(LocalDateTime contactedAt) { this.contactedAt = contactedAt; }
+
+ public String getSalesNotes() { return salesNotes; }
+ public void setSalesNotes(String salesNotes) { this.salesNotes = salesNotes; }
+}
diff --git a/src/main/java/dev/lions/audit/AuditService.java b/src/main/java/dev/lions/audit/AuditService.java
new file mode 100644
index 0000000..56d1167
--- /dev/null
+++ b/src/main/java/dev/lions/audit/AuditService.java
@@ -0,0 +1,247 @@
+package dev.lions.audit;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.EntityManager;
+import jakarta.transaction.Transactional;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Service de gestion des audits de maturité digitale
+ */
+@ApplicationScoped
+public class AuditService {
+
+ @Inject
+ EntityManager em;
+
+ /**
+ * Récupère toutes les questions d'audit actives par catégorie
+ */
+ public Map> getQuestionsByCategory() {
+ List questions = em.createQuery(
+ "SELECT q FROM AuditQuestion q WHERE q.active = true ORDER BY q.displayOrder",
+ AuditQuestion.class
+ ).getResultList();
+
+ return questions.stream()
+ .collect(Collectors.groupingBy(AuditQuestion::getCategory));
+ }
+
+ /**
+ * Calcule le score d'un audit et génère les recommandations
+ */
+ @Transactional
+ public AuditResponse processAuditResponse(AuditResponse response) {
+ Map> questionsByCategory = getQuestionsByCategory();
+ Map categoryScores = new HashMap<>();
+ Map categoryMaxScores = new HashMap<>();
+
+ int totalScore = 0;
+ int maxPossibleScore = 0;
+
+ // Calcul des scores par catégorie
+ for (Map.Entry> entry : questionsByCategory.entrySet()) {
+ String category = entry.getKey();
+ List questions = entry.getValue();
+
+ int categoryScore = 0;
+ int categoryMaxScore = 0;
+
+ for (AuditQuestion question : questions) {
+ Integer answerIndex = response.getAnswers().get(question.getId());
+ if (answerIndex != null && answerIndex < question.getScores().size()) {
+ int questionScore = question.getScores().get(answerIndex) * question.getWeight();
+ categoryScore += questionScore;
+ totalScore += questionScore;
+ }
+
+ int maxQuestionScore = Collections.max(question.getScores()) * question.getWeight();
+ categoryMaxScore += maxQuestionScore;
+ maxPossibleScore += maxQuestionScore;
+ }
+
+ categoryScores.put(category, categoryScore);
+ categoryMaxScores.put(category, categoryMaxScore);
+ }
+
+ // Mise à jour des scores
+ response.setTotalScore(totalScore);
+ response.setMaxPossibleScore(maxPossibleScore);
+ response.setMaturityPercentage((double) totalScore / maxPossibleScore * 100);
+ response.setCategoryScores(categoryScores);
+
+ // Génération des recommandations
+ generateRecommendations(response, categoryScores, categoryMaxScores);
+
+ // Estimation budgétaire
+ estimateBudget(response, categoryScores);
+
+ // Sauvegarde
+ em.persist(response);
+
+ return response;
+ }
+
+ /**
+ * Génère les recommandations personnalisées
+ */
+ private void generateRecommendations(AuditResponse response,
+ Map categoryScores,
+ Map categoryMaxScores) {
+
+ StringBuilder recommendations = new StringBuilder();
+ List priorities = new ArrayList<>();
+
+ // Analyse par catégorie
+ for (Map.Entry entry : categoryScores.entrySet()) {
+ String category = entry.getKey();
+ int score = entry.getValue();
+ int maxScore = categoryMaxScores.get(category);
+ double percentage = (double) score / maxScore * 100;
+
+ String categoryName = getCategoryDisplayName(category);
+
+ if (percentage < 30) {
+ recommendations.append("🚨 ").append(categoryName).append(" : Niveau critique - Digitalisation urgente nécessaire\n");
+ priorities.add("Digitaliser " + categoryName.toLowerCase());
+ } else if (percentage < 60) {
+ recommendations.append("⚠️ ").append(categoryName).append(" : Niveau faible - Améliorations importantes recommandées\n");
+ priorities.add("Améliorer " + categoryName.toLowerCase());
+ } else if (percentage < 80) {
+ recommendations.append("✅ ").append(categoryName).append(" : Niveau correct - Optimisations possibles\n");
+ } else {
+ recommendations.append("🏆 ").append(categoryName).append(" : Excellent niveau de digitalisation\n");
+ }
+ }
+
+ response.setRecommendations(recommendations.toString());
+ response.setPriorityActions(String.join(", ", priorities));
+ }
+
+ /**
+ * Estime le budget nécessaire selon les scores
+ */
+ private void estimateBudget(AuditResponse response, Map categoryScores) {
+ double budgetMin = 0;
+ double budgetMax = 0;
+
+ // Tarification par module selon le niveau de maturité
+ Map modulePricing = Map.of(
+ "commercial", new Double[]{150000.0, 300000.0}, // CRM
+ "stock", new Double[]{100000.0, 250000.0}, // Gestion stocks
+ "comptabilite", new Double[]{200000.0, 400000.0}, // Comptabilité
+ "rh", new Double[]{80000.0, 200000.0}, // RH
+ "infrastructure", new Double[]{100000.0, 300000.0} // IT
+ );
+
+ for (Map.Entry entry : categoryScores.entrySet()) {
+ String category = entry.getKey();
+ int score = entry.getValue();
+
+ if (score < 50 && modulePricing.containsKey(category)) { // Besoin d'amélioration
+ Double[] pricing = modulePricing.get(category);
+ budgetMin += pricing[0];
+ budgetMax += pricing[1];
+ }
+ }
+
+ response.setEstimatedBudgetMin(budgetMin);
+ response.setEstimatedBudgetMax(budgetMax);
+ }
+
+ /**
+ * Récupère les audits non contactés pour le suivi commercial
+ */
+ public List getUncontactedAudits() {
+ return em.createQuery(
+ "SELECT a FROM AuditResponse a WHERE a.contacted = false ORDER BY a.submittedAt DESC",
+ AuditResponse.class
+ ).getResultList();
+ }
+
+ /**
+ * Marque un audit comme contacté
+ */
+ @Transactional
+ public void markAsContacted(Long auditId, String notes) {
+ AuditResponse audit = em.find(AuditResponse.class, auditId);
+ if (audit != null) {
+ audit.setContacted(true);
+ audit.setContactedAt(java.time.LocalDateTime.now());
+ audit.setSalesNotes(notes);
+ }
+ }
+
+ /**
+ * Récupère un audit par ID
+ */
+ public AuditResponse getAuditById(Long auditId) {
+ return em.find(AuditResponse.class, auditId);
+ }
+
+ /**
+ * Récupère les statistiques d'audit
+ */
+ public Map getAuditStatistics() {
+ Map stats = new HashMap<>();
+
+ // Nombre total d'audits
+ Long totalAudits = em.createQuery("SELECT COUNT(a) FROM AuditResponse a", Long.class)
+ .getSingleResult();
+ stats.put("totalAudits", totalAudits);
+
+ // Audits cette semaine
+ Long weeklyAudits = em.createQuery(
+ "SELECT COUNT(a) FROM AuditResponse a WHERE a.submittedAt >= :weekStart", Long.class)
+ .setParameter("weekStart", java.time.LocalDateTime.now().minusDays(7))
+ .getSingleResult();
+ stats.put("weeklyAudits", weeklyAudits);
+
+ // Score moyen
+ Double avgScore = em.createQuery(
+ "SELECT AVG(a.maturityPercentage) FROM AuditResponse a", Double.class)
+ .getSingleResult();
+ stats.put("averageScore", avgScore != null ? avgScore : 0.0);
+
+ // Répartition par secteur
+ List