ATOUTFOX
COMMUNAUTÉ FRANCOPHONE DES PROFESSIONNELS FOXPRO
Visual FoxPro : le développement durable

PRELEVEMENTS SEPA   



L'auteur

eric leissler
France France
Membre Simple
# 0000002784
enregistré le 06/03/2010
http://www.aumeric.fr
68 ans
LEISSLER Eric
85290 MORTAGNE SUR SEVRE
de la société AUMERIC LOGICIELS
Fiche personnelle


Note des membres
20/20
1 vote


Contributions > 01 - PRG : Programmation

PRELEVEMENTS SEPA
# 0000000874
ajouté le 31/01/2014 11:04:55 et modifié le 31/01/2014
consulté 12277 fois
Niveau initié

Version(s) Foxpro :
VFP 9.0
VFP 8.0


Le téléchargement des pièces jointes est limité aux membres
Veuillez vous identifier ou vous inscrire si vous n'avez pas encore de compte ...
Description
Bonjour à tous
J'avais promis de le faire, alors il a fallu que je me replonge dans vfp...
Le code ci dessous permet
de lire un fichier ETEBAC ( fichier PO_RE)
et de le convertir un un fichier de type xml, conforme à la nouvelle norme SEPA.
Ce fichier xml a été testé et validé par le credit mutuel. et est valide à la norme pain.008.001.002
Dans le zip, un fichier BIC.DBF permettant d'obtenir les BIC d'un compte bancaire, à partir du code établissement de l'agence bancaire.
les comptes bancaires de l'exemple sont dépersonnalisés.
Bien cordialement à tous
Bon vent à tous et bonnes rencontres !
Eric LEISSLER
Code source :
*!*  *******************************************
* AUMERIC - JANVIER 2014 -----
*REALISER UN FICHIER XML POUR LE PRELEVEMENTS A LA NORME SEPA
*
* CE PROGRAMME UTILISE LES FONCTIONS DE MA CONTRIB
*http://www.atoutfox.org/articles.asp?ACTION=FCONSULTER&ID=0000000716
*POUR TRANSFORMER LES RIB EN IBAN DANS LE PRG LIB_RIBETIBAN
*
*
* LA TABLE BIC.DBF CONTIENT 933 CODE ETABLISSEMENT ET BIC
*
* Le but de ce programme est de lire un fichier ETEBAC et d'en faire un fichier xml
* à la norme SEPA
* Le fichier xml produit a été testé et validé par le credit MUTUEL.
*
*
******************************************

 *!* ouverture de la table des codes établissements et bic
Use bic Alias bic In 0

*!* creation de la classe
oxml= Createobject("xmlsepa")

*!* -- paramètres
*!* nom de l'organisme qui prélève
oxml.nom = "LENOMDUPRELEVEUR"
*!* nom du message  ( doit être unique )
oxml.msgid="LENOM-SDD-"+Right(Alltrim(Str(Year(Date()))),2)+Padl(jourdelannee(Date()),3,"0")+"-001"

*!* Informations sur le prélèvement
oxml.infopaiement="PRELEVEMENT MOIS "ALLTRIM(CMONTH(DATE()))

*!* N° siret de l'organisme qui prélève
oxml.siret = "1234567890123"

oxml.nics="FR99ZZZ999999"
*!* Méthode de paiement
oxml.pmethode = "DD"
*!* date d'échéance
oxml.date_ech= ALLTRIM(STR(YEAR(DATE())))+"-"+PADL(ALLTRIM(STR(MONTH(DATE()))),2,"0")+"-10"
*!* organisme crediteur
oxml.crediteur="NOMPRELEVEUR"

*!* appel des methodes
oxml.lire_etebac_et_fait_etebac_dbf

oxml.faitlexml


Select bic
Use
Select etebac
Use



*******************************************************************************************
#Define crlf Chr(13)+Chr(10)

