Unified document's modification function
This commit is contained in:
parent
f80ad1adee
commit
57103d406d
3 changed files with 361 additions and 2358 deletions
2385
sage_connector.py
2385
sage_connector.py
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue