Apprendre à utiliser Access pour gérer une cave à vin

Claude Leloup

Cette application de gestion de cave à vin n'a pas la prétention de rivaliser avec les logiciels existants, même gratuits, que vous pouvez facilement trouver sur la toile.
Voyez plutôt ceci comme un prétexte pour montrer combien il est facile d'utiliser Access pour réaliser des outils bien pratiques : en l'occurrence, une gestion de stock.
En effet, il s'agit de décrire différents articles (les bouteilles) que l'on entre en stock (les achats), que l'on range à un endroit précis (les casiers de la cave), que l'on sort ensuite (les consommations avec modération) et périodiquement dresser l'inventaire (ce qui reste, valorisé ici au dernier prix d'achat).

Cet article s'adresse à des utilisateurs d'Access qui ont déjà une petite expérience du logiciel (VBA notamment) ou qui veulent se perfectionner.

Une discussion est ouverte pour vos commentaires éventuels : Commentez

- Titre : Apprendre à utiliser Access pour gérer une cave à vin

- Auteur : Claude Leloup

- Parution : 1 septembre 2017

- Mise à jour : 25 mai 2021

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Claude Leloup. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Le formulaire principal que l'on veut réaliser

- II. Prérequis pour réaliser ceci avec Access

- III. Les tables et leurs relations

- IV. Les bibliothèques utiles

- V. Réalisation du formulaire fBouteilles

- V-A. La source du formulaire

- V-A-1. Première étape : construire une requête qui affiche toutes les données qui interviennent dans le formulaire et les filtres

- V-A-2. Deuxième étape : ajouter les critères de sélection

- V-A-2-a. Exemple pour un filtre texte à recherche étendue

- V-A-2-b. Exemple pour un filtre à recherche stricte

- V-A-2-c. Exemple pour un filtre du type groupe d'options

- V-A-3. Troisième étape : affecter cette requête comme source du formulaire

- V-A-4. Le code VBA associé au fonctionnement des filtres

- V-A-4-a. Après mise à jour d'un filtre

- V-A-4-b. Double-clic sur un contrôle de filtre

- V-A-4-c. Clic sur le bouton « Tout afficher »

- V-B. Dériver la région quand on connaît l'appellation

- V-C. Afficher l'image de la bouteille de l'enregistrement en cours

- V-D. Rapatrier une image pour l'affecter à l'enregistrement en cours

- V-E. Lancer une recherche Google en combinant des données affichées dans le formulaire

- V-F. Colorer un groupe d'options d'après le bouton radio

- V-G. Supprimer une bouteille de la base

- V-H. Comment créer un article quasi semblable à l'enregistrement actif

- V-I. Comptabiliser une entrée ou une sortie

- V-J. Voir l'historique des mouvements pour une bouteille

- VI. Quelques commentaires sur les états

- VI-A. Liste des vins bus avec_

- VI-B. Liste des entrées depuis le_ jusqu'au_

- VI-C. Répartition des bouteilles par région

- VI-D. Inventaire par Pays/Région

- VII. Conclusion

- VIII. Téléchargement

- IX. Remerciements

I - Le formulaire principal que l'on veut réaliser

Les dimensions de certaines images ont été réduites. Cliquez sur l'image pour la voir dans son format original.

Les crus sont affichés par ordre alphabétique. On navigue au moyen des boutons :

Quand l'appellation est choisie, la région s'affiche automatiquement :

Un clic sur le bouton déclenche une recherche comme si vous l'aviez faite avec Google en combinant :

- le cru ;

- le millésime ;

- le propriétaire/négociant ;

- la catégorie.

Un clic sur ouvre une boîte de dialogue pour rechercher l'image *.jpg à associer à cette bouteille.

Un clic sur élimine définitivement l'article de la base.

Un clic sur le bouton permet de créer un nouvel enregistrement dont les caractéristiques se rapprochent de celles de la bouteille affichée. Par exemple le même cru, mais d'un autre millésime.
Après le clic, l'enregistrement nouveau est identique à celui qui a servi de modèle, l'utilisateur modifie alors ce qu'il faut et sauvegarde en cliquant sur la gouttière de sélection :