Define Class xmlsepa As Custom
  nom = ""
  montant=""
  nb_transactions=""
  msgid=""
  siret=""
  nics=""
  lachaineduxml=""
  infopaiement=""
  pmethode=""
  date_ech=""
  crediteur=""
  bic="CMCIFR2A"
  iban="FR7600000000000000000000000"  && cet IBAN N EXISTE PAS METTRE ICI L IBAN DU BENEFICIAIRE

  Procedure Init

  Endproc
  Procedure document_et_pain

  oxml.lachaineduxml='<?xml version="1.0" encoding="UTF-8"?>'+crlf+;
    '<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.001.02">'+crlf+;
    '<CstmrDrctDbtInitn>'+crlf+;
    oxml.lachaineduxml
  Endproc


  Procedure findocument_et_pain
  oxml.lachaineduxml=  oxml.lachaineduxml+crlf+;
    "</CstmrDrctDbtInitn>"+crlf+;
    "</Document>"
  Endproc

  Procedure faitlexml

  machaine = "<OrgId>"+crlf+;
    "<Othr>"+crlf+;
    "<Id>"+oxml.siret+"</Id>"+crlf+;
    "</Othr>"+crlf+;
    "</OrgId>"+crlf

  machaine= "<Id>"+crlf+machaine+"</Id>"
  machaine= "<InitgPty>"+crlf+"<Nm>"+oxml.nom+"</Nm>"+crlf+machaine+crlf+"</InitgPty>"
  machaine =machaine+crlf+"</GrpHdr>"

  Select etebac
  oxml.nb_transactions= Reccount('etebac')

    oxml.nb_transactions=ALLTRIM(STR(oxml.nb_transactions))

  machaine ="<CtrlSum>"+ALLTRIM(STR(oxml.montant,18,2))+"</CtrlSum>"+crlf+  machaine

  machaine = "<NbOfTxs>"+oxml.nb_transactions+"</NbOfTxs>"+crlf+machaine

  machaine ="<CreDtTm>"Alltrim(Str(Year(Date())))+"-"+Padl(Alltrim(Str(Month(Date()))),2,"0")+"-"+Padl(Alltrim(Str(Day(Date()))),2,"0")+"T"+Time()+"</CreDtTm>"+crlf+machaine


  machaine = "<MsgId>"+oxml.msgid+"</MsgId>"+crlf+machaine

  machaine = '<GrpHdr>'+crlf+machaine
  oxml.lachaineduxml=machaine
  oxml.paiementinfo




*-------------------  ON A FINI  OU PRESQUE -------------------------

  oxml.ecrire_le_fichier_xml



  Endproc

  Procedure paiementinfo
  Local machaine
  Local bic_cdt
  Select etebac
  Locate
  iban_crediteur=Strtran(   calculcleiban("FR",etebac.cetab +etebac.cguich +etebac.cpte+Alltrim(clerib(etebac.cetab+  etebac.cguich+   etebac.cpte)))," ","")
  Select bic
  Set Order To etab
  Seek etebac.cetab
  If Found()
    bic_cdt=bic.bic
  Endif
  Select etebac
*!*    oxml.montant= Alltrim(Str(m.combien,15,2))


  Go Top
  Do While Not Eof()
    If Not Empty(Alltrim(etebac.nom)) Then
      machaine = crlf +;
        "<PmtInf>" +crlf+;
        "<PmtInfId>"+ oxml.infopaiement+"</PmtInfId>" +crlf+;
        "<PmtMtd>"+oxml.pmethode+"</PmtMtd>"+crlf+;
        "<NbOfTxs>1</NbOfTxs>"+ crlf+;
                "<CtrlSum>"Alltrim(Str(Val(etebac.valeur)/100,15,2)) +  "</CtrlSum>"+crlf+;
        "<PmtTpInf>"+crlf+;
        "<SvcLvl>"+crlf+;
        "<Cd>SEPA</Cd> "+crlf+;
        "</SvcLvl>"+crlf+;
        "<LclInstrm>"+crlf+;
        "<Cd>CORE</Cd>" +crlf+;
        "</LclInstrm>"+crlf+;
        "<SeqTp>OOFF</SeqTp>" +crlf+;
        "</PmtTpInf>"+crlf+;
        "<ReqdColltnDt>"+oxml.date_ech+"</ReqdColltnDt>" +crlf
      oxml.lachaineduxml=oxml.lachaineduxml + machaine


