135 lines
5 KiB
Python
135 lines
5 KiB
Python
"""
|
||
Commande Django pour importer / mettre à jour les postes d'un contrat
|
||
depuis un fichier Excel de type « Inventaire fonteinen » (format SPRB-GOB).
|
||
|
||
Structure attendue du fichier Excel :
|
||
Col A – Nr. Post (entier) → order_number
|
||
Col B – Code Fontaine/Article → code
|
||
Col C – Beschrijving NL → description_nl
|
||
Col D – AO (TVS = à justifier) → is_amount_to_be_justified
|
||
Col E – Unité NL → unit (fallback)
|
||
Col F – Description FR → description_fr
|
||
Col G – NC (ignorée)
|
||
Col H – U (unité FR) → unit (prioritaire)
|
||
Col I – Quantité présumée (ignorée)
|
||
Col J – Prix unitaire → unit_price
|
||
Col K – Total par poste (ignorée)
|
||
|
||
Usage :
|
||
python manage.py import_contract_posts \\
|
||
--contract E24.143 \\
|
||
--excel-path scripts/data/"E24.143 Inventaire_inventaris ingevuld.xlsm"
|
||
|
||
# simuler sans écrire :
|
||
python manage.py import_contract_posts --contract E24.143 \\
|
||
--excel-path /tmp/inventaire.xlsm --dry-run
|
||
|
||
# mettre à jour les postes déjà présents :
|
||
python manage.py import_contract_posts --contract E24.143 \\
|
||
--excel-path /tmp/inventaire.xlsm --update
|
||
"""
|
||
|
||
from django.core.management.base import BaseCommand, CommandError
|
||
|
||
from contracts.models import Contract
|
||
from contracts.utils_import_posts import import_contract_posts
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = (
|
||
"Importe ou met à jour les postes d'un contrat depuis un fichier Excel "
|
||
"(format Inventaire fonteinen SPRB-GOB)."
|
||
)
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument(
|
||
"--contract",
|
||
required=True,
|
||
metavar="CONTRACT_NUMBER",
|
||
help="Numéro du contrat cible (ex : E24.143).",
|
||
)
|
||
parser.add_argument(
|
||
"--excel-path",
|
||
required=True,
|
||
metavar="PATH",
|
||
help="Chemin vers le fichier Excel (.xlsx / .xlsm).",
|
||
)
|
||
parser.add_argument(
|
||
"--dry-run",
|
||
action="store_true",
|
||
default=False,
|
||
help="Simule l'import sans écrire en base de données.",
|
||
)
|
||
parser.add_argument(
|
||
"--update",
|
||
action="store_true",
|
||
default=False,
|
||
help=(
|
||
"Met à jour les postes déjà présents (même order_number). "
|
||
"Sans ce flag, les postes existants sont ignorés."
|
||
),
|
||
)
|
||
|
||
def handle(self, *args, **options):
|
||
contract_number = options["contract"]
|
||
excel_path = options["excel_path"]
|
||
dry_run = options["dry_run"]
|
||
update_existing = options["update"]
|
||
|
||
if dry_run:
|
||
self.stdout.write(self.style.WARNING(
|
||
"=" * 62 + "\n"
|
||
"MODE DRY-RUN – aucune modification ne sera enregistrée\n"
|
||
"=" * 62
|
||
))
|
||
|
||
# ------------------------------------------------------------------
|
||
# Récupération du contrat
|
||
# ------------------------------------------------------------------
|
||
try:
|
||
contract = Contract.objects.get(contract_number=contract_number)
|
||
except Contract.DoesNotExist:
|
||
raise CommandError(
|
||
f"Contrat « {contract_number} » introuvable en base."
|
||
)
|
||
|
||
self.stdout.write(f"Contrat : {contract}")
|
||
self.stdout.write(f"Fichier : {excel_path}")
|
||
self.stdout.write(f"Mode update: {'oui' if update_existing else 'non'}\n")
|
||
|
||
# ------------------------------------------------------------------
|
||
# Import
|
||
# ------------------------------------------------------------------
|
||
stats = import_contract_posts(
|
||
contract=contract,
|
||
excel_source=excel_path,
|
||
dry_run=dry_run,
|
||
update_existing=update_existing,
|
||
log=lambda msg: self.stdout.write(msg),
|
||
)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Résumé
|
||
# ------------------------------------------------------------------
|
||
marker = " [DRY-RUN]" if dry_run else ""
|
||
self.stdout.write("\n" + "=" * 62)
|
||
self.stdout.write(self.style.SUCCESS(f"RÉSUMÉ{marker}"))
|
||
self.stdout.write("=" * 62)
|
||
self.stdout.write(f" Lignes avec numéro de poste : {stats['total_rows']}")
|
||
self.stdout.write(self.style.SUCCESS(
|
||
f" Postes créés : {stats['created']}"
|
||
))
|
||
self.stdout.write(self.style.SUCCESS(
|
||
f" Postes mis à jour : {stats['updated']}"
|
||
))
|
||
self.stdout.write(
|
||
f" Ignorés (déjà présents) : {stats['skipped_already_exists']}"
|
||
)
|
||
self.stdout.write(
|
||
f" Ignorés (prix manquant) : {stats['skipped_no_price']}"
|
||
)
|
||
if stats['errors']:
|
||
self.stdout.write(self.style.ERROR(
|
||
f" Erreurs : {stats['errors']}"
|
||
))
|
||
self.stdout.write("=" * 62)
|