,, Si on clique l'un des boutons , on peut, respectivement, enregistrer une nouvelle entrée, une nouvelle sortie ou consulter l'historique des mouvements.

Une série de filtres pour limiter l'affichage aux bouteilles qui correspondent aux critères choisis (on peut les conjuguer).
Nous avons donc affaire à un formulaire polyvalent (de type SCRUD : Search, Create, Read, Update, Delete) qui permet d'ajouter, de supprimer, de modifier des enregistrements et de les voir sélectivement.
On peut conjuguer plusieurs critères, par exemple : les bordeaux blancs en bouteille et dont il reste du stock :

N.B. Dans toutes les zones de liste, le choix est limité aux items qui s'affichent : la recherche des bouteilles répondant au(x) critère(s) se fera par comparaison stricte du critère.
Une exception pour *Cru : la liste proposée n'est pas limitée, par exemple si l'utilisateur saisit « Corton » les crus suivants seront sélectionnés : Aloxe-Corton ; Corton Bressandes et Corton Charlemagne. (On parle alors de « comparaison étendue ».)

Abordons maintenant les aspects techniques de l'application.

II - Prérequis pour réaliser ceci avec Access

Ce tutoriel s'adresse à des utilisateurs qui maîtrisent déjà les bases du logiciel Access ou qui veulent faire l'effort pour progresser : l'utilisation du VBA décuple le potentiel de solutions !

Pour vérifier votre niveau, parcourez ces tutoriels : si vous les comprenez facilement, vous êtes OK et si ce n'est pas le cas, insistez :
- pour commencer : Maxence Hubiche Access - Les Bases ;
- pour construire des requêtes : Jean Ballat Créer des requêtes simples ;
- pour construire un formulaire : Jean-Philippe Ambrosino le chapitre 2-1-2 de Mise en surbrillance d'un enregistrement dans un formulaire ;
- pour le VBA : Olivier Lebeau Initiation au VBA Office.

D'une manière générale, pour vous documenter sur les propriétés d'un formulaire ou d'un état, ou de leurs contrôles :
affichez l'objet en mode création ;
cliquez sur la propriété, elle se met alors en surbrillance ;
enfoncez la touche <F1>.
Pour un problème de code dans un module, placez le curseur n'importe où dans l'instruction et pressez <F1>.
L'aide Access s'ouvre alors à la bonne page.
On peut aussi :
ouvrir l'aide <F1>, choisir l'onglet « Aide intuitive » et suivre les instructions ;
ouvrir la fenêtre d'exécution (<Ctrl> + G), saisir un mot-clé, y placer le point d'insertion et presser <F1>.

III - Les tables et leurs relations

Pour vous documenter : Maxence HUBICHE Comprendre les jointures dans Access.

IV - Les bibliothèques utiles

V - Réalisation du formulaire fBouteilles

V-A - La source du formulaire

On s'attend à ce que la source de fBouteilles soit la table tBouteilles.
Cela conviendrait pour un formulaire traditionnel (CRUD) qui permet de créer des enregistrements (Create), de les lire (Read), de les modifier c'est-à-dire de les mettre à jour (Update) et de les supprimer (Delete).
Nous avons voulu ici ajouter une fonctionnalité : restreindre, à la demande, le nombre des enregistrements affichés. À savoir, proposer seulement ceux qui répondent à certains critères exprimés par l'utilisateur, et cela, avec un formulaire SCRUD (Select) qui en plus, permet de sélectionner les enregistrements à afficher.

Sur DVP, on désigne souvent ce type de formulaire sous le vocable « formulaire à recherche multicritère ».

Vous trouverez une documentation abondante à cette adresse : https://access.developpez.com/cours/?page=interfaceform#interfaceform.

Ici les différents critères de sélection sont :

Convention de nommage les champs s'appellent : FiltrePays, FiltreCru, […] , FiltreFormat et caStock.

Voici une méthode pour fabriquer un formulaire SCRUD basé sur une requête enregistrée.