*!* on passe au crédité de l'opération
      machaine=""
      machaine="<Cdtr>"+crlf+;
        "<Nm>"+oxml.crediteur+"</Nm> "+crlf+;
        "</Cdtr>"+crlf+;
        "<CdtrAcct>"+crlf+;
        "<Id>"+crlf+;
        "<IBAN>"+oxml.iban+"</IBAN>"+crlf +;
        "</Id>"+crlf+;
        "</CdtrAcct>"+crlf+;
        "<CdtrAgt>"+crlf+;
        "<FinInstnId>"+crlf+;
        "<BIC>"+oxml.bic+"</BIC> "+crlf+;
        "</FinInstnId>"+crlf+;
        "</CdtrAgt>"+crlf+;
        "<CdtrSchmeId>"+crlf+;
        "<Id>"+crlf+;
        "<PrvtId>"+crlf+;
        "<Othr>"+crlf+;
        "<Id>"+oxml.nics+"</Id> "+crlf+;
        "<SchmeNm>"+crlf+;
        "<Prtry>SEPA</Prtry> "+crlf+;
        "</SchmeNm>"+crlf+;
        "</Othr>"+crlf+;
        "</PrvtId>"+crlf+;
        "</Id>"+crlf+;
        "</CdtrSchmeId>"+crlf





      oxml.lachaineduxml=oxml.lachaineduxml + machaine




*!* -- on passe au débiteur de l'opération

      machaine=""
      machaine="<DrctDbtTxInf>" +crlf+;
        "<PmtId>"+crlf+;
        "<EndToEndId>Prelevement 20130310-3</EndToEndId> "+crlf+;
        "</PmtId>"+crlf+;
        '<InstdAmt Ccy="EUR">'+Alltrim(Str(Val(etebac.valeur)/100,15,2))+"</InstdAmt> "+crlf+;
        "<DrctDbtTx>"+crlf+;
        "<MndtRltdInf>"+crlf+;
        "<MndtId>"+  Alltrim(etebac.libelle ) +"</MndtId>"+crlf+;
        "<DtOfSgntr>"+oxml.date_ech+"</DtOfSgntr>" +crlf+;
        "</MndtRltdInf>"+crlf+;
        "</DrctDbtTx>"+crlf+;
        "<DbtrAgt>"+crlf+;
        "<FinInstnId>"+crlf+;
        "<BIC>"+calcbic()+"</BIC> "+crlf+;
        "</FinInstnId>"+crlf+;
        "</DbtrAgt>"+crlf +;
        "<Dbtr>"+crlf +;
        "<Nm>"+Alltrim(etebac.nom)+" "+Alltrim(etebac.prenom)+"</Nm> "+crlf +;
        "</Dbtr>"+crlf +;
        "<DbtrAcct>"+crlf+;
        "<Id>"+crlf+;
        "<IBAN>"+Strtran(   calculcleiban("FR",etebac.cetab +etebac.cguich +etebac.cpte+PADL(Alltrim(((clerib(etebac.cetab+  etebac.cguich+   etebac.cpte) ))) ,2,"0"))," ","")+"</IBAN> "+crlf+;
        "</Id>"+crlf+;
        "</DbtrAcct>"+crlf+;
        "<RmtInf>"+crlf+;
        "<Ustrd>"+Alltrim(etebac.libelle)+"</Ustrd> "+crlf+;
        "</RmtInf>"+crlf+;
        "</DrctDbtTxInf>" +crlf



      oxml.lachaineduxml=oxml.lachaineduxml + machaine

      oxml.lachaineduxml=oxml.lachaineduxml + " </PmtInf>"+crlf
    Endif

    Select etebac
    If Not Eof()
      Skip
    Else
      Exit
    Endif

  Enddo

  Endproc





  Procedure ecrire_le_fichier_xml
  oxml.document_et_pain
  oxml.findocument_et_pain
  Strtofile(oxml.lachaineduxml,Putfile("PRELEVEMENT_SEPA.xml","PRELEVEMENT_SEPA.xml","xml"))
  Endproc


  Procedure lire_etebac_et_fait_etebac_dbf
  Create Cursor etebac  (nom c(13),;
    prenom c(41),;
    cguich c(5), ;
    cpte c(11),;
    valeur c(14),;
    libelle c(30), ;
    cetab c(5) ,;
    bic c(11) ,;
    iban c(34) )

  m.ctampon=Space(500)
  m.nfic=Fopen(Getfile("","fichier PO_RE","ouvrir",0,"choix du fichier PO_RE" ))
  m.fin=Fseek(nfic,0,2)
  m.deb=Fseek(nfic,0)


  m.combien=0
  m.montant=0
  m.nbpaie=0
  Point=0
  Local monniveau
  monniveau=1

  Do While Point<=m.fin-m.deb  && limiter à 24 enregistrement pour les tests de validité And monniveau <= 27
    monniveau = monniveau +1

    m.ctampon=Fread(m.nfic,162)

    If Point<(fin-163) And Val(Substr(m.ctampon,105,14)) # 0 Then
      m.nbpaie=m.nbpaie+1
      Select etebac
      Insert Into etebac (nom,prenom,cguich,cpte,valeur,libelle,cetab) ;
        VALUES ;
        ( Upper(Substr(m.ctampon,31,13)),;
        UPPER(Substr(m.ctampon,45,41)),;
        SUBSTR(m.ctampon,87,5),;
        UPPER(Substr(m.ctampon,92,11)),;
        SUBSTR(m.ctampon,105,14),;
        UPPER(Substr(m.ctampon,119,30)),;
        SUBSTR(m.ctampon,150,5);
        )
      m.montant=Val(Substr(m.ctampon,105,14))/100
      m.combien=m.combien+m.montant
    Endif
    Point=Point+162

  Enddo
  =Fclose(m.nfic)
   oxml.montant=m.combien

  m.nbpaie=m.nbpaie-2
