From f303ba0fb18e36ba7feb73129fb2217257618488 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 9 Mar 2026 13:26:16 -0400 Subject: [PATCH] =?UTF-8?q?Initialisation=20du=20d=C3=A9p=C3=B4t=20NQFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 ++ CHANGELOG.md | 5 ++ CONTRIBUTING.md | 8 ++ LICENSE | 1 + README.md | 28 +++++++ demonstration/README.md | 19 +++++ demonstration/facture-demo.json | 19 +++++ demonstration/generer-facture-pdf.py | 45 ++++++++++ exemples/facture-exemple.json | 19 +++++ exemples/import-erp-exemple.md | 15 ++++ exemples/qr-payload-exemple.json | 3 + gouvernance/evolution.md | 9 ++ gouvernance/principes.md | 7 ++ gouvernance/proposition.md | 5 ++ outils/generer-qr.py | 21 +++++ outils/requirements.txt | 3 + outils/valider-json.py | 22 +++++ specification/decisions.md | 19 +++++ specification/format-qr.md | 20 +++++ specification/nqfs-1.0.md | 119 +++++++++++++++++++++++++++ specification/schema-nqfs-1.0.json | 38 +++++++++ 21 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 demonstration/README.md create mode 100644 demonstration/facture-demo.json create mode 100755 demonstration/generer-facture-pdf.py create mode 100644 exemples/facture-exemple.json create mode 100644 exemples/import-erp-exemple.md create mode 100644 exemples/qr-payload-exemple.json create mode 100644 gouvernance/evolution.md create mode 100644 gouvernance/principes.md create mode 100644 gouvernance/proposition.md create mode 100755 outils/generer-qr.py create mode 100644 outils/requirements.txt create mode 100755 outils/valider-json.py create mode 100644 specification/decisions.md create mode 100644 specification/format-qr.md create mode 100644 specification/nqfs-1.0.md create mode 100644 specification/schema-nqfs-1.0.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c82a3dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.pyc +.venv/ +env/ +dist/ +build/ +*.png +*.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f69bb4a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Journal des modifications + +## 1.0.0 +- première publication de la structure du projet +- première version de la spécification NQFS diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9158921 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,8 @@ +# Contribution + +Les contributions sont bienvenues. + +Principes : +- préserver la simplicité +- préserver la compatibilité ascendante autant que possible +- éviter les ajouts qui alourdissent inutilement la norme diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..093e635 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +Creative Commons Attribution 4.0 International (CC BY 4.0) diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed26401 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Norme québécoise de facturation simple + +La **NQFS** est une proposition de norme ouverte permettant d’encoder, dans un code QR, les données essentielles d’une facture afin de faciliter son importation automatique dans un progiciel de gestion intégré ou un logiciel comptable. + +## Objectif + +Réduire la ressaisie manuelle des factures fournisseurs en fournissant un format de données simple, structuré et directement exploitable. + +## Principe + +Chaque facture peut contenir un code QR dont le contenu commence par : + +NQFS: + +suivi d’un objet JSON compact contenant les champs utiles à l’importation comptable. + +## Portée + +La norme couvre : +- l’encodage de données essentielles de facture +- le format JSON +- le format de contenu du code QR + +La norme ne couvre pas : +- les lignes détaillées de facture +- les paiements +- la signature cryptographique +- les réseaux d’échange diff --git a/demonstration/README.md b/demonstration/README.md new file mode 100644 index 0000000..4160962 --- /dev/null +++ b/demonstration/README.md @@ -0,0 +1,19 @@ +# Démonstration + +## Préparer l’environnement + +python3 -m venv .venv +source .venv/bin/activate +pip install -r outils/requirements.txt + +## Valider le JSON + +./outils/valider-json.py demonstration/facture-demo.json + +## Générer le QR + +./outils/generer-qr.py demonstration/facture-demo.json demonstration/facture-demo-qr.png + +## Générer une facture PDF de démonstration + +./demonstration/generer-facture-pdf.py demonstration/facture-demo.json demonstration/facture-demo.pdf diff --git a/demonstration/facture-demo.json b/demonstration/facture-demo.json new file mode 100644 index 0000000..83028c7 --- /dev/null +++ b/demonstration/facture-demo.json @@ -0,0 +1,19 @@ +{ + "spec": "NQFS-1.0", + "supplier_name": "Chezlepro Inc", + "supplier_tax_id": "1234567890", + "supplier_email": "facturation@chezlepro.ca", + "supplier_phone": "+1-450-555-1234", + "invoice_number": "INV-2026-00312", + "invoice_date": "2026-03-09", + "due_date": "2026-04-08", + "currency": "CAD", + "subtotal": 1000.00, + "tps_rate": 0.05, + "tps_amount": 50.00, + "tvq_rate": 0.09975, + "tvq_amount": 99.75, + "total": 1149.75, + "reference": "BC-2026-0312", + "description": "Services informatiques" +} diff --git a/demonstration/generer-facture-pdf.py b/demonstration/generer-facture-pdf.py new file mode 100755 index 0000000..0b913b7 --- /dev/null +++ b/demonstration/generer-facture-pdf.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import json +import sys +from pathlib import Path +import qrcode +from reportlab.lib.pagesizes import letter +from reportlab.pdfgen import canvas + +if len(sys.argv) != 3: + print("Usage: generer-facture-pdf.py ") + sys.exit(1) + +src = Path(sys.argv[1]) +pdf_out = Path(sys.argv[2]) +qr_out = pdf_out.with_suffix(".png") + +with open(src, "r", encoding="utf-8") as f: + data = json.load(f) + +payload = "NQFS:" + json.dumps(data, ensure_ascii=False, separators=(",", ":")) +img = qrcode.make(payload) +img.save(qr_out) + +c = canvas.Canvas(str(pdf_out), pagesize=letter) +w, h = letter + +c.setFont("Helvetica-Bold", 14) +c.drawString(50, h - 50, data["supplier_name"]) + +c.setFont("Helvetica", 11) +c.drawString(50, h - 90, f"Facture : {data['invoice_number']}") +c.drawString(50, h - 110, f"Date : {data['invoice_date']}") +c.drawString(50, h - 130, f"Échéance : {data.get('due_date', '')}") +c.drawString(50, h - 150, f"Devise : {data['currency']}") +c.drawString(50, h - 170, f"Sous-total : {data['subtotal']:.2f}") +c.drawString(50, h - 190, f"TPS : {data['tps_amount']:.2f}") +c.drawString(50, h - 210, f"TVQ : {data['tvq_amount']:.2f}") +c.drawString(50, h - 230, f"Total : {data['total']:.2f}") +c.drawString(50, h - 250, f"Référence : {data.get('reference', '')}") +c.drawString(50, h - 270, f"Description : {data.get('description', '')}") + +c.drawImage(str(qr_out), w - 180, h - 240, width=120, height=120) + +c.save() +print(f"PDF généré : {pdf_out}") diff --git a/exemples/facture-exemple.json b/exemples/facture-exemple.json new file mode 100644 index 0000000..83028c7 --- /dev/null +++ b/exemples/facture-exemple.json @@ -0,0 +1,19 @@ +{ + "spec": "NQFS-1.0", + "supplier_name": "Chezlepro Inc", + "supplier_tax_id": "1234567890", + "supplier_email": "facturation@chezlepro.ca", + "supplier_phone": "+1-450-555-1234", + "invoice_number": "INV-2026-00312", + "invoice_date": "2026-03-09", + "due_date": "2026-04-08", + "currency": "CAD", + "subtotal": 1000.00, + "tps_rate": 0.05, + "tps_amount": 50.00, + "tvq_rate": 0.09975, + "tvq_amount": 99.75, + "total": 1149.75, + "reference": "BC-2026-0312", + "description": "Services informatiques" +} diff --git a/exemples/import-erp-exemple.md b/exemples/import-erp-exemple.md new file mode 100644 index 0000000..4b1a31f --- /dev/null +++ b/exemples/import-erp-exemple.md @@ -0,0 +1,15 @@ +# Exemple d’importation ERP + +À partir du QR, un ERP peut créer automatiquement : + +- fournisseur : Chezlepro Inc +- numéro de facture : INV-2026-00312 +- date facture : 2026-03-09 +- échéance : 2026-04-08 +- devise : CAD +- montant avant taxes : 1000.00 +- TPS : 50.00 +- TVQ : 99.75 +- total : 1149.75 +- référence : BC-2026-0312 +- description : Services informatiques diff --git a/exemples/qr-payload-exemple.json b/exemples/qr-payload-exemple.json new file mode 100644 index 0000000..8017d72 --- /dev/null +++ b/exemples/qr-payload-exemple.json @@ -0,0 +1,3 @@ +{ + "qr_payload": "NQFS:{\"spec\":\"NQFS-1.0\",\"supplier_name\":\"Chezlepro Inc\",\"supplier_tax_id\":\"1234567890\",\"supplier_email\":\"facturation@chezlepro.ca\",\"supplier_phone\":\"+1-450-555-1234\",\"invoice_number\":\"INV-2026-00312\",\"invoice_date\":\"2026-03-09\",\"due_date\":\"2026-04-08\",\"currency\":\"CAD\",\"subtotal\":1000,\"tps_rate\":0.05,\"tps_amount\":50,\"tvq_rate\":0.09975,\"tvq_amount\":99.75,\"total\":1149.75,\"reference\":\"BC-2026-0312\",\"description\":\"Services informatiques\"}" +} diff --git a/gouvernance/evolution.md b/gouvernance/evolution.md new file mode 100644 index 0000000..b9b0129 --- /dev/null +++ b/gouvernance/evolution.md @@ -0,0 +1,9 @@ +# Évolution de la norme + +Les changements proposés doivent : +- préserver la simplicité +- justifier leur utilité concrète +- éviter de compromettre la lisibilité du QR +- éviter les dépendances inutiles + +Les évolutions incompatibles DOIVENT entraîner une nouvelle version majeure. diff --git a/gouvernance/principes.md b/gouvernance/principes.md new file mode 100644 index 0000000..63406db --- /dev/null +++ b/gouvernance/principes.md @@ -0,0 +1,7 @@ +# Principes directeurs + +- simplicité d’implantation +- utilité comptable immédiate +- absence de dépendance à une plateforme centrale +- compatibilité avec les factures actuelles +- lisibilité humaine et machine diff --git a/gouvernance/proposition.md b/gouvernance/proposition.md new file mode 100644 index 0000000..c2794b0 --- /dev/null +++ b/gouvernance/proposition.md @@ -0,0 +1,5 @@ +# Proposition + +La NQFS est proposée comme norme ouverte, simple et volontaire visant à réduire la ressaisie manuelle des factures fournisseurs. + +Elle est conçue pour être implantée rapidement dans les logiciels de facturation, les ERP et les logiciels comptables. diff --git a/outils/generer-qr.py b/outils/generer-qr.py new file mode 100755 index 0000000..da09a30 --- /dev/null +++ b/outils/generer-qr.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import json +import sys +from pathlib import Path +import qrcode + +if len(sys.argv) != 3: + print("Usage: generer-qr.py ") + sys.exit(1) + +src = Path(sys.argv[1]) +dst = Path(sys.argv[2]) + +with open(src, "r", encoding="utf-8") as f: + data = json.load(f) + +payload = "NQFS:" + json.dumps(data, ensure_ascii=False, separators=(",", ":")) +img = qrcode.make(payload) +img.save(dst) + +print(f"QR généré : {dst}") diff --git a/outils/requirements.txt b/outils/requirements.txt new file mode 100644 index 0000000..260e710 --- /dev/null +++ b/outils/requirements.txt @@ -0,0 +1,3 @@ +jsonschema +qrcode[pil] +reportlab diff --git a/outils/valider-json.py b/outils/valider-json.py new file mode 100755 index 0000000..9efe4d9 --- /dev/null +++ b/outils/valider-json.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import json +import sys +from pathlib import Path +from jsonschema import validate + +if len(sys.argv) != 2: + print("Usage: valider-json.py ") + sys.exit(1) + +base = Path(__file__).resolve().parent.parent +schema_path = base / "specification" / "schema-nqfs-1.0.json" +data_path = Path(sys.argv[1]).resolve() + +with open(schema_path, "r", encoding="utf-8") as f: + schema = json.load(f) + +with open(data_path, "r", encoding="utf-8") as f: + data = json.load(f) + +validate(instance=data, schema=schema) +print("JSON valide selon NQFS-1.0") diff --git a/specification/decisions.md b/specification/decisions.md new file mode 100644 index 0000000..7cc733a --- /dev/null +++ b/specification/decisions.md @@ -0,0 +1,19 @@ +# Décisions de conception + +## Choix retenus +- JSON plat +- préfixe NQFS: +- taux et montants de taxes +- exclusion des lignes de facture + +## Raisons +- simplicité d’implantation +- compatibilité ERP +- robustesse du code QR +- réduction de la ressaisie + +## Choix explicitement rejetés pour la version 1 +- items détaillés +- signature cryptographique +- mécanisme de paiement +- registre central diff --git a/specification/format-qr.md b/specification/format-qr.md new file mode 100644 index 0000000..af46be2 --- /dev/null +++ b/specification/format-qr.md @@ -0,0 +1,20 @@ +# Format du code QR + +## Règle générale + +Le contenu du code QR DOIT respecter la forme suivante : + +NQFS: + +## Encodage + +- UTF-8 +- JSON compacté sans espaces inutiles + +## Exemple + +NQFS:{"spec":"NQFS-1.0","supplier_name":"Chezlepro Inc","invoice_number":"INV-2026-00312","invoice_date":"2026-03-09","currency":"CAD","subtotal":1000,"tps_rate":0.05,"tps_amount":50,"tvq_rate":0.09975,"tvq_amount":99.75,"total":1149.75} + +## Recommandation + +Le code QR DEVRAIT rester suffisamment compact pour assurer une lecture fiable sur facture papier et PDF. diff --git a/specification/nqfs-1.0.md b/specification/nqfs-1.0.md new file mode 100644 index 0000000..53dc54e --- /dev/null +++ b/specification/nqfs-1.0.md @@ -0,0 +1,119 @@ +# NQFS-1.0 + +## 1. Objet + +La NQFS définit un format simple pour encoder, dans un code QR, les données essentielles d’une facture afin de permettre leur ingestion automatique par un ERP ou un logiciel comptable. + +## 2. Portée + +La norme couvre : +- le contenu logique minimal +- le format JSON +- le préfixe d’identification +- les règles de validation de base + +La norme ne couvre pas : +- les lignes détaillées de facture +- les paiements +- la signature cryptographique +- la transmission réglementaire +- l’archivage documentaire + +## 3. Préfixe de détection + +Le contenu du QR DOIT commencer par : + +NQFS: + +Le texte qui suit DOIT être un document JSON valide encodé en UTF-8. + +## 4. Champs obligatoires + +- spec +- supplier_name +- invoice_number +- invoice_date +- currency +- subtotal +- tps_rate +- tps_amount +- tvq_rate +- tvq_amount +- total + +## 5. Champs recommandés + +- supplier_tax_id +- supplier_email +- supplier_phone +- due_date +- reference +- description + +## 6. Contraintes + +### 6.1 spec +Chaîne. Exemple : NQFS-1.0 + +### 6.2 supplier_name +Chaîne non vide. + +### 6.3 invoice_number +Chaîne non vide. + +### 6.4 invoice_date +Date au format ISO 8601 : YYYY-MM-DD + +### 6.5 due_date +Date au format ISO 8601 : YYYY-MM-DD + +### 6.6 currency +Code alphabétique de devise sur trois caractères. Exemple : CAD + +### 6.7 subtotal +Nombre décimal supérieur ou égal à 0. + +### 6.8 tps_rate +Nombre décimal. Exemple : 0.05 + +### 6.9 tps_amount +Nombre décimal supérieur ou égal à 0. + +### 6.10 tvq_rate +Nombre décimal. Exemple : 0.09975 + +### 6.11 tvq_amount +Nombre décimal supérieur ou égal à 0. + +### 6.12 total +Nombre décimal supérieur ou égal à 0. + +## 7. Règles de cohérence + +- total DEVRAIT être égal à subtotal + tps_amount + tvq_amount +- tps_amount DEVRAIT être cohérent avec subtotal × tps_rate +- tvq_amount DEVRAIT être cohérent avec subtotal × tvq_rate + +Une tolérance d’arrondi de deux cents est acceptable. + +## 8. Exemple + +{ + "spec": "NQFS-1.0", + "supplier_name": "Chezlepro Inc", + "supplier_tax_id": "1234567890", + "supplier_email": "facturation@chezlepro.ca", + "supplier_phone": "+1-450-555-1234", + "invoice_number": "INV-2026-00312", + "invoice_date": "2026-03-09", + "due_date": "2026-04-08", + "currency": "CAD", + "subtotal": 1000.00, + "tps_rate": 0.05, + "tps_amount": 50.00, + "tvq_rate": 0.09975, + "tvq_amount": 99.75, + "total": 1149.75, + "reference": "BC-2026-0312", + "description": "Services informatiques" +} diff --git a/specification/schema-nqfs-1.0.json b/specification/schema-nqfs-1.0.json new file mode 100644 index 0000000..b432f7b --- /dev/null +++ b/specification/schema-nqfs-1.0.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NQFS 1.0", + "type": "object", + "required": [ + "spec", + "supplier_name", + "invoice_number", + "invoice_date", + "currency", + "subtotal", + "tps_rate", + "tps_amount", + "tvq_rate", + "tvq_amount", + "total" + ], + "properties": { + "spec": { "type": "string" }, + "supplier_name": { "type": "string", "minLength": 1 }, + "supplier_tax_id": { "type": "string" }, + "supplier_email": { "type": "string" }, + "supplier_phone": { "type": "string" }, + "invoice_number": { "type": "string", "minLength": 1 }, + "invoice_date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, + "due_date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, + "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }, + "subtotal": { "type": "number", "minimum": 0 }, + "tps_rate": { "type": "number", "minimum": 0 }, + "tps_amount": { "type": "number", "minimum": 0 }, + "tvq_rate": { "type": "number", "minimum": 0 }, + "tvq_amount": { "type": "number", "minimum": 0 }, + "total": { "type": "number", "minimum": 0 }, + "reference": { "type": "string" }, + "description": { "type": "string" } + }, + "additionalProperties": false +}