Skip to main content

Transformation de fichiers Quicken vers GNU Cash

le contexte

J'utilise Quicken depuis 25 ans; d'abord la version 2001, puis la version 2006. Comme je suis sous Linux, Quicken tourne dans Wine.

La seule chose qui ne fonctionnait pas sont les backups; la fonction "Backup" se plantait, mais je pouvais toujours copier le fichier courant, ainsi que l'exporter.

Malheureusement, depuis une mise à jour de Wine, je constate des comportements bizarre; erreur au lancement (table de "Memorized transactions" tronquée), à l'affichage (transactions dupliquées), mais plus inquiétant, transactions manquantes.

C'est le temps de passer à une solution plus moderne, et surtout, native sous Linux. Mon choix c'est porté sur GNU Cash, dont l'ergonomie est assez similaire à Quicken.

Naturellement, je veux importer mon historique de transactions qui, je dois dire, est peut être plutôt complexe: 110 catégories distinctes et 45 comptes (bancaires, amortissement hypothécaire, comptes d'investissement avec des "parts" et un prix unitaire variable, etc..).

Note: dans le texte, je considère que le lecteur connaît quelques commandes linux de base: vim, od, sed et awk.

Premier essai: export par année

A priori assez simple: dans Quicken, exporter au format QIF, puis importer le QIF dans GNU Cash.

Le format QIF est un format texte, donc modifiable avec un simple éditeur de texte (j'utilise vim), mais aussi exploitable par des commandes de traitement de texte comme sed ou awk / nawk.

Le premier problème était dans Quicken qui plante à l'écriture du fichier d'export. J'ai fini par exporter année par année, soit 25 fichiers, pour un total de 180500 lignes. J'ai une année problématique qui semble corrompue; je ferai un export sous mon ancienne version de Wine pour celle là.

Un autre problème est que Quicken exporte au format Windows; les fins de lignes sont donc "\r\n" au lieu de "\n" pour Linux; facile à traiter avec sed:

$ sed -e 's/\r//' Export2010.QIF

Cependant, l'import n'a pas fonctionné: GNU Cash se plaint d'un format de date incorrect. Une des raisons que j'ai identifié est la présence de slash (/) dans des descriptions de compte. En effet, la description d'un compte est un champ de type D ce qui est également le préfixe pour une date.

DCda/Que Pension Plan  ← Ici le champ D correspond à une description
...
D10/ 1' 9              ← Mais ici, il correspond à une date
...

Pour être certain que toutes les dates sont correctes, j'ai fait un petit script awk qui les corrige:

$ cat date.awk
/^D/	{D=substr($0,2,length($0)-1);
		sub("' ","/200",D);
		sub("'","/20",D);
		sub("/ ","/0",D);
		sub(/^[1-9]\//,"0&",D);
	}
$ sed -e 's/\r//' Export2010.QIF | nawk -f date.awk 
...
D01/01/2009
...
D01/12/2009
...

Le format QIF

Mon idée était d'importer les fichiers QIF dans LibreOffice Calc (l'équivalent de Excel sous Linux) afin de vérifier les transactions: éliminer les doublons et recréer les transactions manquantes. Comme les fichiers QIF sont des fichiers texte, il semblait "facile" de les passer à travers un script awk pour les ressortir en CSV (comma-separated values).

Les fichiers QIF sont organisés sections ("chunks"), avec un début et une fin de section. Le nombre de sections dépend de ce qui a été sélectionné à exporter. Malheureusement le format manque de cohérence, les mêmes balises servant à plusieurs usages; ce qui rend l'exploitation par les commandes de traitement de stream (sed, awk ou nawk) difficile.

Rappel: la commande sed (stream editor) permet d'effectuer des commandes basiques (filtrage, remplacement, ...) sur un flot de caractères. Les commandes awk-like (awk, gawk = Gnu awk, et nawk = new awk), qui prennent leurs noms des 3 créateurs Alfred Aho, Peter Weinberger et Brian Kernighan, sont des commandes de traitement de flot plus élaborées, comportant un langage de programmation.

Les chunks

Un chunk débute par ! et se termine par un autre chunk ou la fin du fichier.

La balise "Autoswitch"

Cette balise est utilisée pour lister les comptes, si l'option d'exporter la liste des comptes a été sélectionnée.

$ sed -e 's/\r//' Export*.QIF | grep "^!" | sort -u
!Option:AutoSwitch  ← Début de la liste des comptes 
...
!Clear:AutoSwitch   ← Fin de la liste des comptes 
...

On s'attendrait donc à ne voir cette balise qu'une seule fois; et bien non! Elle apparaît 2 fois dans l'export, comme on le voit sur cet exemple:

$ egrep '^!' -n Export2010.QIF 
1:!Type:Cat             ← on liste les catégories 
770:!Option:AutoSwitch  ← début d'une liste.. 
771:!Account            ← c'est une liste de comptes 
931:!Clear:AutoSwitch   ← fin de liste 
932:!Option:AutoSwitch  ← de nouveau, début d'une liste.. 
933:!Account            ← c'est une liste de comptes... 
937:!Type:Bank          ← ... bancaires 
...
938:!Account
942:!Type:Bank
943:!Account
...                     ← à partir d'ici, on trouve également des transactions 
3820:!Type:CCard        ← puis vient la liste des cartes de crédit 
3821:!Account
...
5291:!Type:Cash         ← puis la liste des cartes des comptes liquides 
5292:!Account
...                     ← etc, mais pas de fin de liste ! 
Le premier chunk, cat, décrit les catégories de transaction, c'est-à-dire les différentes "case" dans lesquelles vous pouvez classer les sommes:
$ head -800 Export2010.QIF | sed -e 's/\r//'
!Type:Cat
NASS GRP             ← N (name) = nom de la catégorie 
DAssurance Groupe    ← D = description 
E                    ← type de catégorie; dépense (E = expense) ou gain (I  = income) 
^                    ← séparateur de fin 
...
NAuto                ← nom de la catégorie 
DAutomobile Expenses ← description 
E                    ← type de catégorie 
^                    ← séparateur de fin 
NAuto:Fuel           ← nom de la catégorie (ici une sous-catégorie) 
DAuto Gas & Oil
T                    ← T (tax): cette catégorie est reliée à un compte de taxes 
R5728                ← Compte de taxes relié 
E
^
...
Certaines catégories comporte un buget mensuel; je pense qu'il est ajouté automatiquement à la première saisie. Personnellement, je ne l'utilise pas. On voit donc 12 balises B qui se suivent:
NAuto:Insurance
DAuto Insurance
B-74.17              ← B (budget mensuel): somme pour le premier mois 
B0.00                ← mois suivant 
B0.00                ← etc... 
B0.00
B0.00
B0.00
B0.00
B0.00
B0.00
B0.00
B0.00
B0.00
T
R5760
E
^
...

Second essai: export par compte

En exportant par compte plutôt que par année, les balises AutoSwitch et Account ne sont plus nécessaires; le fichier ne contient qu'une seule balise identifiant le type de compte.

Voici les identifiants et leur description (source: https://github.com/Gnucash/gnucash/blob/stable/gnucash/import-export/qif-imp/file-format.txt)

$ sed -e 's/\r//' Export*.QIF | grep "^!" | sort -u
!Type:Bank          ← compte bancaire 
!Type:Cash          ← argent liquide 
!Type:Cat           ← catégories de transactions 
!Type:CCard         ← carte de crédit 
!Type:Invst         ← compte d'investissement 
!Type:Memorized     ← transactions mémorisées, pour faciliter la saisie 
!Type:Oth A         ← autre compte d'actifs 
!Type:Oth L         ← autre compte de passifs (dettes, hypothèques) 
!Type:Security      ← compte de placements (actions, obligations) 
!Type:Prices        ← prix pour les compte de placements (par exemple prix unitaire de l'action) 
Voici un exemple:
!Type:Bank                        ← Ensuite viennent les transactions: 
D3/22'10                          ← Date de transaction: 22 mars 2010 
U0.00                             ← montant 
T0.00                             ← montant ?? (je n'ai jamais vu de différences) 
CX                                ← C = Cleared; concilliation bancaire 
POpening Balance                  ← P = Payee; dans ce cas, description 
L[CELI]                           ← Catégorie ou compte (*) 
^

(*) Quand il s'agit d'une catégorie, elle apparaît directement: LInterest Inc. Quand il s'agit d'un transfert vers un autre compte, c'est le compte destination qui apparaît entre les crochets: L[CELI]

Simplification

Voici donc un premier script awk qui affiche les transactions:

/\!Account/	{mode=0;acc_name="";acc_type="";acc_desc="";}# print "Mode 0 (identification du compte)"}
/\!Type:/	{mode=1;D="";N="";T="";U="";P="";C="";M="";L="";}#print "Mode 1 (description d'une transaction)"}
/^D/	{D=substr($0,2,length($0)-1);
		if(mode==0) acc_desc=D;	# Description du compte
		if(mode==1) {sub("' ","/200",D); sub("'","/20",D); sub("/ ","/0",D); sub(/^[1-9]\//,"0&",D);} # Date
	}
/^N/	{N=substr($0,2,length($0)-1); if(mode==0) acc_name=N; }	# Mode 0: Nom du compte. Mode 1: type de transaction / numéro de chèque
/^T/	{T=substr($0,2,length($0)-1); if(mode==0) acc_type=T; }	# Mode 0: Type de compte. Mode 1: Montant de la transaction
/^U/	{U=substr($0,2,length($0)-1);}	# Mode 0: N/A. Mode 1: Montant (autre, inutilisé)
/^P/	{P=substr($0,2,length($0)-1);}	# Mode 0: N/A. Mode 1: Destinataire
/^C/	{C=substr($0,2,length($0)-1);}	# Mode 0: N/A. Mode 1: Reconcillié ?
/^M/	{M=substr($0,2,length($0)-1);}	# Mode 0: N/A. Mode 1: Memo
/^L/	{L=substr($0,2,length($0)-1);}	# Mode 0: N/A. Mode 1: Catégorie ou compte destinataire
/^\^/	{if (mode==1) {
		print "Compte:"acc_type,acc_name,acc_desc;
		DB=""; CB=""; if(substr(T,1,1)=="-") {DB=substr(T,2);} else {CB=T};
		print D,"Type:"N,"Destinataire:"P,"Débit:"DB,"Crédit:"CB,"Checked:"C,"Memo: "M,"Categorie: "L;
		}
	}

C'est loin d'être élégant, mais ça donne le résultat. Avec un problème cependant: le manque de support des caractères supérieurs à 127!

Gestion des caractères accentués

Voici un exemple:

Destinataire:Ad�le ; Débit: ; Crédit:

Le fichier source contient le caractère "è" qui n'est pas bien représenté. Pourtant, à l'affichage, awk représente bien les caractères "é".

La raison est que le "è" provient du fichier source alors que le "é" provient du script awk. Or, pour awk, les caractères accentués sont au delà de 127 (soit 7F en hexa) et représentés par 2 octets; c'est la norme UTF-8. Donc le caractère "è", qui a la valeur 232 dans le fichier source (soit E8 en hexa), ne correspond pas à la norme UTF-8; dans ce cas, la norme précise qu'il doit être représenter par "�".

La commande od (octal dump) permet de voir le détail:

$ sed -e 's/\r//' Export2018.QIF.trans | awk -f trans.awk | od -ct x1 | more
...
0000060  69  6e  61  74  61  69  72  65  3a  41  64  e8  6c  65  20  3b
          i   n   a   t   a   i   r   e   :   A   d 350   l   e       ;
0000100  20  44  c3  a9  62  69  74  3a  20  3b  20  43  72  c3  a9  64
              D 303 251   b   i   t   :       ;       C   r 303 251   d
...

Le è de Adèle est représenté par 1 octet (en hexa: e8), alors que les caractères é de Crédit et Débit sont représentés par 2 octets (en hexa: c3 e9).

Note: la commande od ne sait pas non plus afficher correctement les caractères accentués et affiche à la place les valeurs en octal (303 octal = c3 hexa).

La solution passe à nouveau par sed, grâce à un petit script de transformation que j'ai nommé trans.sed. Les commandes s/\oXXX/Y/g/ signifient : substituer (s) la valeurs XXX (représentée en octal) par le caractères Y, pour toutes les occurences (g = globalement).

s/\r//g
s/\o311/\o303\o211/g  ← É 
s/\o350/\o303\o250/g  ← é 
s/\o351/\o303\o251/g  ← è 
s/\o352/\o303\o252/g  ← ê 
s/\o353/\o303\o253/g  ← ë 
s/\o340/\o303\o240/g  ← à 
s/\o347/\o303\o247/g  ← ç 
s/\o364/\o303\o264/g  ← ô 
s/\o373/\o303\o273/g  ← û 
s/\o260/um/g          ← signe °; transforme N° en Num 

Comme le script a été créé avec vim qui supporte UTF-8, les caractères accentués du script sont bien UTF-8, comme on le voit sur ce dump:

Note: -w13 signifie simplement "afficher sur 13 octets de large", pour que les lignes soit bien alignées.

$ od -ct x1 -w13 trans.sed              ┌┈┈┈┈┈┐  ←  le É est représenté par 2 octets, conformément à UTF-8 
0000000   s   /   \   o   3   1   1   / 303 211   /   g  \n
         73  2f  5c  6f  33  31  31  2f  c3  89  2f  67  0a
0000015   s   /   \   o   3   5   0   / 303 250   /   g  \n
         73  2f  5c  6f  33  35  30  2f  c3  a8  2f  67  0a
0000032   s   /   \   o   3   5   1   / 303 251   /   g  \n
         73  2f  5c  6f  33  35  31  2f  c3  a9  2f  67  0a
...

La commande devient:

$ sed -f trans.sed Export.Compte.QIF | awk -f trans.awk > Export.Compte.csv

Ou pour traiter tous les fichiers:

$ echo > Export.csv
$ for F in Export*.QIF.trans; do sed -f trans.sed $F | awk -f trans.awk >> Export.csv; done

Le résultat peut maintenant être importé dans Excel ou LibreOffice Calc pour une mise en forme.

La cas des ventilations (splits)

Lorsqu'une transaction est ventilée dans plusieurs comptes elle se présente ainsi:

D... ← date 
U... ← montant 
T... ← montant 
CX   ← concillié 
N... ← type de transaction 
P... ← Payee 
M... ← Mémo 
L... ← Catégorie 
S... ← Split 1: Catégorie 
E... ←         Mémo 
$... ←         Montant 
S... ← Split 2: Catégorie 
E... ←         Mémo 
$... ←         Montant 
...
^

Dès l'apparition du S, il faut afficher la ligne.

Le cas des soldes d'ouverture

Lorsqu'un compte est créé, Quicken crée une transaction avec le solde d'ouverture. Dans le fichier QIF, elle se présente sous la forme suivante:

!Type:Cash       ← type de compte
!Account
NCash US $       ← nom du compte  
TCash
^
!Type:Cash
D1/ 1'14         ← date de l'ouverture 
U0.00            ← solde d'ouverture 
T0.00
POpening Balance ← Commentaire automatique 
L[Cash US $]     ← Nom du compte 
^

Transformation en double écriture

GnuCash est basé sur le principe de la double écriture. Toute écriture est constituée de deux transactions: un compte est débité et un autre compte est crédité.

Par exemple, une dépense courante comme l'achat d'une pizza avec une carte de crédit Visa s'écrira en deux transactions:

  • Débiter le compte Passif:VISA
  • Créditer le compte Dépenses:Alimentation

Ce principe est un peu différent de Quicken, qui utilise la notion de catégories: on débite le compte VISA en indiquant dans quelle catégorie affecter la dépense. Cependant, la double écriture est aussi tout à fait possible dans Quicken.

Durant mes 25 années d'utilisation de Quicken, je n'ai pas été toujours constant. J'ai utilisé autant les catégories que la double écriture, et ce parfois pour le même type de dépense. Précisons que je n'ai pas de formation comptable, juste 2 heures de comptabilités par semaine durant mes études en informatique; et ce n'était pas le cours où j´étais le plus attentif!

L'utilisation de la double écriture est venue d'elle-même, logiquement, par l'analyse des paiements hypothécaires. En effet, je voyais bien qu'une partie du paiement est une forme d'épargne (le capital), l'autre partie est "perdue" (les intérêts). Trois comptes devraient être impliqués: un compte débiteur (le compte courant) et deux comptes créditeur; la banque (qui perçoit les intérêts) et le compte d'actif (le logement), sur lequel va le capital. Lors d'un remboursement anticipé, la somme va intégralement sur le capital.

De plus, dans mon cas, le prêteur hypothécaire permettait de payer les taxes (taxe d'habitation et taxe scolaire) en prélevant une somme à chaque versement, ce que j'ai fait pendant plusieurs années. Dans ce cas, deux autres comptes devraient être impliqués: la ville pour la taxe d'habitation et la commission scolaire pour la taxe scolaire.

Dans mes fichiers QIF, certaines périodes utilisent la double écriture, d'autres non.

Il est assez simple de transformer les écritures simples en écritures doubles.

Écriture simple

!Account                          ← On identifie le compte duquel les transaction suivent 
NCompte courant
DCompte chèque
TBank
^
!Type:Bank
D3/22'10
U-800.00
T-800.00
CX
PBanque ABC                       ← Destinataire (Payee) 
LHypothèque                       ← Catégorie: hypothèque 
^

Écriture double (modifications et ajouts en bleu)

!Account                          ← On identifie le compte débité 
NCompte courant
DCompte chèque
TBank
^
!Type:Bank
D3/22'10
U-800.00
T-800.00
CX
PHypothèque
L[Banque ABC]                     ← On transfert vers le compte Banque ABC 
^
!Account                          ← On identifie le compte crédité 
NBanque ABC
DHypothèque #123456
TBank
^
!Type:Bank
D3/22'10
U800.00
T800.00
CX
PHypothèque
L[Compte courant]                 ← On transfert provenant du compte Compte courant 
^
!Account                          ← On doit retourner au compte précédent 
NCompte courant
DCompte chèque
TBank
^

Cependant, modifier le QIF source reste plus difficile que de modifier le résultat du awk; j'ai donc choisi d'extraire du résultat tout ce qui concerne l'hypothèque, puis d'écrire un script qui duplique les transactions.

Compatibilité avec gnucash-importer

gnucash-importer est un script Python qui ajoute à GnuCash la possibilité d'importer directement des fichiers CSV, sans passer par le menu importation. Cependant, mes fichiers n'étant pas très "propres", j'ai préféré continuer avec awk. Par contre, je m'en suis inspiré pour ordonner les colonnes:

    Pour les transactions, première écriture:
  • date au format MM/DD/YYYY
  • description de la transaction
  • commodity: monnaie utilisée pour la transaction (CAD, Euro, USD)
  • account: compte source
  • memo
  • value: montant de la transaction (positif ou négatif)
    Pour les transactions, seconde écriture:
  • date au format MM/DD/YYYY
  • description de la transaction
  • commodity: monnaie utilisée pour la transaction (CAD, Euro, USD)
  • account: compte destination
  • memo
  • value: montant de la transaction (signe opposé à la première écriture)
    Pour les ventilations:
  • Colonne vide
  • Colonne vide
  • Colonne vide
  • account: compte destination
  • memo
  • value: montant ventilé (signe opposé à la première écriture)

Comments

Popular posts from this blog

Drive replacement for Fostex DMT8-vl

The IDE hard drive on my Fostex DMT8-vl multitrack recorder shows signs of its imminent death; when getting hot, I could not record anymore. Must be said this drive comes from an old Sun Station, and has been replaced because I/O failures were detected by Solaris. It worked at least 5 years in my recorder: not so bad. However, time is now to replace it. The DMT8-vl is not able to handle drives bigger than 8.4 GB. Well, it is able to (the current drive is 15 GB), but only 8.4 GB will be usable. My tought was to use a 8 GB CompactFlash; having no moving parts means no noise, which is quite temptating for a music recording device. I purchased a CompactFlash-IDE adapter on the internet (8$) and I had to build a male-male IDE cable adapter (4$). Unfortunately, this doesn't work. The drive is correctly discovered by the operating system, which proposes to format it ("format IDE?"). After answering "yes", the formating runs pretty fast (faster than on a real drive), ...

Samba: Clients get "system error 1223" (or 123) after a server reboot

Facts: a Linux+Samba server shares anonymously a folder. After a reboot, Win clients could not attach the share drive anymore. C:\>net use \\mylinux\folder Enter the user name for 'mylinux': System error 1223 has occurred. The operation was canceled by the user. C:\>net view \\mylinux\ System error 123 has occurred. The filename, directory name, or volume label syntax is incorrect. The process are present, and tcpdump doesn't provide much information. What's going on? After hours of headscratching, the light came: the firewall was on and no rules for the Samba protocol! Grrr!

Issue with Soundpool MO4

I have a Atari STe with a Soundpool MO4 MIDI extension. It used to work very well, but unfortunatelly doesn't anymore: Cubase still detects it, and I can output MIDI to it but nothing is coming out from any MIDI Out. It took me a while to tackle it (lack of time, lack of tool, other items to play with), but I gave a glance last week-end. The parallel port on the Atari uses only the following signals: Pin 1 : Strobe (Atari -> MO4) Pin 2 : Data 0 (Atari -> MO4) Pin 3 : Data 1 (Atari -> MO4) Pin 4 : Data 2 (Atari -> MO4) Pin 5 : Data 3 (Atari -> MO4) Pin 6 : Data 4 (Atari -> MO4) Pin 7 : Data 5 (Atari -> MO4) Pin 8 : Data 6 (Atari -> MO4) Pin 9 : Data 7 (Atari -> MO4) Pin 11: Busy (MO4 -> Atari) The MO4 also decodes few other pins, but since the Atari doesn't, my guess is the MO4 was also targeted for PC. Inside the box, the MO4 is architectured around a CPLD (IspLSI1016 from Lattice) which contains the logi...