*!*    oxml.nb_transactions = ALLTRIM(STR(monniveau-1))
*!*    oxml.montant= Alltrim(Str(m.combien,15,2))

*!*    oxml.nb_transactions=Alltrim(Str(m.nbpaie))

  Endproc







Enddefine

Function calcbic
Local oldselect
oldselect=Select()
Local retour
Select bic
Set Order To etab
Seek etebac.cetab
If Found()
  retour =Alltrim(bic.bic)
Else
  msgbox("l'établissement "+etab.cteab + " n'a pas de bic connu ")
  && stopper la procédure s'il manque un bic **
  *!* cancel
  *!* on shutdown
  *!* close all
  *!* quit
  .
Endif

Select (oldselect)
Return retour
Endfunc




Function jourdelannee
Parameter tdate

*isolate the year and convert it to a string
cYear = Right(Dtoc(tdate),2)
firstjan = Ctod("01/01/" + cYear)

*calculate the sequential number of the day
jday = tdate-firstjan+1
Return Alltrim(Str(jday))














Function lirenombre(sStr As Variant )

public sCaption As String
LOCAL in_i    As Integer
LOCAL sString As String
LOCAL sCar    As String
    sString = CStr(sStr)
    in_i = 1
     Do While in_i <= Len(sString)
        sCar = substr(sString, in_i, 1)
        If Asc(sCar) < 48 Or Asc(sCar) > 57 Then
            sString = substr(sString, 1, in_i) + substr(sString, in_i + 1, Len(sString))
            in_i = in_i - 1
        EndIf
        in_i = in_i + 1
   enddo
    lirenombre = sString
  RETURN lirenombre
EndFunc



Function lirenumerocompte(sStr As Variant)
LOCAL in_i    As Integer
LOCAL sString As String
LOCAL iNb     As Integer
LOCAL sCar    As String
LOCAL sTemp   As String
    sString = CStr(sStr)
    in_i = 1
    Do While in_i <= Len(sString)
        sTemp = substr(sString, in_i, 1)
        If Asc(sTemp) < 48 Or Asc(sTemp) > 57 Then
            If Asc(sTemp) >= 65 Or Asc(sTemp) <= 90 Then
                iNb = Asc(sTemp) - 64
                   If iNb > 9 Then
                 iNb = iNb - 9
                 endif
                If iNb > 9 Then
                iNb = iNb - 8
                endif
                sCar = CStr(iNb)
                sString = substr(sString, 1, in_i - 1) + sCar + substr(sString, in_i + 1, Len(sString))
            Else
                If Asc(sTemp) >= 97 Or Asc(sTemp) <= 122 Then
                    iNb = Asc(sTemp) - 96
                    If iNb > 9 Then
                    iNb = iNb - 9
                    endif
                    If iNb > 9 Then
                    iNb = iNb - 8
                    endif
                     sCar = CStr(iNb)
                    sString = substr(sString, 1, in_i - 1) + sCar + substr(sString, in_i + 1, Len(sString))
                Else
                    sString = substr(sString, 1, in_i) + substr(sString, in_i + 1, Len(sString))
                   in_i = in_i - 1
                EndIf
            Endif
        EndIf
       in_i = in_i + 1
    enddo
     lirenumerocompte = sString
    RETURN lirenumerocompte
