1168 lines
46 KiB
Python
1168 lines
46 KiB
Python
"""
|
|
Ce fichier permet de gerer la relance des factures des client
|
|
|
|
"""
|
|
import ast
|
|
import smtplib
|
|
from email import encoders
|
|
from email.mime.base import MIMEBase
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.mime.text import MIMEText
|
|
|
|
import dateutil
|
|
import pymongo
|
|
import xlsxwriter
|
|
from flask import send_file
|
|
from pandas.io.formats.style import jinja2
|
|
from pymongo import MongoClient
|
|
import json
|
|
from bson import ObjectId
|
|
import re
|
|
from datetime import datetime, timezone, date
|
|
|
|
from xhtml2pdf import pisa
|
|
|
|
import attached_file_mgt
|
|
import prj_common as mycommon
|
|
import secrets
|
|
import inspect
|
|
import sys, os
|
|
import csv
|
|
import pandas as pd
|
|
from pymongo import ReturnDocument
|
|
import GlobalVariable as MYSY_GV
|
|
from math import isnan
|
|
import GlobalVariable as MYSY_GV
|
|
from datetime import timedelta
|
|
from datetime import timedelta
|
|
import Dashbord_queries.formation_tbd_qries as formation_tbd_qries
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
"""
|
|
Creation d'une relance client
|
|
"""
|
|
def Create_Relance_Client_Facture_Given_Client_PDF(diction):
|
|
|
|
try:
|
|
diction = mycommon.strip_dictionary(diction)
|
|
|
|
"""
|
|
Verification des input acceptés
|
|
"""
|
|
field_list = ['token', 'client_id'
|
|
]
|
|
|
|
incom_keys = diction.keys()
|
|
for val in incom_keys:
|
|
if val not in field_list and val.startswith('my_') is False:
|
|
mycommon.myprint(str(
|
|
inspect.stack()[0][3]) + " Le champ '" + val + "' n'est pas autorisé ")
|
|
return False, " Les informations fournies sont incorrectes",
|
|
|
|
"""
|
|
Verification des champs obligatoires
|
|
"""
|
|
field_list_obligatoire = ['token', 'client_id' ]
|
|
for val in field_list_obligatoire:
|
|
if val not in diction:
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][3]) + " - La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
|
return False, " Les informations fournies sont incorrectes",
|
|
|
|
|
|
local_status, my_partner = mycommon.Check_Connexion_And_Return_Partner_Data(diction)
|
|
if (local_status is not True):
|
|
return local_status, my_partner
|
|
|
|
"""
|
|
Verifier la validité du client
|
|
"""
|
|
is_clien_valide = MYSY_GV.dbname['partner_client'].count_documents({'_id':ObjectId(str(diction['client_id'])),
|
|
'partner_owner_recid':str(my_partner['recid']),
|
|
'valide':'1',
|
|
'locked':'0'})
|
|
|
|
if( is_clien_valide != 1):
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][
|
|
3]) + " L'identifiant du client est invalide ")
|
|
return False, " L'identifiant du client est invalide "
|
|
|
|
"""
|
|
Recuperer le modèle de document
|
|
"""
|
|
local_diction = {}
|
|
local_diction['ref_interne'] = "RELANCE_FACTURE_CLIENT"
|
|
local_diction['type_doc'] = "pdf"
|
|
local_diction['partner_owner_recid'] = str(my_partner['recid'])
|
|
|
|
courrier_data_status, courrier_data_retval = mycommon.Get_Courrier_Template_Include_Default_Data(local_diction)
|
|
if (courrier_data_status is False):
|
|
return courrier_data_status, courrier_data_retval
|
|
|
|
if ("contenu_doc" not in courrier_data_retval.keys() or str(courrier_data_retval['contenu_doc']) == ""):
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][
|
|
3]) + " Le modèle de courrier 'RELANCE_FACTURE_CLIENT' n'est pas correctement configuré ")
|
|
return False, " Le modèle de courrier 'RELANCE_FACTURE_CLIENT' n'est pas correctement configuré "
|
|
|
|
"""
|
|
Recuperation du dictionnaire des info
|
|
"""
|
|
tab_client = []
|
|
tab_client.append(ObjectId(str(diction['client_id'])))
|
|
|
|
new_diction = {}
|
|
new_diction['token'] = diction['token']
|
|
new_diction['list_stagiaire_id'] = []
|
|
new_diction['list_session_id'] = []
|
|
new_diction['list_class_id'] = []
|
|
new_diction['list_client_id'] = tab_client
|
|
new_diction['list_apprenant_id'] = []
|
|
|
|
local_status, local_retval = mycommon.Get_Dictionnary_data_For_Template(new_diction)
|
|
|
|
if (local_status is False):
|
|
return local_status, local_retval
|
|
|
|
convention_dictionnary_data = local_retval
|
|
"""
|
|
Algo :
|
|
1 - recuperer toutes les facture du client arrivée a échangé et non payées
|
|
2 - Parcours les factures pour :
|
|
- recuperer le total du reste à payer
|
|
- le plus haut degré de relance
|
|
3 - Faire le courrier
|
|
4 - Mettre à jour chaque facutre pour mettre à jour la date de relance et le niveau de relance
|
|
- tab_relance = [ {date_relance:'xxx', relance_id:'xxx'} ]
|
|
5 - Ajouter l'action dans l'historique du client
|
|
|
|
"""
|
|
|
|
todays_date = str(date.today().strftime("%d/%m/%Y"))
|
|
todays_date_ISO_Date = datetime.strptime(str(todays_date).strip(), '%d/%m/%Y')
|
|
|
|
find_qry = {
|
|
'mysy_invoice_reste_to_paye_num': { '$gt': 0},
|
|
'mysy_invoice_du_date': {'$lt': todays_date_ISO_Date},
|
|
'order_header_client_id': str(diction['client_id']),
|
|
'invoice_litige': {'$ne':'1'},
|
|
|
|
}
|
|
query_invoice_echance_depace = [
|
|
{"$addFields":
|
|
{
|
|
"partner_invoice_header_Id": {"$toString": "$_id"},
|
|
"mysy_total_header_toutes_taxes": {"$convert": {
|
|
'input': "$total_header_toutes_taxes",
|
|
'to': "double",
|
|
'onError': {'error': 0},
|
|
'onNull': {'isnull': 0}
|
|
}
|
|
},
|
|
|
|
"mysy_invoice_du_date": {
|
|
'$dateFromString': {
|
|
"dateString": {
|
|
"$substr": [
|
|
{"$ifNull": ["$invoice_date_echeance", ""]},
|
|
0,
|
|
10
|
|
]
|
|
},
|
|
|
|
"format": "%d/%m/%Y",
|
|
"onError": "null",
|
|
"onNull": "null"
|
|
}
|
|
},
|
|
|
|
|
|
"mysy_invoice_date": {
|
|
"$dateFromString": {
|
|
"dateString": {
|
|
"$substr": [
|
|
{"$ifNull": ["$invoice_date", ""]},
|
|
0,
|
|
10
|
|
]
|
|
},
|
|
"format": "%d/%m/%Y",
|
|
"onError": 'null',
|
|
"onNull": 'null'
|
|
}
|
|
},
|
|
|
|
"mysy_invoice_reste_to_paye_num": {
|
|
"$convert": {
|
|
"input": {
|
|
"$replaceAll": {
|
|
"input": {
|
|
"$replaceAll": {
|
|
"input": {
|
|
"$ifNull": ["$reste_to_paye", "0"]
|
|
},
|
|
"find": " ",
|
|
"replacement": ""
|
|
}
|
|
},
|
|
"find": ",",
|
|
"replacement": "."
|
|
}
|
|
},
|
|
"to": "double",
|
|
"onError": 0,
|
|
"onNull": 0
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{'$match': find_qry},
|
|
{
|
|
"$sort": {
|
|
"mysy_invoice_reste_to_paye_num": 1
|
|
}
|
|
},
|
|
{
|
|
"$project": {
|
|
"_id": 1,
|
|
"invoice_header_ref_interne": 1,
|
|
"invoice_header_type": 1,
|
|
"invoice_date": 1,
|
|
"invoice_date_echeance": 1,
|
|
"order_header_client_id": 1,
|
|
"order_header_email_client": 1,
|
|
"partner_owner_recid": 1,
|
|
"total_header_toutes_taxes": 1,
|
|
"invoice_paiement_status": 1,
|
|
"reste_to_paye": 1,
|
|
"mysy_invoice_reste_to_paye_num": 1,
|
|
'tab_relance':1,
|
|
|
|
}
|
|
}
|
|
]
|
|
|
|
print(" ### query_invoice_echance_depace = ", query_invoice_echance_depace)
|
|
|
|
RetObject = []
|
|
val_tmp = 0
|
|
|
|
total_reste_to_paye = 0
|
|
max_relance_done = 0
|
|
tab_invoice_a_relancer = []
|
|
mytoday22 = datetime.today().strftime("%d/%m/%Y")
|
|
|
|
for retval in MYSY_GV.dbname['partner_invoice_header'].aggregate(query_invoice_echance_depace):
|
|
user = retval
|
|
user['id'] = str(val_tmp)
|
|
|
|
node_tab_relance = {}
|
|
node_tab_relance['invoice_id'] = str(retval['_id'])
|
|
node_tab_relance['invoice_header_ref_interne'] = retval['invoice_header_ref_interne']
|
|
node_tab_relance['total_header_toutes_taxes'] = str(round(float(retval['total_header_toutes_taxes']), 2))
|
|
node_tab_relance['reste_to_paye'] = str(round(float(retval['reste_to_paye']), 2))
|
|
node_tab_relance['invoice_date_echeance'] = retval['invoice_date_echeance']
|
|
node_tab_relance['invoice_date'] = retval['invoice_date']
|
|
|
|
retard = ( datetime.strptime( str(mytoday22),'%d/%m/%Y') - datetime.strptime(str(retval['invoice_date_echeance'])[0:10], '%d/%m/%Y')).days
|
|
|
|
|
|
node_tab_relance['paiement_retard'] = str(retard)
|
|
|
|
tab_invoice_a_relancer.append(node_tab_relance)
|
|
|
|
total_reste_to_paye = total_reste_to_paye + float(retval['mysy_invoice_reste_to_paye_num'])
|
|
|
|
"""
|
|
Recuperer le niveau de relance à faire
|
|
"""
|
|
if( "tab_relance" in retval.keys()):
|
|
if( len(retval['tab_relance']) > max_relance_done ):
|
|
max_relance_done = len(retval['tab_relance'])
|
|
|
|
val_tmp = val_tmp + 1
|
|
RetObject.append(mycommon.JSONEncoder().encode(user))
|
|
|
|
|
|
total_reste_to_paye = f"{float(total_reste_to_paye):,.2f}"
|
|
total_reste_to_paye = total_reste_to_paye.replace(",", " ").replace(".", ",")
|
|
|
|
print(" TOTAL DU = ", total_reste_to_paye)
|
|
|
|
print(" Niveau relance = ", max_relance_done)
|
|
|
|
new_relance_level = 0
|
|
if( max_relance_done < 3 ):
|
|
new_relance_level = max_relance_done + 1
|
|
else:
|
|
new_relance_level = 3
|
|
|
|
convention_dictionnary_data['tab_invoice_a_relancer'] = tab_invoice_a_relancer
|
|
convention_dictionnary_data['total_reste_to_paye'] = str(total_reste_to_paye)
|
|
convention_dictionnary_data['new_relance_level'] = str(new_relance_level)
|
|
|
|
company_data = {
|
|
"params": convention_dictionnary_data,
|
|
}
|
|
|
|
#print(" ###company_data = ", company_data)
|
|
# read css file
|
|
css_file = "./css_document/elyos_css.css"
|
|
from xhtml2pdf.default import DEFAULT_CSS
|
|
if css_file is not None:
|
|
with open(css_file) as f:
|
|
DEFAULT_CSS += f.read()
|
|
|
|
contenu_doc_Template = jinja2.Template(str(courrier_data_retval['contenu_doc']))
|
|
sourceHtml = contenu_doc_Template.render(params=company_data['params'])
|
|
|
|
ts = datetime.now().timestamp()
|
|
ts = str(ts).replace(".", "").replace(",", "")[-5:]
|
|
|
|
orig_file_name = "Relance_Fact_Client_" + str(my_partner['recid'])[0:2] + "_" + str(ts) + ".pdf"
|
|
outputFilename = str(MYSY_GV.TEMPORARY_DIRECTORY) + "/" + str(orig_file_name)
|
|
|
|
# open output file for writing (truncated binary)
|
|
resultFile = open(outputFilename, "w+b")
|
|
|
|
# convert HTML to PDF
|
|
pisaStatus = pisa.CreatePDF(
|
|
src=sourceHtml, # the HTML to convert
|
|
dest=resultFile,
|
|
default_css=DEFAULT_CSS) # file handle to receive result
|
|
|
|
# close output file
|
|
resultFile.close()
|
|
|
|
|
|
"""
|
|
Inserter la relance dans la collection : 'relance_client_invoice'
|
|
"""
|
|
new_relance_data = {}
|
|
new_relance_data['client_id'] = diction['client_id']
|
|
new_relance_data['tab_invoice'] = tab_invoice_a_relancer
|
|
new_relance_data['relance_date'] = str(datetime.now().strftime("%d/%m/%Y %H:%M"))
|
|
new_relance_data['relance_level'] = str(new_relance_level)
|
|
new_relance_data['relance_by'] = str(my_partner['_id'])
|
|
new_relance_data['total_reste_to_paye'] = str(total_reste_to_paye)
|
|
new_relance_data['partner_owner_recid'] = str(my_partner['recid'])
|
|
|
|
new_relance_data['update_date'] = str(datetime.now())
|
|
new_relance_data['update_by'] = str(my_partner['_id'])
|
|
new_relance_data['valide'] = "1"
|
|
new_relance_data['locked'] = "0"
|
|
new_relance_data['mode_envoi'] = "pdf"
|
|
|
|
relance_insert = MYSY_GV.dbname['relance_client_invoice'].insert_one(new_relance_data)
|
|
|
|
if( relance_insert and relance_insert.inserted_id):
|
|
"""
|
|
Mettre à jour la relance des factures
|
|
"""
|
|
todays_date_heure = datetime.now().strftime("%d/%m/%Y %H:%M")
|
|
for fact in tab_invoice_a_relancer:
|
|
new_data = {}
|
|
new_data['date_relance'] = str(todays_date_heure)
|
|
new_data['relance_id'] = str(relance_insert.inserted_id)
|
|
|
|
create = MYSY_GV.dbname['partner_invoice_header'].update_one({
|
|
'_id': ObjectId(str(fact['invoice_id'])),
|
|
'partner_owner_recid': str(my_partner['recid']),
|
|
},
|
|
{
|
|
'$push': {
|
|
"tab_relance": {
|
|
'$each': [new_data]
|
|
}
|
|
}
|
|
},
|
|
|
|
)
|
|
|
|
"""
|
|
Aller stocker le document PDF de relance et le rattacher a la relance
|
|
"""
|
|
saved_outputFilename = outputFilename
|
|
if (orig_file_name and outputFilename):
|
|
new_file = {}
|
|
new_file['token'] = diction['token']
|
|
type_document = "relance_facture"
|
|
|
|
new_file['file_business_object'] = str(orig_file_name)
|
|
|
|
new_file['file_name'] = str(orig_file_name)
|
|
new_file['status'] = "1"
|
|
new_file['type_document'] = str(type_document)
|
|
|
|
new_file['object_owner_collection'] = "partner_client"
|
|
new_file['object_owner_id'] = str(diction['client_id'])
|
|
new_file['file_name_to_store'] = outputFilename
|
|
|
|
tab_key_word = ""
|
|
new_file['tab_key_word'] = tab_key_word
|
|
|
|
|
|
tab_related_collection = []
|
|
node_class = {}
|
|
node_class['related_collection'] = 'relance_client_invoice'
|
|
node_class['related_collection_id'] = str(relance_insert.inserted_id)
|
|
tab_related_collection.append(node_class)
|
|
|
|
print(" ### new_file new_file = ", new_file)
|
|
|
|
local_status, local_retval = attached_file_mgt.Internal_Usage_Store_User_Downloaded_File(
|
|
MYSY_GV.upload_folder, new_file, tab_related_collection)
|
|
|
|
if (local_status is False):
|
|
print(" ## WARINNGGG Impossible de stocker le fichier de Convocation ")
|
|
|
|
if(local_retval ):
|
|
saved_outputFilename = local_retval
|
|
|
|
|
|
"""
|
|
# Ajout de l'evenement sur la fiche client
|
|
"""
|
|
now = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
|
history_event_dict = {}
|
|
history_event_dict['token'] = diction['token']
|
|
history_event_dict['related_collection'] = "partnair_account"
|
|
history_event_dict['related_collection_recid'] = str(diction['client_id'])
|
|
history_event_dict['action_date'] = str(now)
|
|
history_event_dict['action_description'] = "Relance facture client _id = "+str(str(relance_insert.inserted_id))
|
|
|
|
local_status, local_retval = mycommon.Collection_Historique.Add_Historique_Event(history_event_dict)
|
|
if (local_status is False):
|
|
mycommon.myprint(
|
|
" WARNING : Impossible de logguer l'historique pour l'évènement : " + str(history_event_dict))
|
|
|
|
# print(" ### outputFilename = "+str(outputFilename))
|
|
if os.path.exists(saved_outputFilename):
|
|
print(" ### ok os.path.exists(outputFilename) "+str(saved_outputFilename))
|
|
return True, send_file(saved_outputFilename, as_attachment=True)
|
|
|
|
# return True on success and False on errors
|
|
print(pisaStatus.err, type(pisaStatus.err))
|
|
|
|
return True, " le fichier generé "
|
|
|
|
|
|
except Exception as e:
|
|
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
|
return False, " Impossible de faire la relance client "
|
|
|
|
|
|
def Create_Relance_Client_Facture_Given_Client_Email(tab_files, Folder, diction):
|
|
try:
|
|
diction = mycommon.strip_dictionary(diction)
|
|
|
|
"""
|
|
Verification des input acceptés
|
|
"""
|
|
field_list = ['token', 'client_id', 'courrier_template_id', 'email_test', 'email_production' ]
|
|
|
|
incom_keys = diction.keys()
|
|
for val in incom_keys:
|
|
if val not in field_list and val.startswith('my_') is False:
|
|
mycommon.myprint(str(
|
|
inspect.stack()[0][3]) + " Le champ '" + val + "' n'est pas autorisé ")
|
|
return False, " Les informations fournies sont incorrectes",
|
|
|
|
"""
|
|
Verification des champs obligatoires
|
|
"""
|
|
field_list_obligatoire = ['token', 'client_id']
|
|
for val in field_list_obligatoire:
|
|
if val not in diction:
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][
|
|
3]) + " - La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
|
return False, " Les informations fournies sont incorrectes",
|
|
|
|
local_status, my_partner = mycommon.Check_Connexion_And_Return_Partner_Data(diction)
|
|
if (local_status is not True):
|
|
return local_status, my_partner
|
|
|
|
courrier_template_count = MYSY_GV.dbname['courrier_template'].count_documents(
|
|
{'_id': ObjectId(str(diction['courrier_template_id'])),
|
|
'valide': '1',
|
|
'locked': '0',
|
|
'partner_owner_recid': str(my_partner['recid'])}
|
|
)
|
|
|
|
if (courrier_template_count != 1):
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][
|
|
3]) + " L'identifiant du modèle de courrier est invalide ")
|
|
return False, " L'identifiant du modèle de courrier est invalide "
|
|
|
|
"""
|
|
Verifier la validité du client
|
|
"""
|
|
is_clien_valide = MYSY_GV.dbname['partner_client'].count_documents({'_id': ObjectId(str(diction['client_id'])),
|
|
'partner_owner_recid': str(
|
|
my_partner['recid']),
|
|
'valide': '1',
|
|
'locked': '0'})
|
|
|
|
if (is_clien_valide != 1):
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][
|
|
3]) + " L'identifiant du client est invalide ")
|
|
return False, " L'identifiant du client est invalide "
|
|
|
|
"""
|
|
Recuperer le modèle de document
|
|
"""
|
|
courrier_data_retval = MYSY_GV.dbname['courrier_template'].find_one(
|
|
{'_id': ObjectId(str(diction['courrier_template_id'])),
|
|
'valide': '1',
|
|
'locked': '0',
|
|
'partner_owner_recid': str(my_partner['recid'])}
|
|
)
|
|
|
|
|
|
if ("contenu_doc" not in courrier_data_retval.keys() or str(courrier_data_retval['contenu_doc']) == ""):
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][
|
|
3]) + " Le modèle de courrier 'RELANCE_FACTURE_CLIENT' n'est pas correctement configuré ")
|
|
return False, " Le modèle de courrier 'RELANCE_FACTURE_CLIENT' n'est pas correctement configuré "
|
|
|
|
#print(" IIIIIIIIIIIIIIIIIII courrier_data_retval[_id] = ", str(courrier_data_retval['_id']))
|
|
"""
|
|
Recuperation du dictionnaire des info
|
|
"""
|
|
tab_client = []
|
|
tab_client.append(ObjectId(str(diction['client_id'])))
|
|
|
|
new_diction = {}
|
|
new_diction['token'] = diction['token']
|
|
new_diction['list_stagiaire_id'] = []
|
|
new_diction['list_session_id'] = []
|
|
new_diction['list_class_id'] = []
|
|
new_diction['list_client_id'] = tab_client
|
|
new_diction['list_apprenant_id'] = []
|
|
|
|
local_status, local_retval = mycommon.Get_Dictionnary_data_For_Template(new_diction)
|
|
|
|
if (local_status is False):
|
|
return local_status, local_retval
|
|
|
|
convention_dictionnary_data = local_retval
|
|
"""
|
|
Algo :
|
|
1 - recuperer toutes les facture du client arrivée a échangé et non payées
|
|
2 - Parcours les factures pour :
|
|
- recuperer le total du reste à payer
|
|
- le plus haut degré de relance
|
|
3 - Faire le courrier
|
|
4 - Mettre à jour chaque facutre pour mettre à jour la date de relance et le niveau de relance
|
|
- tab_relance = [ {date_relance:'xxx', relance_id:'xxx'} ]
|
|
5 - Ajouter l'action dans l'historique du client
|
|
|
|
"""
|
|
|
|
todays_date = str(date.today().strftime("%d/%m/%Y"))
|
|
todays_date_ISO_Date = datetime.strptime(str(todays_date).strip(), '%d/%m/%Y')
|
|
|
|
find_qry = {
|
|
'mysy_invoice_reste_to_paye_num': {'$gt': 0},
|
|
'mysy_invoice_du_date': {'$lt': todays_date_ISO_Date},
|
|
'order_header_client_id': str(diction['client_id']),
|
|
'invoice_litige': {'$ne': '1'},
|
|
|
|
}
|
|
query_invoice_echance_depace = [
|
|
{"$addFields":
|
|
{
|
|
"partner_invoice_header_Id": {"$toString": "$_id"},
|
|
"mysy_total_header_toutes_taxes": {"$convert": {
|
|
'input': "$total_header_toutes_taxes",
|
|
'to': "double",
|
|
'onError': {'error': 0},
|
|
'onNull': {'isnull': 0}
|
|
}
|
|
},
|
|
|
|
"mysy_invoice_du_date": {
|
|
'$dateFromString': {
|
|
"dateString": {
|
|
"$substr": [
|
|
{"$ifNull": ["$invoice_date_echeance", ""]},
|
|
0,
|
|
10
|
|
]
|
|
},
|
|
|
|
"format": "%d/%m/%Y",
|
|
"onError": "null",
|
|
"onNull": "null"
|
|
}
|
|
},
|
|
|
|
"mysy_invoice_date": {
|
|
"$dateFromString": {
|
|
"dateString": {
|
|
"$substr": [
|
|
{"$ifNull": ["$invoice_date", ""]},
|
|
0,
|
|
10
|
|
]
|
|
},
|
|
"format": "%d/%m/%Y",
|
|
"onError": 'null',
|
|
"onNull": 'null'
|
|
}
|
|
},
|
|
|
|
"mysy_invoice_reste_to_paye_num": {
|
|
"$convert": {
|
|
"input": {
|
|
"$replaceAll": {
|
|
"input": {
|
|
"$replaceAll": {
|
|
"input": {
|
|
"$ifNull": ["$reste_to_paye", "0"]
|
|
},
|
|
"find": " ",
|
|
"replacement": ""
|
|
}
|
|
},
|
|
"find": ",",
|
|
"replacement": "."
|
|
}
|
|
},
|
|
"to": "double",
|
|
"onError": 0,
|
|
"onNull": 0
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{'$match': find_qry},
|
|
{
|
|
"$sort": {
|
|
"mysy_invoice_reste_to_paye_num": 1
|
|
}
|
|
},
|
|
{
|
|
"$project": {
|
|
"_id": 1,
|
|
"invoice_header_ref_interne": 1,
|
|
"invoice_header_type": 1,
|
|
"invoice_date": 1,
|
|
"invoice_date_echeance": 1,
|
|
"order_header_client_id": 1,
|
|
"order_header_email_client": 1,
|
|
"partner_owner_recid": 1,
|
|
"total_header_toutes_taxes": 1,
|
|
"invoice_paiement_status": 1,
|
|
"reste_to_paye": 1,
|
|
"mysy_invoice_reste_to_paye_num": 1,
|
|
'tab_relance': 1,
|
|
|
|
}
|
|
}
|
|
]
|
|
|
|
print(" ### query_invoice_echance_depace = ", query_invoice_echance_depace)
|
|
|
|
RetObject = []
|
|
val_tmp = 0
|
|
|
|
total_reste_to_paye = 0
|
|
max_relance_done = 0
|
|
tab_invoice_a_relancer = []
|
|
mytoday22 = datetime.today().strftime("%d/%m/%Y")
|
|
|
|
for retval in MYSY_GV.dbname['partner_invoice_header'].aggregate(query_invoice_echance_depace):
|
|
user = retval
|
|
user['id'] = str(val_tmp)
|
|
|
|
node_tab_relance = {}
|
|
node_tab_relance['invoice_id'] = str(retval['_id'])
|
|
node_tab_relance['invoice_header_ref_interne'] = retval['invoice_header_ref_interne']
|
|
node_tab_relance['total_header_toutes_taxes'] = str(round(float(retval['total_header_toutes_taxes']), 2))
|
|
node_tab_relance['reste_to_paye'] = str(round(float(retval['reste_to_paye']), 2))
|
|
node_tab_relance['invoice_date_echeance'] = retval['invoice_date_echeance']
|
|
node_tab_relance['invoice_date'] = retval['invoice_date']
|
|
|
|
retard = (datetime.strptime(str(mytoday22), '%d/%m/%Y') - datetime.strptime(
|
|
str(retval['invoice_date_echeance'])[0:10], '%d/%m/%Y')).days
|
|
|
|
node_tab_relance['paiement_retard'] = str(retard)
|
|
|
|
tab_invoice_a_relancer.append(node_tab_relance)
|
|
|
|
total_reste_to_paye = total_reste_to_paye + float(retval['mysy_invoice_reste_to_paye_num'])
|
|
|
|
"""
|
|
Recuperer le niveau de relance à faire
|
|
"""
|
|
if ("tab_relance" in retval.keys()):
|
|
if (len(retval['tab_relance']) > max_relance_done):
|
|
max_relance_done = len(retval['tab_relance'])
|
|
|
|
val_tmp = val_tmp + 1
|
|
RetObject.append(mycommon.JSONEncoder().encode(user))
|
|
|
|
total_reste_to_paye = f"{float(total_reste_to_paye):,.2f}"
|
|
total_reste_to_paye = total_reste_to_paye.replace(",", " ").replace(".", ",")
|
|
|
|
print(" TOTAL DU = ", total_reste_to_paye)
|
|
|
|
print(" Niveau relance = ", max_relance_done)
|
|
|
|
new_relance_level = 0
|
|
if (max_relance_done < 3):
|
|
new_relance_level = max_relance_done + 1
|
|
else:
|
|
new_relance_level = 3
|
|
|
|
convention_dictionnary_data['tab_invoice_a_relancer'] = tab_invoice_a_relancer
|
|
convention_dictionnary_data['total_reste_to_paye'] = str(total_reste_to_paye)
|
|
convention_dictionnary_data['new_relance_level'] = str(new_relance_level)
|
|
|
|
company_data = {
|
|
"params": convention_dictionnary_data,
|
|
}
|
|
|
|
###############################
|
|
# Traitement de l'eventuel fichier joint
|
|
|
|
# Sauvegarde des fichiers joints depuis le front
|
|
tab_saved_file_full_path = []
|
|
for file in tab_files:
|
|
status, saved_file_full_path = mycommon.Upload_Save_PDF_IMG_File(file, Folder)
|
|
if (status is False):
|
|
mycommon.myprint("Impossible de récupérer correctement le fichier à importer")
|
|
return False, "Impossible de récupérer correctement le fichier à importer"
|
|
|
|
tab_saved_file_full_path.append(saved_file_full_path)
|
|
|
|
|
|
tab_files_to_attache_to_mail = []
|
|
tab_list_file_for_queu_mail_and_internal_mail = []
|
|
|
|
for saved_file in tab_saved_file_full_path:
|
|
file_to_attache_to_mail = MIMEBase('application', "octet-stream")
|
|
file_to_attache_to_mail.set_payload(open(saved_file, "rb").read())
|
|
|
|
encoders.encode_base64(file_to_attache_to_mail)
|
|
file_to_attache_to_mail.add_header('Content-Disposition',
|
|
'attachment; filename="{0}"'.format(os.path.basename(saved_file)))
|
|
|
|
new_node = {"attached_file": file_to_attache_to_mail}
|
|
tab_files_to_attache_to_mail.append(new_node)
|
|
tab_list_file_for_queu_mail_and_internal_mail.append(saved_file)
|
|
|
|
|
|
orig_file_name = None
|
|
outputFilename = None
|
|
# Verifier s'il s'agit d'un document à envoyer avec une version de pièce jointe.
|
|
if ("joint_pdf" in courrier_data_retval.keys() and str(courrier_data_retval['joint_pdf']) == "1"):
|
|
# Il s'agit bien d'un envoie avec 'contenu_doc' en pièce jointe PDF
|
|
"""
|
|
1 - Creation du PDF
|
|
"""
|
|
contenu_doc_Template = jinja2.Template(str(courrier_data_retval['contenu_doc']))
|
|
|
|
sourceHtml = contenu_doc_Template.render(params=company_data["params"])
|
|
|
|
ts = datetime.now().timestamp()
|
|
ts = str(ts).replace(".", "").replace(",", "")[-5:]
|
|
|
|
orig_file_name = "Relance_Fact_Client_" + str(my_partner['recid'])[0:2] + "_" + str(ts) + ".pdf"
|
|
outputFilename = str(MYSY_GV.TEMPORARY_DIRECTORY) + "/" + str(orig_file_name)
|
|
|
|
# open output file for writing (truncated binary)
|
|
resultFile = open(outputFilename, "w+b")
|
|
|
|
# convert HTML to PDF
|
|
pisaStatus = pisa.CreatePDF(
|
|
src=sourceHtml, # the HTML to convert
|
|
dest=resultFile) # file handle to receive result
|
|
|
|
# close output file
|
|
resultFile.close()
|
|
|
|
# Attachement du fichier joint
|
|
file_to_attache_to_mail = MIMEBase('application', "octet-stream")
|
|
file_to_attache_to_mail.set_payload(open(outputFilename, "rb").read())
|
|
|
|
encoders.encode_base64(file_to_attache_to_mail)
|
|
file_to_attache_to_mail.add_header('Content-Disposition',
|
|
'attachment; filename="{0}"'.format(os.path.basename(outputFilename)))
|
|
|
|
new_node = {"attached_file": file_to_attache_to_mail}
|
|
tab_files_to_attache_to_mail.append(new_node)
|
|
|
|
## Creation du mail au format email
|
|
|
|
corps_mail_Template = jinja2.Template(str(courrier_data_retval['corps_mail']))
|
|
|
|
sourceHtml = corps_mail_Template.render(params=company_data["params"])
|
|
|
|
html_mime = MIMEText(sourceHtml, 'html')
|
|
|
|
# Creation de l'email à enoyer
|
|
msg = MIMEMultipart("alternative")
|
|
|
|
# Attacher l'eventuelle pièces jointes
|
|
for myfile in tab_files_to_attache_to_mail:
|
|
msg.attach(myfile['attached_file'])
|
|
|
|
else:
|
|
# Il s'agit d'une simple email
|
|
|
|
## Creation du mail au format email
|
|
|
|
contenu_doc_Template = jinja2.Template(str(courrier_data_retval['contenu_doc']))
|
|
|
|
sourceHtml = contenu_doc_Template.render(params=company_data["params"])
|
|
|
|
html_mime = MIMEText(sourceHtml, 'html')
|
|
|
|
# Creation de l'email à enoyer
|
|
msg = MIMEMultipart("alternative")
|
|
|
|
|
|
|
|
# Traitement du sujet du mail
|
|
sujet_mail_Template = jinja2.Template(str(courrier_data_retval['sujet']))
|
|
sujetHtml = sujet_mail_Template.render(params=company_data["params"])
|
|
|
|
# Recuperation des donnes smpt
|
|
local_stpm_status, partner_SMTP_COUNT_smtpsrv, partner_own_smtp_value, partner_SMTP_COUNT_password, partner_SMTP_COUNT_user, partner_SMTP_COUNT_From_User, partner_SMTP_COUNT_port = mycommon.Get_Partner_SMTP_Param(
|
|
my_partner['recid'])
|
|
|
|
if (local_stpm_status is False):
|
|
return local_stpm_status, partner_SMTP_COUNT_smtpsrv
|
|
|
|
msg.attach(html_mime)
|
|
|
|
if (str(partner_own_smtp_value) == "1"):
|
|
msg['From'] = partner_SMTP_COUNT_From_User
|
|
else:
|
|
msg['From'] = str(partner_SMTP_COUNT_From_User)
|
|
|
|
msg['Bcc'] = 'contact@mysy-training.com'
|
|
msg['Subject'] = sujetHtml
|
|
# msg['to'] = "billardman01@hotmail.com"
|
|
|
|
"""
|
|
Important :
|
|
L'email est envoyé
|
|
- au client principale,
|
|
- au mail du invoiced
|
|
- en copie le responsable du compte chez OF
|
|
"""
|
|
tab_email_contact_client_destinataire = []
|
|
client_data = MYSY_GV.dbname['partner_client'].find_one({'_id': ObjectId(str(diction['client_id'])),
|
|
'partner_owner_recid': str(
|
|
my_partner['recid']),
|
|
'valide': '1',
|
|
'locked': '0'}, {'email':1, 'invoice_email':1, 'responsable_compte_id':1})
|
|
|
|
if( client_data and "email" in client_data.keys()):
|
|
tab_email_contact_client_destinataire.append(client_data['email'])
|
|
|
|
if (client_data and "invoice_email" in client_data.keys()):
|
|
tab_email_contact_client_destinataire.append(client_data['invoice_email'])
|
|
|
|
responsable_compte_email = ""
|
|
if (client_data and "responsable_compte_id" in client_data.keys()):
|
|
responsable_compte_data = MYSY_GV.dbname['ressource_humaine'].find_one({'_id': ObjectId(str(client_data['responsable_compte_id'])),
|
|
'partner_recid': str(
|
|
my_partner['recid']),
|
|
'valide': '1',
|
|
'locked': '0'}, {'email':1, 'nom':1, 'prenom':1})
|
|
|
|
if( responsable_compte_data and 'email' in responsable_compte_data.keys()):
|
|
responsable_compte_email = responsable_compte_data['email']
|
|
|
|
|
|
toaddrs = ",".join(tab_email_contact_client_destinataire)
|
|
|
|
msg['to'] = str(toaddrs)
|
|
msg['cc'] = str(responsable_compte_email)
|
|
|
|
|
|
|
|
smtpserver = smtplib.SMTP(partner_SMTP_COUNT_smtpsrv, partner_SMTP_COUNT_port)
|
|
|
|
smtpserver.ehlo()
|
|
smtpserver.starttls()
|
|
smtpserver.login(partner_SMTP_COUNT_user, partner_SMTP_COUNT_password)
|
|
|
|
val = smtpserver.send_message(msg)
|
|
smtpserver.close()
|
|
print(" Email envoyé icii" + str(val))
|
|
|
|
################################
|
|
|
|
|
|
"""
|
|
Inserter la relantion dans la collection : 'relance_client_invoice'
|
|
"""
|
|
new_relance_data = {}
|
|
new_relance_data['client_id'] = diction['client_id']
|
|
new_relance_data['tab_invoice'] = tab_invoice_a_relancer
|
|
new_relance_data['relance_date'] = str(datetime.now().strftime("%d/%m/%Y %H:%M"))
|
|
new_relance_data['relance_level'] = str(new_relance_level)
|
|
new_relance_data['relance_by'] = str(my_partner['_id'])
|
|
new_relance_data['total_reste_to_paye'] = str(total_reste_to_paye)
|
|
new_relance_data['partner_owner_recid'] = str(my_partner['recid'])
|
|
|
|
new_relance_data['update_date'] = str(datetime.now())
|
|
new_relance_data['update_by'] = str(my_partner['_id'])
|
|
new_relance_data['valide'] = "1"
|
|
new_relance_data['locked'] = "0"
|
|
new_relance_data['mode_envoi'] = "email"
|
|
|
|
relance_insert = MYSY_GV.dbname['relance_client_invoice'].insert_one(new_relance_data)
|
|
|
|
if (relance_insert and relance_insert.inserted_id):
|
|
"""
|
|
Mettre à jour la relance des factures
|
|
"""
|
|
todays_date_heure = datetime.now().strftime("%d/%m/%Y %H:%M")
|
|
for fact in tab_invoice_a_relancer:
|
|
new_data = {}
|
|
new_data['date_relance'] = str(todays_date_heure)
|
|
new_data['relance_id'] = str(relance_insert.inserted_id)
|
|
|
|
create = MYSY_GV.dbname['partner_invoice_header'].update_one({
|
|
'_id': ObjectId(str(fact['invoice_id'])),
|
|
'partner_owner_recid': str(my_partner['recid']),
|
|
},
|
|
{
|
|
'$push': {
|
|
"tab_relance": {
|
|
'$each': [new_data]
|
|
}
|
|
}
|
|
},
|
|
|
|
)
|
|
|
|
"""
|
|
Aller stocker le document PDF de relance et le rattacher a la relance
|
|
"""
|
|
saved_outputFilename = outputFilename
|
|
if (orig_file_name and outputFilename):
|
|
new_file = {}
|
|
new_file['token'] = diction['token']
|
|
type_document = "relance_facture"
|
|
|
|
new_file['file_business_object'] = str(orig_file_name)
|
|
|
|
new_file['file_name'] = str(orig_file_name)
|
|
new_file['status'] = "1"
|
|
new_file['type_document'] = str(type_document)
|
|
|
|
new_file['object_owner_collection'] = "partner_client"
|
|
new_file['object_owner_id'] = str(diction['client_id'])
|
|
new_file['file_name_to_store'] = outputFilename
|
|
|
|
tab_key_word = ""
|
|
new_file['tab_key_word'] = tab_key_word
|
|
|
|
tab_related_collection = []
|
|
node_class = {}
|
|
node_class['related_collection'] = 'relance_client_invoice'
|
|
node_class['related_collection_id'] = str(relance_insert.inserted_id)
|
|
tab_related_collection.append(node_class)
|
|
|
|
#print(" ### new_file new_file = ", new_file)
|
|
|
|
local_status, local_retval = attached_file_mgt.Internal_Usage_Store_User_Downloaded_File(
|
|
MYSY_GV.upload_folder, new_file, tab_related_collection)
|
|
|
|
if (local_status is False):
|
|
print(" ## WARINNGGG Impossible de stocker le fichier de Convocation ")
|
|
|
|
if (local_retval):
|
|
saved_outputFilename = local_retval
|
|
|
|
|
|
|
|
"""
|
|
# Ajout de l'evenement sur la fiche client
|
|
"""
|
|
now = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
|
history_event_dict = {}
|
|
history_event_dict['token'] = diction['token']
|
|
history_event_dict['related_collection'] = "partnair_account"
|
|
history_event_dict['related_collection_recid'] = str(diction['client_id'])
|
|
history_event_dict['action_date'] = str(now)
|
|
history_event_dict['action_description'] = "Relance facture client "
|
|
|
|
local_status, local_retval = mycommon.Collection_Historique.Add_Historique_Event(history_event_dict)
|
|
if (local_status is False):
|
|
mycommon.myprint(
|
|
|
|
" WARNING : Impossible de logguer l'historique pour l'évènement : " + str(history_event_dict))
|
|
|
|
|
|
|
|
return True, " La relance client a été correctement envoyée par email "
|
|
|
|
|
|
except Exception as e:
|
|
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
|
return False, " Impossible d'envoyer la relance client par email"
|
|
|
|
|
|
"""
|
|
Recuperer la liste des relances avec un filter
|
|
"""
|
|
def Get_List_Relance_with_filter(diction):
|
|
try:
|
|
diction = mycommon.strip_dictionary(diction)
|
|
|
|
"""
|
|
Verification des input acceptés
|
|
"""
|
|
field_list = ['token', 'client_id', 'invoice_id', 'invoice_ref', 'client_info']
|
|
|
|
incom_keys = diction.keys()
|
|
for val in incom_keys:
|
|
if val not in field_list and val.startswith('my_') is False:
|
|
mycommon.myprint(str(
|
|
inspect.stack()[0][3]) + " Le champ '" + val + "' n'est pas autorisé")
|
|
return False, " Les informations fournies sont incorrectes"
|
|
|
|
"""
|
|
Verification des champs obligatoires
|
|
"""
|
|
field_list_obligatoire = ['token',]
|
|
|
|
for val in field_list_obligatoire:
|
|
if val not in diction:
|
|
mycommon.myprint(
|
|
str(inspect.stack()[0][3]) + " - La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
|
return False, " Les informations fournies sont incorrectes"
|
|
|
|
"""
|
|
Verification de l'identité et autorisation de l'entité qui
|
|
appelle cette API
|
|
"""
|
|
token = ""
|
|
if ("token" in diction.keys()):
|
|
if diction['token']:
|
|
token = diction['token']
|
|
|
|
local_status, my_partner = mycommon.Check_Connexion_And_Return_Partner_Data(diction)
|
|
if (local_status is not True):
|
|
return local_status, my_partner
|
|
|
|
"""
|
|
Filtre général sur le client (raison_sociale, nom, email, telephone
|
|
"""
|
|
filt_gle_client = {}
|
|
tab_client_id = []
|
|
if ("client_info" in diction.keys() and diction['client_info']):
|
|
|
|
local_myquery_gle = {
|
|
"partner_owner_recid": str(my_partner['recid']),
|
|
"valide": "1",
|
|
"locked": "0",
|
|
"$or": [
|
|
{'raison_sociale': {'$regex': mycommon.regex_replace_cartere(str(diction['client_info'])),
|
|
"$options": "i"}},
|
|
{'nom': {'$regex': mycommon.regex_replace_cartere(str(diction['client_info'])),
|
|
"$options": "i"}},
|
|
{'email': {'$regex': mycommon.regex_replace_cartere(str(diction['client_info'])),
|
|
"$options": "i"}},
|
|
{'telephone': {'$regex': mycommon.regex_replace_cartere(str(diction['client_info'])),
|
|
"$options": "i"}},
|
|
]
|
|
}
|
|
|
|
for tmp in MYSY_GV.dbname['partner_client'].find(local_myquery_gle):
|
|
tab_client_id.append(str(tmp['_id']))
|
|
|
|
|
|
if ("client_id" in diction.keys() and diction['client_id']):
|
|
if (diction['client_id'] not in tab_client_id):
|
|
tab_client_id.append(str(diction['client_id']))
|
|
|
|
filt_gle_client = {'client_id': {'$in': tab_client_id}}
|
|
|
|
|
|
filter_invoice_id = {}
|
|
if ("invoice_id" in diction.keys() and diction['invoice_id']):
|
|
filter_invoice_id = {'tab_invoice.invoice_id': str(diction['invoice_id'])}
|
|
|
|
filter_invoice_ref = {}
|
|
if ("invoice_ref" in diction.keys() and diction['invoice_ref']):
|
|
filter_invoice_ref = {'tab_invoice.invoice_header_ref_interne': str(diction['invoice_ref'])}
|
|
|
|
qery_match = {'$and': [{"partner_owner_recid": str(my_partner['recid']), 'valide': '1',
|
|
'locked': '0'},
|
|
filter_invoice_id, filter_invoice_ref, filt_gle_client]}
|
|
|
|
#print(" ### relance list : qery_match = ", qery_match)
|
|
|
|
RetObject = []
|
|
val_tmp = 0
|
|
for New_retVal in MYSY_GV.dbname['relance_client_invoice'].find(qery_match).sort([("_id", pymongo.DESCENDING), ]):
|
|
user = New_retVal
|
|
user['id'] = str(val_tmp)
|
|
|
|
if( "mode_envoi" not in user.keys() ):
|
|
user['mode_envoi'] = ""
|
|
|
|
strored_document_id = ""
|
|
strored_document_file_extention = ""
|
|
strored_document_file_business_object = ""
|
|
|
|
"""
|
|
Aller recuperer l'_id du document de relance associé
|
|
"""
|
|
qry_get_document = {}
|
|
|
|
qry_get_document['type_document'] = "relance_facture"
|
|
qry_get_document['partner_owner_recid'] = str(my_partner['recid'])
|
|
qry_get_document['valide'] = "1"
|
|
qry_get_document['locked'] = "0"
|
|
qry_get_document['tab_related_collection'] = {
|
|
'$elemMatch': {
|
|
'related_collection': "relance_client_invoice",
|
|
'related_collection_id': str(user['_id'])
|
|
}
|
|
}
|
|
|
|
#print(" QRY gry download_files_relance = ", qry_get_document)
|
|
|
|
|
|
download_files_relance = MYSY_GV.dbname['download_files'].find_one(qry_get_document, {'_id':1, 'file_extention':1, 'file_business_object':1})
|
|
if( download_files_relance and '_id' in download_files_relance.keys()):
|
|
strored_document_id = str(download_files_relance['_id'])
|
|
|
|
if (download_files_relance and 'file_extention' in download_files_relance.keys()):
|
|
strored_document_file_extention = str(download_files_relance['file_extention'])
|
|
|
|
if (download_files_relance and 'file_business_object' in download_files_relance.keys()):
|
|
strored_document_file_business_object = str(download_files_relance['file_business_object'])
|
|
|
|
user['stored_document_id'] = str(strored_document_id)
|
|
user['strored_document_file_extention'] = str(strored_document_file_extention)
|
|
user['strored_document_file_business_object'] = str(strored_document_file_business_object)
|
|
|
|
|
|
val_tmp = val_tmp + 1
|
|
RetObject.append(mycommon.JSONEncoder().encode(user))
|
|
|
|
|
|
return True, RetObject
|
|
|
|
except Exception as e:
|
|
exc_type, exc_obj, exc_tb = sys.exc_info()
|
|
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
|
return False, " Impossible de récupérer la liste des relances "
|