V-A-1 - Première étape : construire une requête qui affiche toutes les données qui interviennent dans le formulaire et les filtres

Le formulaire fBouteilles doit être ouvert pour que l'on puisse faire référence à son contenu.

Nous prenons évidemment toutes les colonnes de tBouteilles, nous ajoutons une requête rStock pour disposer du stock et la table tRegions pour disposer de la région. La table tAppellations est aussi nécessaire pour permettre la liaison entre une bouteille et sa région.

V-A-2 - Deuxième étape : ajouter les critères de sélection

Il faut dire à Access : « Si dans le formulaire la valeur du filtre est Null, alors il faut ramener tous les enregistrements. Sinon, ramener seulement ceux qui satisfont au critère contenu dans le filtre. »

V-A-2-a - Exemple pour un filtre texte à recherche étendue

V-A-2-b - Exemple pour un filtre à recherche stricte

V-A-2-c - Exemple pour un filtre du type groupe d'options

D'une part la requête rStock et d'autre part le groupe d'options :

Le challenge, c'est de dire à Access : « Si le groupe d'options vaut :

- 0, il faut ramener les enregistrements dont la somme des mouvements est Null ou 0 ;

- 1, il faut ramener les enregistrements dont la somme des mouvements est différente de 0 ;

- 2, il faut ramener tous les enregistrements. »

Nous n'avons pas trouvé la syntaxe pour exprimer le critère par rapport au nombre de bouteilles en stock, par exemple, dire « ramener les enregistrements avec stock <>0 si le groupe vaut 1 » (voyez cette discussion sur le forum à ce sujet).
C'est pour contourner cet obstacle que nous avons ajouté une colonne booléenne « OuiSiVide » à la requête rStock et nous testons ainsi :

V-A-3 - Troisième étape : affecter cette requête comme source du formulaire

Habituellement, il suffit de renseigner la requête que l'on vient de construire comme source du formulaire :

Ici, une petite difficulté supplémentaire : le résultat de la requête n'est plus modifiable à cause de la requête rStock qui intervient dans le modèle :

La requête rfBouteilles ne peut pas être la source du formulaire fBouteilles sous peine qu'il devienne impossible de créer un nouvel enregistrement, impossible de modifier une donnée et impossible de supprimer un enregistrement :

Pour contourner le problème, nous allons ruser : au lieu de prendre rfBouteilles comme source, nous allons prendre une requête qui ramène tous les enregistrements de tBouteilles qui sont sélectionnés par rfBouteilles, c'est-à-dire ceux qui sont ramenés par :

Et comme source du formulaire, nous prendrons :

V-A-4 - Le code VBA associé au fonctionnement des filtres

V-A-4-a - Après mise à jour d'un filtre

Pour actualiser les enregistrements proposés :

Private Sub FiltrePays_AfterUpdate()
Me.Requery
End Sub

V-A-4-b - Double-clic sur un contrôle de filtre

Pour annuler l'effet du filtre :

Private Sub FiltrePays_DblClick(Cancel As Integer)
Me.ActiveControl = Null
Me.Requery
End Sub

V-A-4-c - Clic sur le bouton « Tout afficher »

Private Sub BtTout_Click()
Dim oCtl As Control
'Réinitialiser les filtres standard
For Each oCtl In Me.Controls
If oCtl.Name Like "Filtre*" Then
oCtl = Null
End If
Next oCtl
'Réinitialiser caStock
Me.caStock = 2
'Réactualiser la sélection
Me.Requery
End Sub

V-B - Dériver la région quand on connaît l'appellation

Remarquez la syntaxe pour désigner le contenu de la 3e colonne :

=[cboAppellationAcloner].column(2)

(2) est l'indice de la colonne. Access commence la numérotation des indices à zéro !

V-C - Afficher l'image de la bouteille de l'enregistrement en cours

Ou signaler son absence éventuelle.

Access permet de stocker des images directement dans la base de données. Que ce soit en tant que valeur du champ OLE d'une table ou en tant que propriété Image (Picture) d'un formulaire, d'un état ou d'un contrôle de l'un de ces deux objets. Toutefois, cela peut très vite alourdir la base de données si l'on a un grand nombre d'images en jeu.