EndFunc



Function lirenumeroiban(sStr As String)

LOCAL in_i       As Integer
LOCAL sString    As String
LOCAL sStringRes As String
LOCAL iNb        As Integer
LOCAL sCar       As String
LOCAL sTemp      As String

    sString = sStr
    sStringRes = ""
    in_i = 1
      Do While in_i <= Len(sString)
        sTemp = substr(sString, in_i, 1)
        If Asc(sTemp) < 48 Or Asc(sTemp) > 57 Then
            If Asc(sTemp) >= 65 Or Asc(sTemp) <= 90 Then
                iNb = Asc(sTemp) - 55
                sCar = CStr(iNb)
                sStringRes = sStringRes + sCar
            Else
                If Asc(sTemp) >= 97 Or Asc(sTemp) <= 122 Then
                    iNb = Asc(sTemp) - 87
                    sCar = CStr(iNb)
                    sStringRes = sStringRes + sCar
                Else
                EndIf
            EndIf
        Else
            sStringRes = sStringRes + substr(sString, in_i, 1)
        EndIf
        in_i = in_i + 1
    ENDDO
     lirenumeroiban = sStringRes
RETURN lirenumeroiban
EndFunc





Function Calculcleiban(sCodePays As String, sRib As String)

LOCAL tCodePays   As String
LOCAL tRib        As String
LOCAL tConcat     As String
LOCAL in_i        As Integer
LOCAL sRetenue    As Variant
LOCAL sCle        As Variant
LOCAL iNbInterm   As Variant
LOCAL sStrInterm  As Variant
LOCAL iCodeNum    As Integer
LOCAL sCodeStr    As String
LOCAL tIBAN       As String

    tRib = lirenumeroiban(sRib)
    tCodePays = lirenumeroiban(sCodePays)

    If Len(sCodePays) <> 2 Then
        MESSAGEBOX("Le Code Pays n'a pas 2 lettres", vbCritical, Trim(sCaption))
        Calculcleiban = ""
        Exit Function
    EndIf

    tConcat = tRib + tCodePays + "00"

    in_i = 1
    sRetenue = ""

    Do While in_i <= Len(tConcat)
        sStrInterm = sRetenue + substr(tConcat, in_i, 9)
        iNbInterm = INT(val(sStrInterm))
        sCle = calculmodulo(iNbInterm, 97)
        sRetenue = cstr(sCle)
        in_i = in_i + 9
    enddo

    iCodeNum = 98 - calculmodulo(sCle, 97)

    If iCodeNum < 10 Then
        sCodeStr = "0" + ALLTRIM(STR(iCodeNum))
    Else
        sCodeStr = iCodeNum
    EndIf

           scodestr=cstr(sCodeStr )


    tIBAN = sCodePays + sCodeStr + " " + substr(sRib, 1, 4)
    tIBAN = tIBAN + " " + substr(sRib, 5, 4)
    tIBAN = tIBAN + " " + substr(sRib, 9, 4)
    tIBAN = tIBAN + " " + substr(sRib, 13, 4)
    tIBAN = tIBAN + " " + substr(sRib, 17, 4)
    tIBAN = tIBAN + " " + substr(sRib, 21, 3)

    Calculcleiban = tIBAN
RETURN Calculcleiban
EndFunc


Function FormatRib(sCodeBanque As String, sCodeGuichet As String, sNoCompte As String, sCleRib As String)

LOCAL tCodeBanque     As String
LOCAL tCodeGuichet    As String
LOCAL tNoCompte       As String
LOCAL tCleRib         As String
LOCAL tCodeStr        As String
LOCAL tRib            As String
LOCAL iNbCleRib       As Integer

    tCodeBanque = lirenombre(sCodeBanque)
    tCodeGuichet = lirenombre(sCodeGuichet)
    tNoCompte = lirenumerocompte(sNoCompte)
    tCleRib = lirenombre(sCleRib)

    iNbCleRib = MOD(sCleRib,100)

    If tCleRib < 10 Then
        tCodeStr = "0" + iNbCleRib
    Else
        tCodeStr = iNbCleRib
    EndIf

    tRib = sCodeBanque + sCodeGuichet + sNoCompte + tCodeStr

    FormatRib = tRib
