Publipostage Multiligne via VBA
Bonjour,
Cela fait plusieurs mois que je bloque sur un publipostage.
Je dois envoyer des commandes à plusieurs fournisseurs et aimerais faire un seul mail par fournisseur. Mes données sont regroupées et organisées dans un excel.
J'ai trouvé ce poste sur le forum qui m'a avancé https://forum.excel-pratique.com/excel/publipostage-multi-ligne-175227, mais je ne suis pas au bout
Joint mon fichier Test anonymisé, dans lequel j'ai les deux modules suivants :
Création des Mails :
Option Explicit
Sub publipostage()
Application.ScreenUpdating = False
Dim classeurOrigine As String, classeurPublipostage As String
classeurOrigine = ActiveWorkbook.Name
Workbooks.Add
classeurPublipostage = ActiveWorkbook.Name
[a1] = "Publipostage automatique réalisé le " & Format(Now, "dd/mm/yyyy hh:mm")
Windows(classeurOrigine).Activate
Dim table As Range
Set table = [a1].CurrentRegion
Dim ligne As Integer, colonne As Integer
For ligne = 2 To table.Rows.Count
Dim enregistrements As String, email As String
enregistrements = ""
For colonne = 1 To table.Columns.Count
enregistrements = enregistrements & table(1, colonne) & ";" & table(ligne, colonne) & "!"
Next
Debug.Print Now, ligne, enregistrements
ActiveSheet.Shapes(1).Copy
Application.Wait (Now + TimeValue("0:00:01"))
Windows(classeurPublipostage).Activate
Sheets.Add After:=ActiveSheet
ActiveSheet.Paste
Application.CutCopyMode = False
Dim enregistrement As Variant, nouveauTexte As String
For Each enregistrement In Split(enregistrements, "!")
If enregistrement <> "" Then
nouveauTexte = ActiveSheet.Shapes(1).TextFrame.Characters.Text
nouveauTexte = Replace(nouveauTexte, "[" & Split(enregistrement, ";")(0) & "]", Split(enregistrement, ";")(1), 1)
ActiveSheet.Shapes(1).TextFrame.Characters.Text = nouveauTexte
If Split(enregistrement, ";")(0) = "Mail" Then
email = Split(enregistrement, ";")(1)
End If
End If
Next
creerMail email, nouveauTexte
Windows(classeurOrigine).Activate
Next
Application.ScreenUpdating = True
End SubEnvoi des Mails :
Option Explicit
Sub creerMail(email As String, corps As String)
Dim oOutlook As Object
Set oOutlook = CreateObject("Outlook.Application")
Dim oMail As Object
Set oMail = oOutlook.CreateItem(0)
With oMail
.To = email
.Body = corps
.Subject = "Groupe XXX - Commandes Contractuelles"
.Display
End With
End SubSauf que dans cette configuration j'ai un mail par ligne et non pas un mail par fournisseur.
Est-il possible de modifier le code pour solutionner ce problème ?
Merci d'avance
Bonjour,
Et quelle "présentation" voulez-vous pour les infos à regrouper :
- Simple : 1 ligne par numéro de commande et les infos simplement mises cotes à cotes ? (je saurai le faire)
- Complexe : principe similaire mais mis en page dans un tableau (je ne saurai pas le faire)
Doit-on garder les crochets ?
Êtes-vous bien sur Windows ? Car l'utilisation des dictionnaires est une méthode simple et efficace pour regrouper par adresse email.
Je n'ai pas trop compris pourquoi votre code navigue entre des classeurs ? Il me semble qu'on puisse tout faire avec le classeur courant.
Bonjour,
Idéalement sous forme de tableau, mais si je peux déjà avoir les données côte à cote ce serait une grande avancée.
Je pense que les crochets sont hérités d'une tentative de publipostage sur word, ils ne sont pas nécessaires.
Je suis effectivement bien sur windows, je ne connais pas l'utilisation des dictionnaires
J'ai glané des bouts de codes à droite à gauche pour constituer celui ci j'avoue ne pas etre experte et c'est sans doute pour ça que j'ai des maladresses. Il me semble que je me suis basée sur un code qui permettait de générer une feuille de classeur par courrier et un autre qui permettait d'envoyer des mails.
Bon, avec un peu d'aide IA pour générer un tableau je suis arrivé à la révision de code ci-dessous.
Le seul problème que j'ai c'est que je n'arrive pas à extraire correctement le texte contenu dans la forme car : pour générer un tableau dans le mail il faut passer par le code HTML du mail. Cependant la shape contenant votre texte ne permet pas de récupérer le HTML correspondant à son formatage... Je verrai demain si je trouve une alternative, peut etre avec un Word encastré à la place de votre forme dans le document Excel.
Ci-après le nouveau code (lancez le sub PublipostageParFournisseur) :
Sub PublipostageParFournisseur()
Dim table As Range
Set table = [A1].CurrentRegion
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim headerRow As Range
Set headerRow = table.Rows(1)
' Define relevant column headers
Dim selectedHeaders As Variant
selectedHeaders = Array( _
"Numéro de Commande", "Site", "Nom du site", "Adresse du site", _
"Prestation", "Montant", "Date début", "Adresse postale facture", _
"Adresse dematerialisation facture", "Adresse mail relance facture" _
)
' Map header names to column indices
Dim headerMap As Object
Set headerMap = CreateObject("Scripting.Dictionary")
Dim colIndex As Long
For colIndex = 1 To table.Columns.Count
Dim headerName As String
headerName = Trim(CStr(headerRow.Cells(1, colIndex).Value))
If Not headerMap.exists(headerName) Then
headerMap.Add headerName, colIndex
End If
Next colIndex
' Group rows by email
Dim rowIndex As Long
For rowIndex = 2 To table.Rows.Count
Dim email As String
email = CStr(table.Cells(rowIndex, 1).Value)
Dim rowData() As Variant
ReDim rowData(0 To UBound(selectedHeaders))
Dim i As Long
For i = 0 To UBound(selectedHeaders)
Dim colNum As Long
colNum = headerMap(selectedHeaders(i)) + 1
rowData(i) = table.Cells(rowIndex, colNum).Value
Next i
Dim dataArray() As Variant
If Not dict.exists(email) Then
ReDim dataArray(0 To 0)
dataArray(0) = selectedHeaders
dict.Add email, Array(dataArray, 1)
End If
Dim currentData As Variant
currentData = dict(email)
dataArray = currentData(0)
Dim nextIndex As Long
nextIndex = currentData(1)
ReDim Preserve dataArray(0 To nextIndex)
dataArray(nextIndex) = rowData
dict(email) = Array(dataArray, nextIndex + 1)
Next rowIndex
' Send emails
Dim outlookApp As Object
Set outlookApp = CreateObject("Outlook.Application")
Dim bodyTxt As String
bodyTxt = ActiveSheet.Shapes(1).TextFrame.Characters.Text
Dim emailK As Variant
For Each emailK In dict.Keys
dataArray = dict(emailK)(0)
Dim htmlTable As String
htmlTable = ArrayToHtml(WorksheetFunction.Transpose(WorksheetFunction.Transpose(dataArray)))
Dim mailItem As Object
Set mailItem = outlookApp.CreateItem(0)
With mailItem
.To = emailK
.Subject = "Groupe XXX - Commandes Contractuelles"
.htmlBody = "<html><body><p>" & bodyTxt & "</p>" & _
htmlTable & "<p>Cordialement,</p></body></html>"
.Display
End With
Next emailK
End Sub
Private Function ArrayToHtml(arr As Variant) As String
Dim htmlOutput As String
htmlOutput = "<table style='border-collapse:collapse;border:1px solid #000;font-family:Calibri;font-size:11pt;'>"
Dim isTwoDimensional As Boolean
Dim rowCount As Long
Dim colCount As Long
' Determine array dimensions
On Error Resume Next
colCount = UBound(arr, 2) - LBound(arr, 2) + 1
If Err.Number = 0 Then
isTwoDimensional = True
rowCount = UBound(arr, 1) - LBound(arr, 1) + 1
Else
isTwoDimensional = False
rowCount = 1
colCount = UBound(arr) - LBound(arr) + 1
Err.Clear
End If
On Error GoTo 0
Dim rowIndex As Long
For rowIndex = 0 To rowCount - 1
htmlOutput = htmlOutput & "<tr>"
Dim colIndex As Long
For colIndex = 0 To colCount - 1
Dim cellValue As String
If isTwoDimensional Then
cellValue = arr(rowIndex + LBound(arr, 1), colIndex + LBound(arr, 2))
Else
cellValue = CStr(arr(colIndex + LBound(arr)))
End If
Dim cellTag As String
If rowIndex = 0 Then
cellTag = "th"
Else
cellTag = "td"
End If
htmlOutput = htmlOutput & "<" & cellTag & _
" style='border:1px solid #000;padding:4px;text-align:left;white-space:nowrap;'>" & _
cellValue & "</" & cellTag & ">"
Next colIndex
htmlOutput = htmlOutput & "</tr>"
Next rowIndex
htmlOutput = htmlOutput & "</table>"
ArrayToHtml = htmlOutput
End FunctionBonjour,
Tout d'abord un énorme merci, c'est (presque) exactement ce que je cherchais à faire.
Il y a juste une chose, j'ai l'impression qu'il y a un décalage dans le tableau, en effet, ce sont les données des colonnes D à H puis J à N qui devraient se reporter dans le tableau. je suppose que c'est cette partie du code qui est en cause :
' Map header names to column indices
Dim headerMap As Object
Set headerMap = CreateObject("Scripting.Dictionary")
Dim colIndex As Long
For colIndex = 1 To table.Columns.Count
Dim headerName As String
headerName = Trim(CStr(headerRow.Cells(1, colIndex).Value))
If Not headerMap.exists(headerName) Then
headerMap.Add headerName, colIndex
End If
Next colIndex
' Group rows by email
Dim rowIndex As Long
For rowIndex = 2 To table.Rows.Count
Dim email As String
email = CStr(table.Cells(rowIndex, 1).Value)
Dim rowData() As Variant
ReDim rowData(0 To UBound(selectedHeaders))
Dim i As Long
For i = 0 To UBound(selectedHeaders)
Dim colNum As Long
colNum = headerMap(selectedHeaders(i)) + 1
rowData(i) = table.Cells(rowIndex, colNum).Value
Next i
Dim dataArray() As Variant
If Not dict.exists(email) Then
ReDim dataArray(0 To 0)
dataArray(0) = selectedHeaders
dict.Add email, Array(dataArray, 1)
End If
Dim currentData As Variant
currentData = dict(email)
dataArray = currentData(0)
Dim nextIndex As Long
nextIndex = currentData(1)
ReDim Preserve dataArray(0 To nextIndex)
dataArray(nextIndex) = rowData
dict(email) = Array(dataArray, nextIndex + 1)
Next rowIndexPouvez vous m'aider à aligner le tableau du mail avec les données du classeur ?
Bonjour,
Merci pour votre retour. En réalité c'est cette partie du code qui est en cause :
Dim selectedHeaders As Variant
selectedHeaders = Array( _
"Numéro de Commande", "Site", "Nom du site", "Adresse du site", _
"Prestation", "Montant", "Date début", "Adresse postale facture", _
"Adresse dematerialisation facture", "Adresse mail relance facture" _
)La macro va ensuite automatiquement chercher dans la feuille où se trouvent ces en-têtes. Donc pour changer l'ordre d'affichage dans le mail, changez le ici.
PS : Attention aux majuscules/minuscules/accents/espaces !
Si vous regardez j'ai repris la liste entre crochets qui était en fin de mail, c'est pour ça.
Bonjour,
Si je comprends bien vous me parlez de l'ordre des data mais si vous régardez la capture d'écran, c'est le contenu de ma colonne "Site" qui apparait dans "numéro de commande" dans le mail, l'ordre ne change pas
En fait il y a un décalage des données
Ah oui effectivement ! J'avais mal compris. Avec des données fictives l'erreur ne m'était pas sauté aux yeux. Voici la correction (valeur de colNum) :
' Group rows by email
Dim rowIndex As Long
For rowIndex = 2 To table.Rows.Count
Dim email As String
email = CStr(table.Cells(rowIndex, 1).Value)
Dim rowData() As Variant
ReDim rowData(0 To UBound(selectedHeaders))
Dim i As Long
For i = 0 To UBound(selectedHeaders)
Dim colNum As Long
colNum = headerMap(selectedHeaders(i)) ' + 1 // ici il faut retirer le +1
rowData(i) = table.Cells(rowIndex, colNum).Value
Next iC'est parfait ! un énorme merci vraiment je me suis arraché les cheveux sur cette problématique. Merci merci encore
Je vous en prie, content d'avoir pu vous faire avancer, j'ai moi aussi appris des choses.
Bonne journée et au plaisir