cafeine, dans son tutoriel Gestion de photos par formulaire, a montré comment externaliser les images : il propose de stocker les adresses dans une table au lieu des images elles-mêmes.

Ici, nous allons un pas plus loin. Pas de table avec les adresses, mais une convention : les images seront toutes logées dans un sous-répertoire « Images » de l'application. Ainsi, quel que soit l'endroit où vous avez installé la base de données, le chemin complet de la photo sera CurrentProject.Path & "/Images/" & LeNomDeLaPhoto.

Et pour simplifier encore : on conviendra que le nom de la photo se compose de la clé de l'enregistrement avec l'extension « .jpg ».

S'il s'avère que le fichier est absent, on affiche par défaut Default.jpg

Voici le code VBA associé à l'événement « Sur Activation » du formulaire, il se déclenche à chaque lecture d'un enregistrement :

Private Sub Form_Current()

'Afficher l'image
If existeFileFSO(CurrentProject.Path & "/Images/" & Me.txttBouteillesPK & ".jpg") Then
Me.Image.Picture = CurrentProject.Path & "/Images/" & Me.txttBouteillesPK & ".jpg"
Else
Me.Image.Picture = CurrentProject.Path & "/Images/Default.jpg"
End If

Remarquez que dans cette application, les images « statiques » (comme celle de ce bouton par exemple, sont aussi externalisées.

Si le sujet vous intéresse, voyez ce tutoriel Stockez les images statiques de vos formulaires et états Access hors de la base de données.

V-D - Rapatrier une image pour l'affecter à l'enregistrement en cours

L'idée consiste à ouvrir une boîte de dialogue pour rechercher une image .jpg, quel que soit son nom actuel, pour en stocker une copie rebaptisée dans le sous-répertoire Images.

Voici le code associé au clic du bouton :

Private Sub BtAjouterImage_Click()
Dim sOriginal As String
On Error GoTo GestionErreurs
Me.Refresh
sOriginal = ChoisirUnFichier(Me.hwnd, "Quelle image .jpg voulez-vous affecter ?", 1, "Fichiers *.jpg", "jpg", "c:\")
'Affecter l'image à l'enregistrement en cours
FileCopy sOriginal, CurrentProject.Path & "/Images/" & Me.txttBouteillesPK & ".jpg"
Me.Image.Picture = CurrentProject.Path & "/Images/" & Me.txttBouteillesPK & ".jpg"
GestionErreurs:
Select Case Err.Number
Case 0 'pas d'erreur
Exit Sub
Case 75 'aucun fichier n'a été choisi
Exit Sub
Case Else
MsgBox "Erreur dans BtAjouterImage_Click N° " & Err.Number & " " & Err.Description
End Select
End Sub

N.B. Le code de la routine ChoisirUnFichier se trouve dans le module mArkham46.

Il s'agit d'une version améliorée par Arkham46 pour la rendre compatible 32 bits/64 bits.

V-E - Lancer une recherche Google en combinant des données affichées dans le formulaire

Loufab a proposé une routine qui permet d'ouvrir un fichier avec le programme que l'utilisateur a associé à l'extension du fichier.

Il suffit de loger dans un module le code suivant :

Le module mLoufab

Option Compare Database
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal Hwnd As LongPtr, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As LongPtr
#Else
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
(ByVal Hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
#End If
Public Sub Ouvrir_fichier(Chemin As String)

'Ouvrir, avec le pgm associé, le fichier dont le chemin est passé en paramètre
ShellExecute Application.hWndAccessApp, "open", Chemin, "", "", 1

End Sub

On écrit Ouvrir_fichier(LeCheminDeCeFichier) et tout se passe comme si vous aviez double-cliqué sur le nom du fichier dans votre explorateur de fichiers.

Cette routine fonctionne aussi avec une URL, ainsi, Ouvrir_fichier ("http://www.google.com/") va entraîner l'affichage de la page d'accueil de Google par votre navigateur.

Et si vous voulez lancer directement une recherche, la syntaxe est
Ouvrir_fichier ("http://www.google.fr/search?q=les mots-clés séparés par un espacement")

Dans notre cas :

Private Sub btGoogle_Click()
Ouvrir_fichier ("http://www.google.com/search?q=" _
& Me.txtCruAcloner & " " & Me.txtMillesimeAcloner & " " _
& Me.txtProprioNegoAcloner & " " & Me.cboCategorieAcloner.Column(1))
End Sub

V-F - Colorer un groupe d'options d'après le bouton radio

La fonctionnalité « Mise en forme conditionnelle » n'est pas disponible pour un cadre d'options.

Qu'à cela ne tienne, nous allons ruser…

À côté du groupe d'options, nous plaçons une zone de texte avec certaines propriétés communes :

Nous définissons les caractéristiques suivantes pour la mise en forme conditionnelle de la zone de texte :

Remarquez que c'est la valeur du cadre d'options qui sert de référence à la mise en forme conditionnelle de la zone de texte.

Nous superposons ces deux contrôles, avec la zone de texte à l'arrière-plan.

Pour garantir que la zone de texte ne reprenne l'avant-plan, ce code dans son événement « Sur réception du focus » :

Private Sub caAppreciationCouleur_GotFocus()
Me.caAppreciation.SetFocus
End Sub

Un dernière retouche : à ce stade, le choix de l'option « À garder » donnerait ceci :

Ce serait plus lisible si l'étiquette des boutons était en blanc. Allez, faisons joli !

Dans l'événement « Sur activation » du formulaire et après mise à jour de caAppreciation, nous déclencherons cette routine :

Public Sub CouleurEtica()
Dim i As Integer
If Me.caAppreciation = 2 Then 'en blanc
For i = 1 To 3
Me("etica" & i).BorderColor = 16777215 'blanc
Me("etica" & i).ForeColor = 16777215 'blanc
Next i
Else
For i = 1 To 3
Me("etica" & i).BorderColor = 0 'noir
Me("etica" & i).ForeColor = 0 'noir
Next i
End If
End Sub

Et voilà le travail !

V-G - Supprimer une bouteille de la base

Pour supprimer un enregistrement de la table tBouteilles, il faut d'abord supprimer les enregistrements des autres tables qui se réfèrent (éventuellement) à la clé primaire de l'enregistrement à supprimer.

Dans notre cas, il s'agit des enregistrements (éventuels) de tMouvements :

On demandera également le sort à réserver à l'image (éventuellement) associée à l'enregistrement.

Pour éviter les regrets éternels, on demande d'abord à l'utilisateur de confirmer ses intentions destructrices.

Private Sub BtSupprimer_Click()
Dim iReponse As Integer
iReponse = MsgBox("Confirmez que vous voulez supprimer cet article", vbOKCancel + vbDefaultButton2)
If iReponse = vbCancel Then Exit Sub
'Supprimer l'image (éventuellement)
If existeFileFSO(CurrentProject.Path & "/Images/" & Me.txttBouteillesPK & ".jpg") = True Then
iReponse = MsgBox("Voulez-vous aussi supprimer l'image ?", vbYesNo + vbDefaultButton1)
If iReponse = vbYes Then
Kill (CurrentProject.Path & "/Images/" & Me.txttBouteillesPK & ".jpg")
End If
End If
DoCmd.SetWarnings False
'Supprimer les mouvements (éventuels)
DoCmd.RunSQL ("DELETE * FROM tMouvements WHERE tBouteillesFK=" & Me.txttBouteillesPK & ";")
DoCmd.RunSQL ("DELETE * FROM tBouteilles WHERE tBouteillesPK=" & Me.txttBouteillesPK & ";")
DoCmd.SetWarnings True
Me.Requery
End Sub

V-H - Comment créer un article quasi semblable à l'enregistrement actif

Une belle occasion d'utiliser la propriété Remarque (Tag) d'un contrôle !

Voici une propriété rarement utilisée et pourtant bien pratique ! C'est un pense-bête : vous pouvez y inscrire ce que vous voulez avec un maximum de 2048 caractères.

Contrairement aux autres propriétés, le paramétrage de celle-ci n'affecte pas les attributs d'un objet.

La première idée pour créer un enregistrement quasi semblable, c'est de d'abord le copier et ensuite modifier ce qu'il faut.
Dans notre cas, ce n'est pas possible, car pour éviter les doublons (deux bouteilles identiques dans notre base), nous avons placé un index unique sur les colonnes de la table qui singularisent un article :

Donc, la tentative d'ajouter d'abord un enregistrement identique à celui affiché échouerait.

Il faut trouver un moyen pour pouvoir modifier au moins une valeur avant d'ajouter le nouvel enregistrement dans tBouteilles.

Au fait, quels champs faut-il cloner ? Eh bien ! ceux- ci :

Pour simplifier l'écriture du code, nous avons donné un nom « particulier » aux contrôles qui contiennent ces champs, leur nom se termine par « Acloner » : txtCruAcloner, txtMillesimeAcloner, cboAppellationAcloner…

Quand l'utilisateur clique le bouton, le programme va :
- copier le contenu de chaque contrôle suffixé « Acloner » dans sa propriété Remarque ;
- proposer un enregistrement vierge ;
- dans chaque contrôle suffixé « Acloner », reporter le contenu de la propriété Remarque dans la propriété Valeur.

À ce moment, l'utilisateur reprend la main pour modifier ce qu'il faut et sauvegarder ce nouvel enregistrement.

Voici le code :

Private Sub BtCloner_Click()
Dim oCtl As Control
'On Error GoTo GestionErreurs
Me.Refresh
'une boucle sur tous les contrôles, détecter ceux *Acloner et copier leur contenu
For Each oCtl In Me.Controls
If oCtl.Name Like "*Acloner" Then
Me(oCtl.Name).Tag = Nz(Me(oCtl.Name), " ") 'pour les cas où un champ texte est vide
End If
Next oCtl
'Ouvrir un nouvel enregistrement
DoCmd.GoToRecord , , acNewRec
'Retransférer Remarque dans Value
For Each oCtl In Me.Controls
If oCtl.Name Like "*Acloner" Then
Me(oCtl.Name) = Me(oCtl.Name).Tag
End If
Next oCtl
GestionErreurs:
Select Case Err.Number
Case 0 ' pas d'erreur
Case Else
MsgBox "Erreur N° " & Err.Number & " " & Err.Description & vbLf _
& "dans BtCloner_Click."
End Select
End Sub

V-I - Comptabiliser une entrée ou une sortie

Le clic sur l'un des boutons ouvre le formulaire fMouvements en adaptant sa présentation :

Remarquez que le formulaire fMouvements s'affiche juste en dessous du bouton cliqué, quelle que soit la position du formulaire fBouteilles à l'écran !

Clic sur entrée

1. Private Sub BtEntree_Click()

2. Me.Refresh 'utile si l'enregistrement est en cours de création

3. 'S'ils sont ouverts, refermer fJLEntreesSorties et fMouvements

4. If CurrentProject.AllForms("fJLEntreesSorties").IsLoaded Then DoCmd.Close acForm, "fJLEntreesSorties"

5. If CurrentProject.AllForms("fMouvements").IsLoaded Then DoCmd.Close acForm, "fMouvements"

6. 'Ouvrir fMouvements juste en dessous du bouton (méthode Arkham46)

7. DoCmd.OpenForm "fMouvements", , , , , acHidden

8. PositionForm Forms("fMouvements"), Me.BtEntree

9. 'Adapter la forme de fMouvements pour une entrée

10. Forms!fMouvements!txtTitre = "Entrée" & Chr(13) & Chr(10) _

11. & "saisissez un nombre positif" & Chr(13) & Chr(10) _

12. & "dans le champ Nombre."

13. Forms!fMouvements!TXTtBouteillesFK = Me.txttBouteillesPK

14. Forms!fMouvements.Section("PiedFormulaire").Visible = False

15. End Sub

Commentaires du code

N° d'instruction

3 à 8

Ouverture du formulaire fMouvements par rapport à la position du bouton cliqué.
Cette routine est proposée à cette adresse par Arkham46.
Même si vous ne comprenez pas à quoi servent toutes les API auxquelles il fait appel dans son code, faites confiance : ça marche au poil !
Il suffit de copier le code proposé dans un module (dans notre base exemple, c'est le module mArkham46).
Et pour l'utiliser, deux lignes de code :

DoCmd.OpenForm "FormulaireAPositionner", , , , , acHidden
PositionForm Forms("FormulaireAPositionner"), Me.UneZoneDeTexte

Deux précautions :

- le formulaire à ouvrir doit être en fenêtre indépendante ;

- en cas d'appels successifs, refermez d'abord le formulaire précédemment ouvert avant de l'ouvrir une nouvelle fois.

10 à 12

On adapte la forme de fMouvements (titre et contrôles visibles selon le bouton cliqué).

13

On copie le tBouteillesPK de l'enregistrement actif de fBouteilles dans le tBouteillesFK de fMouvements.

14

On cache les contrôles spécifiques à l'autre bouton (ici on cache ceux qui concernent une sortie).

Et un code semblable pour un clic sur l'autre bouton :

Private Sub BtSortie_Click()
'S'ils sont ouverts, refermer fJLEntreesSorties et fMouvements
If CurrentProject.AllForms("fJLEntreesSorties").IsLoaded Then DoCmd.Close acForm, "fJLEntreesSorties"
If CurrentProject.AllForms("fMouvements").IsLoaded Then DoCmd.Close acForm, "fMouvements"
'Ouvrir fMouvements juste en dessous du bouton (méthode Arkham46)
DoCmd.OpenForm "fMouvements", , , , , acHidden
PositionForm Forms("fMouvements"), Me.BtSortie
'Adapter la forme de fMouvements pour une sortie
Forms!fMouvements!txtTitre = "Sortie" & Chr(13) & Chr(10) _
& "saisissez un nombre négatif" & Chr(13) & Chr(10) _
& "dans le champ Nombre."
Forms!fMouvements!TXTtBouteillesFK = Me.txttBouteillesPK
Forms!fMouvements!etiPrix.Visible = False
Forms!fMouvements!txtPrix.Visible = False
End Sub

Dans les zones de liste, si l'utilisateur propose un texte qui n'est pas encore dans les choix prévus, le programme propose de l'ajouter :

Private Sub cboBuAvec_NotInList(NewData As String, Response As Integer)
If MsgBox("Voulez-vous ajouter " & NewData & " à la liste des convives ?", _
vbYesNo + vbQuestion + vbDefaultButton2, "Ajout") = vbYes Then
DoCmd.SetWarnings False
DoCmd.RunSQL "INSERT INTO tBuAvec ( BuAvec ) SELECT """ & NewData & """;"
DoCmd.SetWarnings True
Response = acDataErrAdded
Else
Response = acDataErrContinue
Me.cboBuAvec.Undo
End If

End Sub

Private Sub cboBuSur_NotInList(NewData As String, Response As Integer)
If MsgBox("Voulez-vous ajouter " & NewData & " à la liste des mets dégustés ?", _
vbYesNo + vbQuestion + vbDefaultButton2, "Ajout") = vbYes Then
DoCmd.SetWarnings False
DoCmd.RunSQL "INSERT INTO tBuSur ( BuSur ) SELECT """ & NewData & """;"
DoCmd.SetWarnings True
Response = acDataErrAdded
Else
Response = acDataErrContinue
Me.cboBuAvec.Undo
End If
End Sub

Private Sub cboCommentaire_NotInList(NewData As String, Response As Integer)
If MsgBox("Voulez-vous ajouter " & NewData & " à la liste des commentaires ?", _
vbYesNo + vbQuestion + vbDefaultButton2, "Ajout") = vbYes Then
DoCmd.SetWarnings False
DoCmd.RunSQL "INSERT INTO tCommentaires ( Commentaire ) SELECT """ & NewData & """;"
DoCmd.SetWarnings True
Response = acDataErrAdded
Else
Response = acDataErrContinue
Me.cboBuAvec.Undo
End If
End Sub

C'est l'adaptation du code proposé dans la FAQ : https://access.developpez.com/faq/?page=zdl#AbsDsListe.

V-J - Voir l'historique des mouvements pour une bouteille

Le clic sur ce bouton ouvre le formulaire fJLEntreesSorties :

Il est seulement possible de modifier un enregistrement, pas d'ajout ni de suppression :

En fait, pour « neutraliser » un enregistrement, il suffit d'encoder 0 dans le nombre, l'enregistrement sera alors physiquement supprimé lors de la fermeture de fBouteilles (voir l'événement Private Sub Form_Close() de fBouteilles).

Lorsque l'utilisateur s'apprête à modifier un nombre, le programme contrôle d'abord que cette modification n'entraînera pas un stock négatif :

Private Sub txtMvt_BeforeUpdate(Cancel As Integer)
'Vérifier que la modif ne provoquera pas un stock négatif
If Forms!fBouteilles!txtStock - Me.txtMvt.OldValue + Me.txtMvt.Value < 0 Then
MsgBox "Cette modification entraînerait un stock négatif !", vbCritical
Cancel = True
Me.txtMvt.Undo
End If
End Sub

Si c'est le cas, la modif est refusée et un message est affiché :

VI - Quelques commentaires sur les états

VI-A - Liste des vins bus avec_

La source

VI-B - Liste des entrées depuis le_ jusqu'au_

La source

Remarquez le critère pour le MvtDate :

- si l'utilisateur n'a pas complété la date de départ (elle est donc Null), elle sera remplacée par une date lointaine dans le passé (01/01/1900) ;

- pareil pour l'absence de la date de fin (date du jour).

En d'autres mots, si les dates sont null, on liste depuis le début jusqu'à aujourd'hui.

VI-C - Répartition des bouteilles par région

La source

On part de la requête rRepartition :

… que nous allons convertir en requête d'analyse croisée à l'aide de l'interface proposée par Access.

Voici les différentes étapes :

1° affichez la fenêtre des objets à l'onglet « Requêtes ». Cliquez sur « Nouveau ». Dans la fenêtre qui vient, sélectionnez « Assistant Requête analyse croisée » et cliquez le bouton « Suivant> » :

2° répondez à la série de questions :

3° terminez en donnant un nom significatif à la requête croisée :

4° il vient :

… qui servira de source à l'état eRepartition :

VI-D - Inventaire par Pays/Région

La source

Procédure pour imprimer sur deux colonnes

VII - Conclusion

Ce tutoriel a été l'occasion de montrer quelques réalisations pratiques qui répondent à des questions souvent posées sur le forum :

- mettre en place une gestion de stock, avec une évaluation au dernier prix d'achat ;

- construire un formulaire de recherche multicritère basé sur une requête enregistrée qui est non modifiable ;

- afficher une image différente pour chaque enregistrement et logeant ces images en dehors de la base ;

- montrer une astuce pour contourner l'absence de mise en forme conditionnelle pour un cadre d'options ;

- décrire une méthode pour cloner un enregistrement et ses dépendants éventuels dans les tables liées à la sienne ;

- construire un état basé sur une requête d'analyse croisée ;

- imprimer un état sur plusieurs colonnes, quand cela permet d'économiser du papier.

VIII - Téléchargement

La base de données au format Access2000 et un sous-répertoire Images réduit :

https://claudeleloup.developpez.com/tutoriels/access/cave-a-vin/CaveAvin.zip (10 608 ko).

Pour plus d'images de bouteilles, remplacez le sous-répertoire Images réduit par celui-ci :

https://claudeleloup.developpez.com/tutoriels/access/cave-a-vin/ToutesLesImages.zip (120 142 ko).

IX - Remerciements

Un grand merci à :

marcdels pour sa contribution à la mise au point de cette application et notamment les exemples et les tests des fonctionnalités ;

tee_grandbois pour ses explications claires sur le forum ;

Jacques THERY pour la relecture orthographique.