RETURN formatrib
EndFunc




Function calculmodulo(x As Variant, y As Variant) As Variant

*!*      calculmodulo = x - (Int(x / y) * y)
   calculmodulo  =  MOD(x,y)
RETURN calculmodulo
ENDFUNC


FUNCTION cstr(truc)
IF VARTYPE(truc)=="N"
RETURN ALLTRIM(STR(truc))
ELSE
RETURN truc
endif
ENDFUNC

FUNCTION cvar(toto)
IF VARTYPE(toto)=="C"
RETURN VAL(toto)
ELSE
RETURN toto
ENDIF
endfunc


function clerib(nu_compte)
local premier,deuxieme,troisime,prerest,deuxrest,troisrest,valretour
***********************************************************************
*  la fonction doit recevoir le numéro de compte en parametres
*  5 digits pour le code établissement
*  5 digits pour le code guichet
*  11 digits pour le numéro de compte   soit 21 digits au total
*  la clé rib est renvoyé par la fonction en numérique
*
*  Pour les comptes CCP les lettres sont remplacées par des chiffres
*  selon la convention ci_dessous
*
*    A=1   j=1 b=2 k=2 etc..etc
*
*
*
*
*
*
***********************************************************************
* changement des lettres en chiffres grace à la fonction strtran
nu_compte=ChrTran(nu_compte,"AJBKSCLTDMUENVFOWGPXHQYIRZ","11222333444555666777888999")
* vérification du numéro de compte 21 digits en tout
if len(nu_compte)#21
  em_message(" Numéro de compte non valide")
  return "0"
endif
valretour="0"
* calcul de la clé
nu_compte=nu_compte+"00"
premier=substr(nu_compte,1,7)
deuxieme=substr(nu_compte,8,8)
troisieme=substr(nu_compte,16,8)
prerest=alltrim(str(mod(val(premier),97)))
deuxieme=prerest+deuxieme
deuxrest=alltrim(str(mod(val(deuxieme),97)))
troisieme=deuxrest+troisieme
troisrest=alltrim(str(mod(val(troisieme),97)))

valretour=97-val(troisrest)
valretour=alltrim(str(valretour))

return PADL(valretour,2,"0")


Commentaires
le 28/06/2014, Francis Faure a écrit :
Eric,
De grands mercis pour cette contribution.
Cordialement
Francis

le 29/06/2014, eric leissler a écrit :
Bonjour Francis ,
De rien.
J'aurais voulu faire mieux mais pas trop de temps en ce moment.
Le code peut surement être afiné.
Cordialement
Eric

le 30/06/2014, raypo a écrit :
Bonjour Francis,

Vous etes mon espoir dans la tache que je dois bientot rendre, je dois justement etablir un fichier XML pour des prelevements SEPA, mais je ne comprend absolument rien...Pouvez vous juste m’expliquer comment rentrer ce code afin de pouvoir transformer mon fichier en XML.
Je vous remercie d’avance

le 01/07/2014, Francis Faure a écrit :
Raypo,
Eric a publié sa solution répondant à son problème c'est à dire de reprendre un fichier etabc/cfonb existant pour le transformer en SEPA xml.
Mon besoin n'est pas identique car je génère directement les prélèvements SEPA xml avec des configurations différentes de la solution de Eric (un lot contenant plusieurs prélèvements groupés, des prélèvements récurrents et une gestion différente du "en to end" qui est fixe dans sa solution.)
Grâce la contrib de Eric: Je me suis donc facilement créé une petite classe pour cela. Eric donne en pdf les spécifications compléte a lire en premier et il donne aussi un exemple de fichier xml et son approche : tout cela est très précieux.
Maintenant avec tout cela si vous ne vous en sortez pas avec la spécification et le xml et selon vos besoins qui peuvent être différents : dans ce cas c'est une prestation, hors du champ associatif , qui est a envisager.
Cordialement
Francis


www.atoutfox.org - Site de la Communauté Francophone des Professionnels FoxPro - v3.4.0 - © 2004-2024.
Cette page est générée par un composant COM+ développé en Visual FoxPro 9.0-SP2-HF3