""" 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)