Unified document's modification function

This commit is contained in:
fanilo 2026-01-05 17:31:18 +01:00
parent f80ad1adee
commit 57103d406d
3 changed files with 361 additions and 2358 deletions

File diff suppressed because it is too large Load diff

View file

@ -383,10 +383,302 @@ def _relire_document_final(
return resultat return resultat
def modifier_document_vente(
self, numero: str, doc_data: dict, type_document: TypeDocumentVente
) -> Dict:
"""
Méthode unifiée de modification de documents de vente
RÉUTILISE les mêmes sous-méthodes que la création
"""
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
config = ConfigDocument(type_document)
logger.info(f"📝 === MODIFICATION {config.nom_document.upper()} {numero} ===")
try:
with self._com_context(), self._lock_com:
# ==========================================
# 1. CHARGEMENT DOCUMENT
# ==========================================
logger.info("📂 Chargement document...")
factory = self.cial.FactoryDocumentVente
persist = None
for type_test in [config.type_sage, 50]:
try:
persist_test = factory.ReadPiece(type_test, numero)
if persist_test:
persist = persist_test
logger.info(f" ✓ Document trouvé (type={type_test})")
break
except:
continue
if not persist:
raise ValueError(
f"{config.nom_document.capitalize()} {numero} introuvable"
)
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read()
# Vérifications
statut_actuel = getattr(doc, "DO_Statut", 0)
if statut_actuel == 5:
raise ValueError(f"Le {config.nom_document} a déjà été transformé")
if statut_actuel == 6:
raise ValueError(f"Le {config.nom_document} est annulé")
# ==========================================
# 2. SAUVEGARDE CLIENT INITIAL
# ==========================================
client_code_initial = ""
try:
client_obj = getattr(doc, "Client", None)
if client_obj:
client_obj.Read()
client_code_initial = getattr(client_obj, "CT_Num", "").strip()
logger.info(f" Client initial: {client_code_initial}")
except Exception as e:
logger.error(f" Erreur lecture client: {e}")
if not client_code_initial:
raise ValueError("Client introuvable dans le document")
# ==========================================
# 3. COMPTAGE LIGNES EXISTANTES
# ==========================================
nb_lignes_initial = 0
try:
factory_lignes = getattr(doc, "FactoryDocumentLigne", None) or getattr(
doc, "FactoryDocumentVenteLigne", None
)
index = 1
while index <= 100:
try:
ligne_p = factory_lignes.List(index)
if ligne_p is None:
break
nb_lignes_initial += 1
index += 1
except:
break
logger.info(f" Lignes existantes: {nb_lignes_initial}")
except Exception as e:
logger.warning(f" Erreur comptage lignes: {e}")
# ==========================================
# 4. ANALYSE MODIFICATIONS
# ==========================================
champs_modifies = []
modif_date = config.champ_date_principale in doc_data
modif_date_sec = (
config.champ_date_secondaire
and config.champ_date_secondaire in doc_data
)
modif_statut = "statut" in doc_data
modif_ref = "reference" in doc_data
modif_lignes = "lignes" in doc_data and doc_data["lignes"] is not None
logger.info("📋 Modifications demandées:")
logger.info(f" {config.champ_date_principale}: {modif_date}")
if config.champ_date_secondaire:
logger.info(f" {config.champ_date_secondaire}: {modif_date_sec}")
logger.info(f" Statut: {modif_statut}")
logger.info(f" Référence: {modif_ref}")
logger.info(f" Lignes: {modif_lignes}")
# Reporter référence et statut après les lignes
doc_data_temp = doc_data.copy()
reference_a_modifier = None
statut_a_modifier = None
if modif_lignes:
if modif_ref:
reference_a_modifier = doc_data_temp.pop("reference")
modif_ref = False
if modif_statut:
statut_a_modifier = doc_data_temp.pop("statut")
modif_statut = False
# ==========================================
# 5. TEST WRITE BASIQUE
# ==========================================
logger.info("🔍 Test Write() basique...")
try:
doc.Write()
doc.Read()
logger.info(" ✓ Write() basique OK")
except Exception as e:
logger.error(f" ❌ Document verrouillé: {e}")
raise ValueError(f"Document verrouillé: {e}")
# ==========================================
# 6. MODIFICATIONS SIMPLES (sans lignes)
# ==========================================
if not modif_lignes and (
modif_date or modif_date_sec or modif_statut or modif_ref
):
logger.info("📝 Modifications simples...")
if modif_date:
doc.DO_Date = pywintypes.Time(
normaliser_date(doc_data_temp.get(config.champ_date_principale))
)
champs_modifies.append(config.champ_date_principale)
if modif_date_sec:
doc.DO_DateLivr = pywintypes.Time(
normaliser_date(doc_data_temp[config.champ_date_secondaire])
)
champs_modifies.append(config.champ_date_secondaire)
if modif_statut:
doc.DO_Statut = doc_data_temp["statut"]
champs_modifies.append("statut")
if modif_ref:
doc.DO_Ref = doc_data_temp["reference"]
champs_modifies.append("reference")
# 🔥 CONFIGURATION SPÉCIFIQUE FACTURE
if type_document == TypeDocumentVente.FACTURE:
self._configurer_facture(doc)
doc.Write()
logger.info(" ✓ Modifications appliquées")
# ==========================================
# 7. REMPLACEMENT LIGNES
# ==========================================
elif modif_lignes:
logger.info("🔄 REMPLACEMENT COMPLET DES LIGNES...")
# Dates
if modif_date:
doc.DO_Date = pywintypes.Time(
normaliser_date(doc_data_temp.get(config.champ_date_principale))
)
champs_modifies.append(config.champ_date_principale)
if modif_date_sec:
doc.DO_DateLivr = pywintypes.Time(
normaliser_date(doc_data_temp[config.champ_date_secondaire])
)
champs_modifies.append(config.champ_date_secondaire)
# 🔥 CONFIGURATION SPÉCIFIQUE FACTURE (avant lignes)
if type_document == TypeDocumentVente.FACTURE:
self._configurer_facture(doc)
nouvelles_lignes = doc_data["lignes"]
nb_nouvelles = len(nouvelles_lignes)
logger.info(f" {nb_lignes_initial}{nb_nouvelles} lignes")
try:
factory_lignes = doc.FactoryDocumentLigne
except:
factory_lignes = doc.FactoryDocumentVenteLigne
factory_article = self.cial.FactoryArticle
# Suppression lignes existantes
if nb_lignes_initial > 0:
logger.info(f" 🗑️ Suppression {nb_lignes_initial} lignes...")
for idx in range(nb_lignes_initial, 0, -1):
try:
ligne_p = factory_lignes.List(idx)
if ligne_p:
ligne = win32com.client.CastTo(
ligne_p, "IBODocumentLigne3"
)
ligne.Read()
ligne.Remove()
except Exception as e:
logger.warning(f" Ligne {idx}: {e}")
logger.info(" ✓ Lignes supprimées")
# Ajout nouvelles lignes avec REMISES
logger.info(f" Ajout {nb_nouvelles} lignes...")
for idx, ligne_data in enumerate(nouvelles_lignes, 1):
# 🔥 UTILISE _ajouter_ligne_document qui applique les remises
_ajouter_ligne_document(
cial=self.cial,
factory_lignes=factory_lignes,
factory_article=factory_article,
ligne_data=ligne_data,
idx=idx,
doc=doc,
)
logger.info(" ✓ Nouvelles lignes ajoutées avec remises")
doc.Write()
time.sleep(0.5)
doc.Read()
champs_modifies.append("lignes")
# ==========================================
# 8. MODIFICATIONS REPORTÉES
# ==========================================
if reference_a_modifier is not None:
try:
logger.info(
f" 📝 Modification référence: '{reference_a_modifier}'"
)
doc.DO_Ref = (
str(reference_a_modifier) if reference_a_modifier else ""
)
doc.Write()
time.sleep(0.5)
doc.Read()
champs_modifies.append("reference")
except Exception as e:
logger.warning(f" Référence: {e}")
if statut_a_modifier is not None:
try:
logger.info(f" 📝 Modification statut: {statut_a_modifier}")
doc.DO_Statut = int(statut_a_modifier)
doc.Write()
time.sleep(0.5)
doc.Read()
champs_modifies.append("statut")
except Exception as e:
logger.warning(f" Statut: {e}")
# ==========================================
# 9. RELECTURE FINALE
# ==========================================
resultat = self._relire_document_final(config, numero, doc_data)
resultat["champs_modifies"] = champs_modifies
logger.info(f"{config.nom_document.upper()} {numero} MODIFIÉ")
logger.info(
f" Totaux: {resultat['total_ht']}€ HT / {resultat['total_ttc']}€ TTC"
)
logger.info(f" Champs modifiés: {champs_modifies}")
return resultat
except ValueError as e:
logger.error(f"❌ ERREUR MÉTIER: {e}")
raise
except Exception as e:
logger.error(f"❌ ERREUR TECHNIQUE: {e}", exc_info=True)
raise RuntimeError(f"Erreur Sage: {str(e)}")
__all__ = [ __all__ = [
"creer_document_vente", "creer_document_vente",
"_ajouter_ligne_document", "_ajouter_ligne_document",
"_configurer_facture", "_configurer_facture",
"_recuperer_numero_document", "_recuperer_numero_document",
"_relire_document_final", "_relire_document_final",
"modifier_document_vente",
] ]

View file

@ -123,6 +123,37 @@ def _row_to_collaborateur_dict(row) -> Optional[dict]:
} }
def row_to_collaborateur_dict(row):
"""Convertit une ligne SQL en dictionnaire collaborateur"""
return {
"numero": row.CO_No,
"nom": _safe_strip(row.CO_Nom), # ⚠️ Utiliser _safe_strip
"prenom": _safe_strip(row.CO_Prenom),
"fonction": _safe_strip(row.CO_Fonction),
"adresse": _safe_strip(row.CO_Adresse),
"complement": _safe_strip(row.CO_Complement),
"code_postal": _safe_strip(row.CO_CodePostal),
"ville": _safe_strip(row.CO_Ville),
"code_region": _safe_strip(row.CO_CodeRegion),
"pays": _safe_strip(row.CO_Pays),
"service": _safe_strip(row.CO_Service),
"vendeur": bool(row.CO_Vendeur),
"caissier": bool(row.CO_Caissier),
"acheteur": bool(row.CO_Acheteur),
"telephone": _safe_strip(row.CO_Telephone),
"telecopie": _safe_strip(row.CO_Telecopie),
"email": _safe_strip(row.CO_EMail),
"tel_portable": _safe_strip(row.CO_TelPortable),
"matricule": _safe_strip(row.CO_Matricule),
"facebook": _safe_strip(row.CO_Facebook),
"linkedin": _safe_strip(row.CO_LinkedIn),
"skype": _safe_strip(row.CO_Skype),
"sommeil": bool(row.CO_Sommeil),
"chef_ventes": bool(row.CO_ChefVentes),
"numero_chef_ventes": row.CO_NoChefVentes if row.CO_NoChefVentes else None,
}
def _row_to_tiers_dict(row) -> dict: def _row_to_tiers_dict(row) -> dict:
"""Convertit une ligne SQL en dictionnaire tiers""" """Convertit une ligne SQL en dictionnaire tiers"""
tiers = { tiers = {
@ -218,4 +249,5 @@ __all__ = [
"_contact_to_dict", "_contact_to_dict",
"_row_to_contact_dict", "_row_to_contact_dict",
"_row_to_tiers_dict", "_row_to_tiers_dict",
"row_to_collaborateur_dict"
] ]