85 Commits

Author SHA1 Message Date
15d9ce3b77 correction bug import DD MAIF PL 2024-06-16 06:50:46 +02:00
86889f33ce fusion devis et facture dans un chantier 2024-03-25 10:57:22 +01:00
58371d3b84 ajout libelle status dans liste docs 2024-01-27 15:33:59 +01:00
76e46da8f2 added prestatataire PE dans MAIF 2023-10-27 16:43:07 +02:00
26e131ca22 removed bcc to phuoc 2023-10-14 18:06:39 +02:00
556b49e002 fixed updates stats 2023-10-14 17:59:40 +02:00
0615cccc06 comented updates stats 2023-10-14 17:56:30 +02:00
07011002f3 added last modified tables in dashboard 2023-10-14 16:10:02 +02:00
b0f25625ee uncomment for operation 2023-10-14 07:47:00 +02:00
2efeb53cec sending email reminders with bcc to phuoc 2023-10-11 14:24:59 +02:00
1c4fb6d701 sending email reminders by company 2023-10-11 12:20:53 +02:00
7dfc0064d1 added AVANSSUR in SINAPPS PDF 2023-09-27 07:03:04 +02:00
80ca84804e remove set_trace() 2023-09-24 09:00:09 +02:00
bb249c873a get societe in axa request 2023-09-24 08:53:22 +02:00
25212af64f intégrer les PDF de SINAPPS 2023-09-22 14:19:35 +02:00
98efbe2c2e changer email de demande de DOMUS 2023-07-21 11:03:56 +02:00
86d72a507a ne pas afficher nb_mails dans home 2023-07-21 10:50:56 +02:00
240684b574 fixed open outlook.office386.com with ssl 2023-07-19 15:33:49 +02:00
35c06a9c5e repr fixed 2023-05-15 19:46:04 +02:00
a304f35be8 repr(e) 2023-05-15 19:43:17 +02:00
0e0d56de07 send mail only from admin_email 2023-05-15 19:25:45 +02:00
d84f30287a log only first 400 char of error text 2023-05-15 18:27:26 +02:00
d22b219432 log first 400 char of error message 2023-05-15 18:17:21 +02:00
e99eb82222 log only first 400 char of error message 2023-05-15 18:13:21 +02:00
c908a21433 fixed admin email in production.ini 2023-05-15 17:48:37 +02:00
cc21ae1559 changed smtp in production.ini 2023-05-14 19:05:04 +02:00
94027e396e utilise le SMTP de Office386 2023-05-09 14:52:17 +02:00
0f28596721 enlève f-string car non comptatible avec python 3.5 2023-04-29 15:00:17 +02:00
47a85b66f4 connect to exchange server 2023-04-27 12:57:48 +02:00
123230b8c9 ajout de PO dans email de demandes 2022-11-29 11:11:25 +01:00
13144ca430 fixed upload demande AXA FRANCE 2022-11-01 10:53:11 +01:00
afd6906667 Revert "bug upload demande AXA"
This reverts commit d5ee75bcf7.
2022-11-01 10:30:00 +01:00
eceb08fb7f Revert "bug lecture no sinistre AXA"
This reverts commit 8f7ef2d2ba.
2022-11-01 10:28:17 +01:00
8f7ef2d2ba bug lecture no sinistre AXA 2022-10-28 08:24:10 +02:00
d5ee75bcf7 bug upload demande AXA 2022-10-28 07:55:29 +02:00
cdb85e6eb2 fixed label visu_tele in rdf_vew.pt and rdf_edit.pt 2022-10-18 12:16:01 +02:00
4f22d9da41 ne prendre que les 11er car. du no sinistre MAIF 2022-09-04 15:40:29 +02:00
cd8a69210d change cd_cli maif menuserie 2022-08-25 17:42:45 +02:00
1fdd9bda91 si user=TST alors interdire générer dossier par email 2022-08-22 13:25:15 +02:00
35922aed85 removed infrastructure.pt 2022-06-08 07:38:22 +02:00
b3e733888b purge des versements obsolètes 2022-02-27 11:35:41 +01:00
ed897c728f fixed article update 2022-02-07 11:53:23 +01:00
4865576862 accès Consulation peut voir les documents attachés 2021-11-24 09:43:19 +01:00
c8883bda11 création acces = Consultation et table p_acces 2021-11-10 18:31:31 +01:00
3c23bf36ea tuning home.pt 2021-11-04 10:17:28 +01:00
2305f4761e afficher nb de mails à importer 2021-10-02 16:47:32 +02:00
7afcb9b8b1 retouche home.pt et modif statut dossier 2021-10-02 12:55:40 +02:00
e346440748 ne plus les bages : TROP LONG 2021-08-26 17:02:34 +02:00
08ef4dcb07 ne plus afficher nb rdv : TROP LONG 2021-08-26 16:49:05 +02:00
cc12a9baa3 ne plus afficher le nb de mail à importer : TROP LONG 2021-08-26 16:28:06 +02:00
9e0679d4c8 optimiser affichage home.pt 2021-08-26 11:52:45 +02:00
9bcd435f6c enleve set_trace 2021-08-25 07:07:10 +02:00
c0f911efc2 retablir affichage nb emails à importer 2021-08-24 15:25:46 +02:00
175a53080a supprimer PROVISOIREMENT affichage nb emails à importer 2021-08-24 09:46:02 +02:00
9532f09cdd added update devis status 4 2021-08-23 21:13:29 +02:00
bb83650b92 enlever commentaires 2021-08-23 13:42:50 +02:00
398b5ab56b bug fetch message ordre de mission 2021-08-23 13:39:14 +02:00
4a0810acb3 remettre demandes.pt comme avant 2021-08-23 12:15:45 +02:00
0f37a80263 retour à onglet specifique dans dossier_view.pt 2021-08-21 09:46:33 +02:00
049f06604a test dessin_edit 2021-08-20 15:40:00 +02:00
52b30b5660 remplacer devis-en-attente par derniers-suivis 2021-08-18 11:17:40 +02:00
8efd6589bc mise en prod 2021-08-15 16:11:18 +02:00
thienan
15dc6603ba minor fix stats devis 2021-08-13 10:38:03 +02:00
cthienan
cbcdd3e19f Merge branch 'master' of https://bitbucket.org/caotek/dumas_gestion 2021-08-13 10:16:14 +02:00
cthienan
c74f60de09 correction stats devis 2021-08-13 10:15:43 +02:00
dfbc1ea083 ajout croquis_edit 2021-08-06 19:34:11 +02:00
c0a7f5bd9b ajout saisie de notes dans dossier 2021-08-03 18:00:34 +02:00
5e933151ed Merge branch 'master' of https://bitbucket.org/caotek/dumas_gestion 2021-08-03 08:15:30 +02:00
59d42f81ac modifs ??? 2021-08-03 08:15:13 +02:00
cthienan
0e77614b89 ajouts astats pourcentage devis + modif permission devis gest 2021-08-02 15:05:28 +02:00
cthienan
63105f5c67 bug fix 2021-07-28 17:15:01 +02:00
cthienan
2917fd4159 ajout ca groupe global 2021-07-28 16:59:08 +02:00
cthienan
d47e2a2378 ajout import de la table relou... (tarifs IMH) 2021-07-26 15:21:06 +02:00
5b8c73caea use ALINTO smtp relay for production 2021-07-24 16:14:28 +02:00
cthienan
9cd021f474 ajout import tarif maif 2021-07-22 14:43:42 +02:00
cthienan
1d1b8b29a7 ajout import tarif maif 2021-07-22 14:42:04 +02:00
a6eeb30b82 import tarifs 2021-07-21 20:34:10 +02:00
7a706ba9f2 change email dumas passwords 2021-07-21 20:32:01 +02:00
thienan
c142e0ccb6 correction liens devis en att et facture en att 2021-07-13 15:16:35 +02:00
thienan
fe2c58f65f harmonisation dem_devis, devs et facture 2021-07-12 15:10:38 +02:00
thienan
e83c5f2d53 fix 2021-07-07 12:07:29 +02:00
thienan
839b913b02 conversion new_home en home 2021-07-07 12:06:37 +02:00
thienan
f742176c1e correction new_home + stats + dem_devis 2021-07-07 12:04:03 +02:00
31ed53f0f1 merge 2021-07-06 16:05:46 +02:00
02fdd57538 changement de SMTP alinto 2021-07-06 15:18:55 +02:00
73 changed files with 5201 additions and 1405 deletions

7
.vscode/launch.json vendored
View File

@@ -4,15 +4,16 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "python",
"request": "launch",
"name": "Pserve",
"python": "/Users/Thien-An/AppData/Local/Programs/Python/Python37",
"program": "/pyramid/Scripts/pserve",
"python": "C:/pyramid10/Scripts/python.exe",
"program": "/pyramid10/Scripts/pserve.exe",
"args": [
"-v",
"development.ini"
"development.ini",
],
"console": "integratedTerminal",
"debugOptions": [

View File

@@ -1,5 +1,5 @@
{
"html.validate.scripts": false,
"editor.minimap.enabled": false,
"python.pythonPath": "C:\\Users\\Thien-An\\AppData\\Local\\Programs\\Python\\Python37\\python.exe"
"python.pythonPath": "C:\\pyramid10\\Scripts\\python.exe"
}

View File

@@ -0,0 +1 @@
9HMYPQ7Yz8emdBwGpbg3

View File

@@ -19,18 +19,26 @@ pyramid.includes =
pyramid_tm
#sqlalchemy.url = mysql://phuoc:phuoc!@localhost/bddevfac?charset=utf8
sqlalchemy.url = mysql://phuoc:phuoc!@192.168.1.17/bddevfac?charset=utf8
# sqlalchemy.url = mysql://dumas_ro:dumas_RO!@localhost/bddevfac?charset=utf8
sqlalchemy.url = mysql://phuoc:phuoc!@localhost/bddevfac?charset=utf8
# sqlalchemy.url = mysql://phuoc:phuoc!@192.168.0.31/bddevfac?charset=utf8
mondumas.admin_email = cao.thien-phuoc@orange.fr
mondumas.admin_email = peinture-dumas@entreprise-dumas.com
mondumas.devfac_url = mondumas:static/DEVFAC/DOCS_ATTACHES/
mondumas.devfac_dir = /DEVFAC14/DOCS_ATTACHES
# Mailer configuration
mail.host = smtp.orange.fr
mail.port = 25
mail.username = cao.thien-phuoc@orange.fr
mail.host = smtp.office365.com
mail.port = 587
mail.tls = True
mail.username = peinture-dumas@entreprise-dumas.com
mail.password = Nar50611
# SMTP RELAY alinto
# mail.host = gatewayxl.alinto.net
# mail.port = 465
# mail.ssl = True
# mail.username = smtpsasdumas@entreprise-dumas.com
[server:main]
use = egg:waitress#main

View File

49
imap-over-tls-example.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
import imaplib
import msal
import pprint
conf = {
"authority": "https://login.microsoftonline.com/47f02128-acf0-47a7-a3bf-27a3619d4a4f",
"client_id": "4fdf2634-a260-4576-a442-684998f0187b", #AppID
"scope": ['https://outlook.office365.com/.default'],
"secret": "dZD8Q~GmG_PFi.t_uRiyBEzwLeMZeemQGHUFta~J", #Key-Value
"secret-id": "5cae3249-0fc0-43e2-bc0e-d78f41964970", #Key-ID
}
def generate_auth_string(user, token):
return f"user={user}\x01auth=Bearer {token}\x01\x01"
if __name__ == "__main__":
app = msal.ConfidentialClientApplication(conf['client_id'], authority=conf['authority'],
client_credential=conf['secret'])
result = app.acquire_token_silent(conf['scope'], account=None)
if not result:
print("No suitable token in cache. Get new one.")
result = app.acquire_token_for_client(scopes=conf['scope'])
if "access_token" in result:
print(result['token_type'])
pprint.pprint(result)
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
# imap = imaplib.IMAP4('outlook.office365.com')
# imap.starttls()
imap = imaplib.IMAP4_SSL('outlook.office365.com', 993)
imap.authenticate("XOAUTH2", lambda x: generate_auth_string("polynet-dumas@entreprise-dumas.com",
result['access_token']).encode("utf-8"))
# Print list of mailboxes on server
code, mailboxes = imap.list()
for mailbox in mailboxes:
print(mailbox.decode("utf-8"))
# Select mailbox
imap.select("INBOX")
# Cleanup
imap.close()

View File

@@ -2,12 +2,10 @@ Metadata-Version: 2.1
Name: mondumas
Version: 1.0
Summary: mondumas
Home-page: UNKNOWN
Home-page:
Author:
Author-email:
License: UNKNOWN
Keywords: web wsgi bfg pylons pyramid
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Framework :: Pyramid
Classifier: Topic :: Internet :: WWW/HTTP
@@ -56,5 +54,3 @@ Elle est développée avec les composants open source suivants :
---
- Initial version

View File

@@ -1,8 +1,8 @@
CHANGES.txt
MANIFEST.in
README.md
bitbucket app password.txt
development.ini
diff.txt
production.ini
pytest.ini
setup.py

View File

@@ -1,4 +1,2 @@
[paste.app_factory]
main = mondumas:main
[console_scripts]
[paste.app_factory]
main = mondumas:main

View File

@@ -1,14 +1,14 @@
pyramid
pyramid==1.10
pyramid_chameleon
pyramid_debugtoolbar
pyramid_layout
pyramid_mailer
pyramid_tm
SQLAlchemy
SQLAlchemy==1.2.19
transaction
zope.sqlalchemy==1.1
waitress
mysqlclient
mysqlclient==1.4
docutils
pdfkit
python-dateutil

View File

@@ -23,7 +23,7 @@ def execute_query(request, query, params):
def get_users_agenda(request, user):
if user == '':
""" lire la liste des users ayant un agenda"""
query = "SELECT * FROM p_users WHERE cd_uti != 'N' and cd_uti = agenda AND actif != 0 ORDER BY nom;"
query = "SELECT * FROM p_users WHERE cd_uti = agenda AND actif != 0 ORDER BY nom;"
results = request.dbsession.execute(query).fetchall()
else:
""" lire le user ayant un agenda"""

View File

@@ -33,31 +33,29 @@ def get_member_by_mdp_oublie(request, lien):
def get_member_by_id(request, mbr_id):
if mbr_id == '0':
query = "SELECT * FROM p_users ORDER BY cd_uti"
query = """SELECT p_users.*, p_acces.libelle FROM p_users
INNER JOIN p_acces ON p_acces.code = p_users.access
ORDER BY cd_uti"""
results = request.dbsession.execute(query).fetchall()
else:
# lire le membres par son identifianr
query = """SELECT * FROM p_users WHERE CD_UTI=:mbr_id;"""
query = """SELECT p_users.*, p_acces.libelle FROM p_users
INNER JOIN p_acces ON p_acces.code = p_users.access
WHERE CD_UTI=:mbr_id;"""
results = request.dbsession.execute(query, {'mbr_id': mbr_id}).first()
return results
def get_member_info(request, logged_in):
# lire le membres par son identifianr
query = """SELECT nom, email, access, societe FROM p_users WHERE CD_UTI=:logged_in;"""
query = """SELECT u.nom, u.email, u.access, u.societe, p_acces.libelle FROM p_users u
INNER JOIN p_acces ON p_acces.code = u.access
WHERE CD_UTI=:logged_in;"""
results = request.dbsession.execute(query, {'logged_in': logged_in}).first()
if results.access == 9:
fonction = 'Administrateur'
elif results.access == 8:
fonction = 'Comptabilité'
elif results.access == 5:
fonction = 'Gestion'
else:
fonction = 'Production'
return {
'nom': results.nom,
'email': results.email,
'fonction': fonction,
'fonction': results.libelle,
'societe': results.societe,
}
@@ -88,9 +86,6 @@ def update_membre(request, cd_uti, new_values):
for param in new_values.keys():
if param == 'nom':
new_values['nom'] = new_values['nom'].upper()
if param == 'access':
' ne prend que le 1er caractère'
new_values['access'] = new_values['access'][0]
if s:
s += ",%s=:%s" % (param, param)
@@ -108,7 +103,7 @@ def delete_membre(request, cd_uti):
query = "DELETE FROM p_users WHERE cd_uti = :cd_uti ;"
execute_query(request, query, {'cd_uti': cd_uti})
def get_article(request, type, groupe, libelle):
def get_tarif_ajax(request, type, groupe, libelle):
if type == 'LIB':
if groupe == 'TEXTE':
# lire tous les textes dont le libelle commençe par
@@ -130,35 +125,35 @@ def get_codespostaux(request, codep):
return results
def get_dd_restant(request):
query = """SELECT SUM(IF(societe='PE',1,0)) AS nb_PE,
SUM(IF(societe='ME',1,0)) AS nb_ME,
SUM(IF(societe='PL',1,0)) AS nb_PL
FROM dem_devis WHERE STATUS=0;"""
query = """SELECT (SELECT count(*) FROM dem_devis WHERE societe='PE' AND STATUS=0) AS nb_PE,
(SELECT count(*) FROM dem_devis WHERE societe='ME' AND STATUS=0) AS nb_ME,
(SELECT count(*) FROM dem_devis WHERE societe='PL' AND STATUS=0) AS nb_PL;"""
results = request.dbsession.execute(query).first()
return results
def get_de_restant(request):
query = """SELECT SUM(IF(societe='PE',1,0)) AS nb_PE,
SUM(IF(societe='ME',1,0)) AS nb_ME,
SUM(IF(societe='PL',1,0)) AS nb_PL
FROM devis WHERE STATUS<4;"""
# les suivis des attachés commerciaux dnas les 4 derniers jours
query = """SELECT
(SELECT count(*) FROM dem_lig WHERE societe='PE' AND DATEDIFF(CURDATE(), date) <= 7 AND usermaj in ('CG','MP','RV','VD')) AS nb_PE,
(SELECT count(*) FROM dem_lig WHERE societe='ME' AND DATEDIFF(CURDATE(), date) <= 7 AND usermaj in ('CG','MP','RV','VD')) AS nb_ME,
(SELECT count(*) FROM dem_lig WHERE societe='PL' AND DATEDIFF(CURDATE(), date) <= 7 AND usermaj in ('CG','MP','RV','VD')) AS nb_PL;"""
results = request.dbsession.execute(query).first()
return results
def get_fa_restant(request):
query = """SELECT SUM(IF(societe='PE',1,0)) AS nb_PE,
SUM(IF(societe='ME',1,0)) AS nb_ME,
SUM(IF(societe='PL',1,0)) AS nb_PL
FROM facture WHERE STATUS<8;"""
query = """SELECT (SELECT count(*) FROM facture WHERE societe='PE' AND STATUS < 8) AS nb_PE,
(SELECT count(*) FROM facture WHERE societe='ME' AND STATUS < 8) AS nb_ME,
(SELECT count(*) FROM facture WHERE societe='PL' AND STATUS < 8) AS nb_PL;"""
results = request.dbsession.execute(query).first()
return results
def get_rdv_by_date(request, date, agenda):
query = """SELECT COUNT(*) AS nb_rdv FROM bddevfac.dem_lig WHERE DATEVI=:date AND LISTE=:agenda;"""
results = request.dbsession.execute(query, {'date': date, 'agenda': agenda}).first()
return results
return results.nb_rdv
def get_rdf_null(request):
query = """SELECT COUNT(*) AS nb_rdf FROM bddevfac.dem_rdf WHERE date_relu IS NULL;"""
# lire les RDF non validés depuis moins d'un an
query = """SELECT COUNT(*) AS nb_rdf FROM bddevfac.dem_rdf WHERE date_relu IS NULL AND year(date_inter) > 2021;"""
results = request.dbsession.execute(query).first()
return results
return results.nb_rdf

View File

@@ -25,19 +25,23 @@ def execute_query(request, query, params):
mark_changed(request.dbsession)
transaction.commit()
def get_devis_byName(request, societe, name):
numero = to_int(name)
if numero > 0:
query = """SELECT date,'DE' AS TYPE, LPAD(no_id,6,'0') AS numero, nomcli, CONCAT(c_nom,'; ',c_adr,'; ',c_ville) AS chantier, COALESCE(totalht,0) AS montant, status, nosin, nopol, nochantier, web
FROM devis WHERE societe=:societe AND no_id >=:name AND web = 'W' LIMIT 300;;""" % (societe, name)
elif len(name) == 0:
query = """SELECT date,'DE' AS TYPE, LPAD(no_id,6,'0') AS numero, nomcli, CONCAT(c_nom,'; ',c_adr,'; ',c_ville) AS chantier, COALESCE(totalht,0) AS montant, status, nosin, nopol, nochantier, web
FROM devis WHERE societe=:societe AND web = 'W' ORDER BY no_id DESC LIMIT 300;"""
else:
query = """(SELECT date,'DE' AS TYPE, LPAD(no_id,6,'0') AS numero, nomcli, CONCAT(c_nom,'; ',c_adr,'; ',c_ville) AS chantier, COALESCE(totalht,0) AS montant, status, nosin, nopol , nochantier, web
FROM devis WHERE societe=:societe AND c_nom LIKE ':name%' AND web = 'W' LIMIT 500)"""
results = request.dbsession.execute(query, {'societe': societe, 'name': name}).fetchall()
def get_dossiers_byName(request, societe, name):
# lires tous les dossiers d'un chantier
query = """select * from (
SELECT date,'DD' AS TYPE, LPAD(no_id,6,'0') AS numero, nomcli, CONCAT(c_nom,'; ',c_adr,'; ',c_ville) AS chantier, 0 AS montant, status, nosin, societe , no_id as nochantier
FROM dem_devis WHERE societe=:societe AND c_nom LIKE :name
UNION
SELECT date,'DE' AS TYPE, LPAD(no_id,6,'0') AS numero, nomcli, CONCAT(c_nom,'; ',c_adr,'; ',c_ville) AS chantier, COALESCE(totalht,0) AS montant, status, nosin, societe , nochantier
FROM devis WHERE societe=:societe AND c_nom LIKE :name
UNION
SELECT date,'FA' AS TYPE, LPAD(no_id,6,'0') AS numero, nomcli, CONCAT(c_nom,'; ',c_adr,'; ',c_ville) AS chantier, COALESCE(totalht,0) AS montant, status, nosin, societe , nochantier
FROM facture WHERE societe=:societe AND c_nom LIKE :name
) a
order by date, TYPE
"""
results = request.dbsession.execute(query, {'societe': societe, 'name': name+'%'}).fetchall()
return results
def get_devfac_by_no(request,nodossier):
@@ -234,3 +238,15 @@ def update_devis_cloture(request, nodevis, status, logged_in):
# met le montant regle à 1 centime pour terminé le dossier
query = "UPDATE devis SET STATUS = :status, USERMAJ = :logged_in WHERE societe=:societe AND no_id=:nochantier;"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'status': status, 'logged_in': logged_in})
def update_devis_nochantier(request, societe, no_devis, nochantier):
# extraire type de doc et no de doc à mettre à jour
type = no_devis[0:2]
no_id = no_devis[3:]
if type == 'DE':
# maj le numero du dossier du devis
query = "UPDATE devis SET nochantier = :nochantier WHERE societe=:societe AND no_id=:no_id;"
else:
# maj le numero du dossier de la facture
query = "UPDATE facture SET nochantier = :nochantier WHERE societe=:societe AND no_id=:no_id;"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'no_id': no_id})

View File

@@ -47,8 +47,14 @@ def get_dossier_by_sinistre(request,societe, nosin):
results = request.dbsession.execute(query).first()
return results
def get_dossiers_traites(request):
query = "SELECT d.*, s.libelle FROM dem_devis d JOIN p_statuts s ON d.STATUS = s.CODE WHERE d.STATUS < 2 ORDER BY d.societe, d.STATUS, d.nomcli";
def get_dossiers_traites(request, societe):
query = """SELECT d.date, LPAD(d.no_id,6,'0') AS numero, d.nomcli, CONCAT(d.c_nom,'; ',d.c_adr,'; ',d.c_ville) AS chantier, d.mttrav AS montant, status, s.libelle, d.nosin, d.nopol, d.humidite, d.usermaj
FROM dem_devis d JOIN p_statuts s ON d.STATUS = s.CODE WHERE d.societe = :societe AND d.status < 2 ORDER BY d.status, d.nomcli""";
results = request.dbsession.execute(query, {'societe': societe}).fetchall()
return results
def get_dossiers_importes(request):
query = "SELECT * FROM dem_devis WHERE usermaj='EMAIL' and status=0 ORDER BY DATEMAJ";
results = request.dbsession.execute(query).fetchall()
return results
@@ -57,6 +63,12 @@ def get_clients_byName(request, societe, nom):
results = request.dbsession.execute(query).fetchall()
return results
def get_derniers_suivis(request):
query = """SELECT l.*, CONCAT(l.societe,'-',l.no_id) AS nodossier, d.C_NOM FROM dem_lig l
INNER JOIN dem_devis d ON l.societe=d.societe AND l.no_id=d.no_id
WHERE DATEDIFF(CURDATE(), l.date) <= 7 AND l.usermaj in ('CG','MP','RV','VD') ORDER BY l.date, l.societe DESC;"""
results = request.dbsession.execute(query, ).fetchall()
return results
def get_dossier_rdv_by_no(request,nodossier, nolig):
@@ -286,6 +298,16 @@ def update_rapport_client(request, norapport, nomClient, codeClient):
query = "UPDATE dem_rdf SET CD_CLI = :cd_cli, NOMCLI = :nomClient WHERE no_id = :norapport"
execute_query(request, query, {'norapport': norapport, 'cd_cli': cd_cli, 'nomClient': nomClient})
def update_rapport_nochantier(request, norapport, new_nochantier):
# controler que le no du nouveau dossier existe
dossier = get_dossier_by_no(request,'PL-' + new_nochantier)
if dossier:
query = "UPDATE dem_rdf SET nochantier = :new_nochantier WHERE no_id = :norapport"
execute_query(request, query, {'norapport': norapport, 'new_nochantier': new_nochantier})
return "OK"
else:
return "NOK"
def insert_facture_rdf(request, societe, nochantier, cd_cli, nomcli, user, ref, date_rapport):
# créer une facture vierge à partir du dossier
query = "CALL spINS_FACTURE_RDF(:societe, :nochantier, :cd_cli, :nomcli, :user, :ref, :date_rapport)"
@@ -338,12 +360,51 @@ def get_status_by_id(request, code):
results = request.dbsession.execute(query, {'code': code}).first()
return results
def get_devis_en_att(request):
query = "SELECT d.*, s.libelle FROM devis d JOIN p_statuts s ON d.STATUS = s.CODE WHERE d.STATUS < 4 ORDER BY d.societe, d.STATUS, d.nomcli;"
results = request.dbsession.execute(query).fetchall()
def get_motifs(request):
query = """SELECT * FROM p_motifs;"""
results = request.dbsession.execute(query,).fetchall()
return results
def get_factures_en_att(request):
query = "SELECT f.*, s.libelle FROM facture f JOIN p_statuts s ON f.STATUS = s.CODE WHERE f.STATUS < 8 ORDER BY f.societe, f.STATUS, f.nomcli;"
results = request.dbsession.execute(query).fetchall()
def get_factures_en_att(request, societe):
query = """SELECT f.date, LPAD(f.no_id,6,'0') AS numero, f.nomcli, CONCAT(f.c_nom,'; ',f.c_adr,'; ',f.c_ville) AS chantier, f.totalht AS montant, f.status, s.libelle, f.nosin, f.nopol, f.usermaj
FROM facture f JOIN p_statuts s ON f.STATUS = s.CODE WHERE f.societe=:societe AND f.STATUS < 8 ORDER BY f.societe, f.STATUS, f.nomcli;"""
results = request.dbsession.execute(query, {'societe': societe}).fetchall()
return results
def get_dem_notes(request, nodossier, noligne):
societe = nodossier[0:2]
nochantier = int(nodossier[3:])
if noligne == '0':
query = "SELECT societe, nochantier, noligne, libelle FROM dem_notes WHERE societe = :societe AND nochantier = :nochantier ORDER BY libelle;"
results = request.dbsession.execute(query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne}).fetchall()
else:
query = "SELECT * FROM dem_notes WHERE societe = :societe AND nochantier = :nochantier AND noligne = :noligne;"
results = request.dbsession.execute(query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne}).first()
return results
def delete_dem_note(request, nodossier, noligne):
societe = nodossier[0:2]
nochantier = int(nodossier[3:])
# une note ou dessin
query = "DELETE FROM dem_notes WHERE societe=:societe AND nochantier=:nochantier AND noligne=:noligne;"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne})
def update_dem_note(request, nodossier, noligne, notes, logged_in):
societe = nodossier[0:2]
nochantier = int(nodossier[3:])
# création ou modif ?
if noligne == '0':
query = "INSERT INTO dem_notes (societe, nochantier, notes, usermaj) VALUES (:societe, :nochantier, :notes, :logged_in);"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'notes': notes, 'logged_in': logged_in})
else:
query = "UPDATE dem_notes SET notes=:notes, logged_in=:logged_in WHERE societe=:societe AND nochantier=:nochantier AND noligne=:noligne;"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne, 'notes': notes, 'logged_in': logged_in})
def get_nb_dessins(request, nodossier):
societe = nodossier[0:2]
nochantier = nodossier[3:]
query = "SELECT count(*) AS nb FROM dossier_attaches WHERE societe=:societe AND nochantier=:nochantier AND origine='FRN' AND nomfichier LIKE '%DESSIN No %';"
results = request.dbsession.execute(query, {'societe': societe, 'nochantier': nochantier}).first()
return results.nb

View File

@@ -1,17 +1,8 @@
# -*- coding: utf8 -*-
from sqlalchemy import text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
scoped_session,
sessionmaker,
)
from zope.sqlalchemy import (
ZopeTransactionExtension,
mark_changed
)
from datetime import *
import dateutil.relativedelta
import transaction
def execute_query(request, query, params):
@@ -26,6 +17,25 @@ def get_log_nuit(request, ):
results = request.dbsession.execute(query, )
return results.fetchall()
def get_last_modified(request,):
query = """
SELECT 'DEMANDES' as nomtable, societe, no_id, datemaj, usermaj FROM bddevfac.dem_devis where date(datemaj)=CURDATE()
UNION
SELECT 'DEM_LIGNE' as nomtable, societe, no_id, datemaj, usermaj FROM bddevfac.dem_lig where date(datemaj)=CURDATE()
UNION
SELECT 'DEVIS' as nomtable, societe, no_id, datemaj, usermaj FROM bddevfac.devis where date(datemaj)=CURDATE()
UNION
SELECT 'DEVIS_LIG' as nomtable, societe, no_id, datemaj, usermaj FROM bddevfac.devis_lig where date(datemaj)=CURDATE()
UNION
SELECT 'FACTURES' as nomtable, societe, no_id, datemaj, usermaj FROM bddevfac.facture where date(datemaj)=CURDATE()
UNION
SELECT 'FACT_LIG' as nomtable, societe, no_id, datemaj, usermaj FROM bddevfac.facture_lig where date(datemaj)=CURDATE()
UNION
SELECT 'FAC_REGL' as nomtable, societe, cod_bnq, date as datemaj, JST FROM bddevfac.facture_reg where date(date)=CURDATE()
order by datemaj desc limit 10; """
results = request.dbsession.execute(query, )
return results.fetchall()
def get_rappels_rdv(request):
"""Lire les 200 derniers envois d'emails"""
query = "SELECT * FROM email_rappels ORDER BY no_id DESC LIMIT 400;"
@@ -33,7 +43,6 @@ def get_rappels_rdv(request):
return results.fetchall()
def get_dossiers_byChantier(request, societe, name):
query = "CALL spGET_DOSSIERS_byChantier('%s','%s','%s');" % (societe, 'DE', name.replace("'","''"))
results = request.dbsession.execute(query).fetchall()
return results
@@ -114,6 +123,11 @@ def get_articles_byFam(request, fam):
results = request.dbsession.execute(query, ).fetchall()
return results
def get_article(request, ref):
query = "SELECT * FROM articles WHERE REF = ref;"
results = request.dbsession.execute(query, ).first()
return results
def update_article(request, ref, new_values):
# formater les champs
s = ''
@@ -227,3 +241,8 @@ def get_tarif(request, groupe, ref):
query = "SELECT * FROM tarifs WHERE groupe = :groupe and ref = :ref;"
results = request.dbsession.execute(query, {'groupe': groupe, 'ref': ref}).first()
return results
def get_p_acces(request):
query = """SELECT * FROM p_acces ORDER BY code;"""
results = request.dbsession.execute(query).fetchall()
return results

View File

@@ -82,6 +82,20 @@ def get_ca_groupe_3y(request, societe, year):
results = request.dbsession.execute(query, {'societe': societe, 'year': year})
return results.fetchall()
def get_ca_groupe_3y_with_others(request, societe, year):
query = """SELECT groupe,
SUM(IF (year(date) = :year - 2, TOTALHT, 0)) as Annee1,
SUM(IF (year(date) = :year - 2, 1, 0)) as Count1,
SUM(IF (year(date) = :year - 1, TOTALHT, 0)) as Annee2,
SUM(IF (year(date) = :year - 1, 1, 0)) as Count2,
SUM(IF (year(date) = :year, TOTALHT, 0)) as Annee3,
SUM(IF (year(date) = :year, 1, 0)) as Count3
FROM bddevfac.facture
WHERE societe=:societe AND year(date) >= :year - 2 AND typecli <> 'I' GROUP BY groupe;"""
results = request.dbsession.execute(query, {'societe': societe, 'year': year})
return results.fetchall()
def get_ca_clients_12m(request, societe, datedeb, datefin):
query = """SELECT DATE_FORMAT(date, "%Y%m") as yymm,
@@ -127,3 +141,32 @@ def get_delais_pourcent(request, societe, groupe, datedeb):
WHERE societe = :societe AND GROUPE = :groupe AND date >= :datedeb;"""
results = request.dbsession.execute(query, {'societe': societe, 'groupe': groupe, 'datedeb': datedeb.strftime("%Y-%m")})
return results.fetchall()
def get_nb_devis_fact(request, societe, year):
query = """SELECT
SUM(IF (NOFACT > 0 AND year(date) = :year - 2, 1, 0)) AS devis_fact_y1,
SUM(IF (NOFACT <= 0 AND year(date) = :year - 2, 1, 0)) AS devis_non_fact_y1,
SUM(IF (NOFACT > 0 AND year(date) = :year - 1, 1, 0)) AS devis_fact_y2,
SUM(IF (NOFACT <= 0 AND year(date) = :year - 1, 1, 0)) AS devis_non_fact_y2,
SUM(IF (NOFACT > 0 AND year(date) = :year, 1, 0)) AS devis_fact_y3,
SUM(IF (NOFACT <= 0 AND year(date) = :year, 1, 0)) AS devis_non_fact_y3
FROM bddevfac.devis
WHERE societe = :societe;"""
results = request.dbsession.execute(query, {'societe': societe, 'year': year})
return results.fetchall()
def get_nb_fact_with_devis(request, societe, year):
query = """SELECT
SUM(IF (NODEVIS > 0 AND year(date) = :year - 2, 1, 0)) AS fact_w_devis_y1,
SUM(IF (NODEVIS <= 0 AND year(date) = :year - 2, 1, 0)) AS fact_wo_devis_y1,
SUM(IF (NODEVIS > 0 AND year(date) = :year - 1, 1, 0)) AS fact_w_devis_y2,
SUM(IF (NODEVIS <= 0 AND year(date) = :year - 1, 1, 0)) AS fact_wo_devis_y2,
SUM(IF (NODEVIS > 0 AND year(date) = :year, 1, 0)) AS fact_w_devis_y3,
SUM(IF (NODEVIS <= 0 AND year(date) = :year, 1, 0)) AS fact_wo_devis_y3
FROM bddevfac.facture
WHERE societe = :societe;"""
results = request.dbsession.execute(query, {'societe': societe, 'year': year})
return results.fetchall()

View File

@@ -139,22 +139,25 @@ def purge_mensuelle(request, until_date):
WHERE facture.date < :until_date AND ABS(facture.totalttc - facture.mtregl) < 1;"""
execute_query(request, query, {'until_date': until_date})
# ----- Purger les FACTURES réglées antérieure à until_date
# ----- Purger les FACTURES réglées antérieures à until_date
query = """DELETE FROM facture WHERE facture.date < :until_date AND ABS(facture.totalttc - facture.mtregl) < 1;"""
execute_query(request, query, {'until_date': until_date})
# ---- Purger les VERSEMENTS inutilisés et antérieurs à until_date
query = "DELETE FROM liv_bnq WHERE date < :until_date AND ABS(MontantRegl - MontantDebit) < 1;"
execute_query(request, query, {'until_date': until_date})
# ----- Purger les DEVIS n'ayant pas de facture antérieure à until_date
# ----- Purger les DEVIS n'ayant pas de facture antérieure à until_date
query = """DELETE FROM devis WHERE devis.date < :until_date AND nofact=0;"""
execute_query(request, query, {'until_date': until_date})
# -- RAZ les liens FACTURE et DEVIS
query = "UPDATE dem_devis SET nodevis = 0, nofact = 0, datemaj=datemaj WHERE dem_devis.date < :until_date;"
execute_query(request, query, {'until_date': until_date})
# -- recreer les lien DEVIS
# -- recreer les lien factures
query = """UPDATE dem_devis INNER JOIN facture ON dem_devis.societe = facture.societe and dem_devis.no_id = facture.nochantier
SET dem_devis.nofact = facture.no_id, dem_devis.datemaj=dem_devis.datemaj WHERE dem_devis.date < :until_date;"""
execute_query(request, query, {'until_date': until_date})
# -- recreer les lien factures
# -- recreer les lien DEVIS
query = """UPDATE dem_devis INNER JOIN devis ON dem_devis.societe = devis.societe and dem_devis.no_id = devis.nochantier
SET dem_devis.nodevis = devis.no_id, dem_devis.datemaj=dem_devis.datemaj WHERE dem_devis.date < :until_date"""
execute_query(request, query, {'until_date': until_date})
@@ -183,6 +186,11 @@ def get_last_devis_client(request, societe, cd_cli):
results = request.dbsession.execute(query, {'societe': societe, 'cd_cli': cd_cli}).first()
return results
def get_last_proforma_client(request, societe, cd_cli):
query = "SELECT * FROM proforma WHERE societe = :societe AND cd_cli = :cd_cli order by date DESC LIMIT 1;"
results = request.dbsession.execute(query, {'societe': societe, 'cd_cli': cd_cli}).first()
return results
def get_last_chantier_client(request, societe, cd_cli):
query = "SELECT * FROM dem_devis WHERE societe = :societe AND cd_cli = :cd_cli order by date DESC LIMIT 1;"
results = request.dbsession.execute(query, {'societe': societe, 'cd_cli': cd_cli}).first()
@@ -198,10 +206,27 @@ def update_client_dern_operation(request, societe, cd_cli, dern_operation):
execute_query(request, query, {'societe': societe, 'cd_cli': cd_cli})
def delete_client_unused(request):
query = "DELETE FROM clients WHERE cd_cli <> 1 AND dern_operation IS NULL AND YEAR(cree_le) < YEAR(CURRENT_DATE()) - 2;"
query = "DELETE FROM clients WHERE cd_cli <> 1 AND dern_operation IS NULL AND YEAR(cree_le) < YEAR(CURRENT_DATE()) - 5;"
execute_query(request, query, {})
def update_stats_delais(request, societe, date, groupe):
query = "CALL spUPD_STATS_DELAIS(:societe, :date, :groupe);"
execute_query(request, query, {'societe': societe, 'date': date, 'groupe': groupe})
def update_devis_statut_4(request):
# lire tutes les lignes de dem_devis mentionnant la commande
query = "SELECT * FROM dem_lig where comment like '% est COMMANDE %';"
devis_cdes = request.dbsession.execute(query, {}).fetchall()
for item in devis_cdes:
if item.COMMENT.find('!! DE') == 0:
# recupère le no de devis commandé
nodevis = item.COMMENT[5:11]
# maj status de dem_devis concernée
query = "UPDATE dem_devis SET status = 4, DATEMAJ = DATEMAJ WHERE societe = :societe AND no_id = :no_id AND status < 4;"
execute_query(request, query, {'societe': item.societe, 'no_id': item.NO_ID})
# maj status de devis concerné
query = "UPDATE devis SET status = 4, DATEMAJ = DATEMAJ WHERE societe = :societe AND no_id = :no_id AND status < 4;"
execute_query(request, query, {'societe': item.societe, 'no_id': nodevis})

View File

@@ -5,7 +5,7 @@ def includeme(config):
config.add_route('planning', '/planning/{date}')
config.add_route('rdv_edit','/rdv_edit/{nodossier}/{nolig}')
# default
config.add_route('new_home', '/')
config.add_route('home', '/')
config.add_route('affiche_message','/affiche_message/{login}')
config.add_route('ajax_article', '/ajax_article')
config.add_route('ajax_client', '/ajax_client')
@@ -22,24 +22,35 @@ def includeme(config):
# devis
config.add_route('devis_ligne', '/devis_ligne/{type_ligne}/{nodevis}/{nolig}')
config.add_route('devis_lig_mv', '/devis_lig_mv/{move}/{nodevis}/{nolig}')
config.add_route('devis_list', '/devis_list')
config.add_route('devis_list', '/devis_list/{societe}/{nodevis}')
config.add_route('devis_create', '/devis_create/{nodossier}')
config.add_route('devis_nochantier', '/devis_nochantier/{societe}/{nodevis}/{nochantier}')
config.add_route('devis_web', '/devis_web/{nodevis}')
config.add_route('devis_view', '/devis_view/{nodevis}')
config.add_route('devis_preview', '/devis_preview/{nodevis}')
config.add_route('devis_select', '/devis_select/{date}')
config.add_route('devis_selected', '/devis_selected/{goto}/{date}/{nodevis}')
config.add_route('factures_en_att','/factures_en_att')
config.add_route('facture_select', '/facture_select/{date}')
config.add_route('facture_selected', '/facture_selected/{goto}/{date}/{nofacture}')
# dossier
config.add_route('demandes','/demandes')
config.add_route('demandes_dl','/demandes_dl/{societe}/{email_from}/{email_uid}')
config.add_route('dem_devis','/dem_devis')
config.add_route('delete_img','/delete_img/{nodossier}/{norapport}/{origine}/{nomfic}')
config.add_route('dern_suivis','/dern_suivis')
config.add_route('dessin_edit','/dessin_edit/{nodossier}/{noligne}')
config.add_route('dossier_edit', '/dossier_edit/{nodossier}')
config.add_route('dossier_lookup', '/dossier_lookup')
config.add_route('dossier_select', '/dossier_select/{date}')
config.add_route('dossier_selected', '/dossier_selected/{goto}/{date}/{nodossier}')
config.add_route('dossier_view', '/dossier_view/{nodossier}')
config.add_route('note_edit','/note_edit/{nodossier}/{noligne}')
config.add_route('rdf_bill','/rdf_bill/{no_id}')
config.add_route('rdf_client','/rdf_client/{no_id}')
config.add_route('rdf_edit','/rdf_edit/{nodossier}/{date_inter}')
config.add_route('rdf_list','/rdf_list')
config.add_route('rdf_nochantier','/rdf_nodossier/{no_id}')
config.add_route('rdf_rapport','/rdf_rapport/{no_id}')
config.add_route('rdf_view','/rdf_view/{no_id}')
config.add_route('rotate_img','/rotate_img/{nodossier}/{norapport}/{origine}/{nomfic}/{angle}')
@@ -47,9 +58,6 @@ def includeme(config):
config.add_route('upload_doc', '/upload_doc/{nodossier}/{origine}')
config.add_route('upload_img', '/upload_img/{norapport}/{origine}')
config.add_route('upload_om', '/upload_om')
config.add_route('dem_devis','/dem_devis')
config.add_route('devis_en_att','/devis_en_att')
config.add_route('factures_en_att','/factures_en_att')
# parametres
config.add_route('parametres', '/parametres')
config.add_route('article_edit', '/article_edit/{ref}')
@@ -60,7 +68,6 @@ def includeme(config):
config.add_route('dashboard', '/dashboard')
config.add_route('emails_msg', '/emails_msg/{nolig}')
config.add_route('expert_edit', '/expert_edit/{code_cab}/{code_exp}')
config.add_route('infrastructure', '/infrastructure')
config.add_route('rappels_rdv', '/rappels_rdv')
config.add_route('rdf_cause_edit', '/rdf_cause_edit/{code}')
config.add_route('rdf_causes', '/rdf_causes')
@@ -75,7 +82,6 @@ def includeme(config):
config.add_route('user_edit', '/user_edit/{cd_uti}')
config.add_route('users', '/users')
config.add_route('users_ua', '/users_ua')
config.add_route('home', '/new_home')
# stats
config.add_route('stats', '/stats')
@@ -84,6 +90,7 @@ def includeme(config):
config.add_route('ca_groupes', '/ca_groupes/{societe}')
config.add_route('ca_clients', '/ca_clients/{societe}')
config.add_route('delais_pourcentage', '/delais_pourcentage/{societe}')
config.add_route('pourcentage_devis', '/pourcentage_devis/{societe}')
# utils
config.add_route('batch_nuit', '/batch_nuit/{param}')

View File

@@ -276,6 +276,10 @@ color: black;
}
}
#dessin {
width: 1140px;
height: 1140px;
}
/* ne pas affichier l'url after the link */
@media print {
@@ -283,3 +287,31 @@ color: black;
content: none !important;
}
}
.info-box {
display: block;
min-height: 70px;
background: #fff;
width: 100%;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
border-radius: 2px;
margin-bottom: 15px;
}
.info-box-icon {
border-top-left-radius: 2px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 2px;
display: block;
float: left;
height: 70px;
width: 70px;
text-align: center;
font-size: 40px;
line-height: 70px;
background: rgba(0, 0, 0, 0.2);
}
.info-box-content {
padding: 5px 5px;
margin-left: 80px;
}

View File

@@ -0,0 +1,88 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
.drawing-board, .drawing-board * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; }
.drawing-board-utils-hidden, .drawing-board-controls-hidden { display: none !important; }
.drawing-board { position: relative; display: block; }
.drawing-board-canvas-wrapper { position: relative; margin: 0; border: 1px solid #ddd; }
.drawing-board-canvas { position: absolute; top: 0; left: 0; z-index: 10; width: auto; }
.drawing-board-canvas { cursor: crosshair; z-index: 20; }
.drawing-board-cursor { position: absolute; top: 0; left: 0; pointer-events: none; border-radius: 50%; background: #ccc; background: rgba(0, 0, 0, 0.2); z-index: 30; }
.drawing-board-control > button, .drawing-board-control-colors-rainbows, .drawing-board-control-size .drawing-board-control-inner, .drawing-board-control-size-dropdown { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; overflow: hidden; border: none; background-color: #eee; padding: 2px 4px; border: 1px solid #ccc; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); height: 28px; }
.drawing-board-control > button { cursor: pointer; min-width: 28px; line-height: 14px; }
.drawing-board-control > button:hover, .drawing-board-control > button:focus { background-color: #ddd; }
.drawing-board-control > button:active, .drawing-board-control > button.active { box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); background-color: #ddd; }
.drawing-board-control > button[disabled] { color: gray; }
.drawing-board-control > button[disabled]:hover, .drawing-board-control > button[disabled]:focus, .drawing-board-control > button[disabled]:active, .drawing-board-control > button[disabled].active { background-color: #eee; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); cursor: default; }
.drawing-board-controls { margin: 0 auto; text-align: center; font-size: 0; display: table; border-spacing: 9.33333px 0; position: relative; min-height: 28px; }
.drawing-board-controls[data-align="left"] { margin: 0; left: -9.33333px; }
.drawing-board-controls[data-align="right"] { margin: 0 0 0 auto; right: -9.33333px; }
.drawing-board-canvas-wrapper + .drawing-board-controls, .drawing-board-controls + .drawing-board-canvas-wrapper { margin-top: 5px; }
.drawing-board-controls-hidden { height: 0; min-height: 0; padding: 0; margin: 0; border: 0; }
.drawing-board-control { display: table-cell; border-collapse: separate; vertical-align: middle; font-size: 16px; height: 100%; }
.drawing-board-control-inner { position: relative; height: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
.drawing-board-control > button { margin: 0; vertical-align: middle; }
.drawing-board-control-colors { font-size: 0; line-height: 0; }
.drawing-board-control-colors-current { border: 1px solid #ccc; cursor: pointer; display: inline-block; width: 26px; height: 26px; }
.drawing-board-control-colors-rainbows { display: inline-block; margin-left: 5px; position: absolute; left: 0; top: 33px; margin-left: 0; z-index: 100; width: 250px; height: auto; padding: 4px; }
.drawing-board-control-colors-rainbow { height: 18px; }
.drawing-board-control-colors-picker:first-child { margin-right: 5px; }
.drawing-board-control-colors-picker { display: inline-block; width: 18px; height: 18px; cursor: pointer; }
.drawing-board-control-colors-picker[data-color="rgba(255, 255, 255, 1)"] { width: 16px; height: 17px; border: 1px solid #ccc; border-bottom: none; }
.drawing-board-control-colors-picker:hover { width: 16px; height: 16px; border: 1px solid #555; }
.drawing-board-control-drawingmode > button { margin-right: 2px; }
.drawing-board-control-drawingmode > button:last-child { margin-right: 0; }
.drawing-board-control-drawingmode-pencil-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe9JREFUeNpiZAACVlFRBhYREQZcQPnbNwa3N28YlL5+ZfgLFfvPwGD9m4FhIgsDHuAO0gTUDNKIBvyBmqt/MTDMY8Gl0f31azD7L6oUIxCnAzWmAPHBfwwM01AMUAV6JfPQIVwOYgVqqPnFyOjz6///O38YGKpAgmAD1OXlGdTk5PD5hgeouZudj8/uy9evP/78/dsFFPsJNiAoKIiBABAHap4oLi9v8fTNm48//v7NBwbgWZgkE7rqt8DY+A8JZRBW+cfIuEDT0NDlzadP3z98/doPFDuCrB7TAGFhBqCNIGwM9OcKUzs7+xdv3355+f79VqDYAiTDwZgJh7ONgYpnOvn4GL949erT7UePdgL5JVCD4fgBLBBxaX74+PG789evnwby0/8jKXgExIeB+CG6Af///1e9Ki9vFSAkZPzoyZPPJy9evA9MB77/sWiEARZkzV+/fvXYtGnTpG3btj28EBT0BqjZ5D8OjXCwPksUhA1Wpggf/PHjx/9169Y9EBERaUlgZmaIAcrLE4rk5sIqBqDmlefnRPzfWGX5EaSZm5ubgRloADGA5QZ3RgK7gESY4PMNn9ZtObPpzZvfU4DiYkiB/RcHG+S7fyxAMH/lFU2GOZd2bLx18/cEUMoD4j9I+DcS/RtJHGTYf4AAAwAxaOMYHjxKFwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-pencil-button:before { content: ""; display: block; width: 0; height: 100%; }
.drawing-board-control-drawingmode-eraser-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkpJREFUeNp0kk+IElEcx39vFBc9+OfQRTAwzFt4CaYOKStj6MoeculStzoIQSB4kCVckmDx4iGCXWYJIqjoVOzO1l4qT1F7WfBWHvxzDPyTB3XUmXn93suRybUffHmP997n9/cRsFgwGARJkiAcDsPlwgEIeEZQAhCRAkgAlOD6SQP4rgMFDWVnYCAQgFgsBqFQCBwOByzZNQOotPHx1RNCCCipu6bfb+zSnslkeOQVILPrBkAirbws9btdTEWAzZPXpfepOzaeGMBXwe/3w3+MwTc3Dl+UeghTiskbBvR6Pbh18mZHB0jjmxvCKhIfR37s3r+Sevf8ca/T4TBF2HTSODuDxP7uNjrZFFbBk8lEzOVyspa4ykGYw2zfbTb/7ilvok1YhlVVFfP5vDydTkHXdXDdlhZOOnPY4/HA0YPtp3h6LFjh8XgsFgoFGTPgsKm1zDr8ajTQh8Fh5eGjZzjGI8yjKlgjF4tFGdd/YKYmRja24hw+zu3sYe2HiH3hYzQjl8tleTQanWtou93G6Qngdrth6+1+9h6hTULJZ/PeziJXKhV5OByeg1ut1gJOp9NZTdNOcQ419ot+ggp1qoLdBFmqVmNpm3A8Huewy+Wq1RH8QH9zmBlJJpMRdCIqiiIPBgN+2MCGsW/r8/kgGo1m0fmpzWarseayHlmNeL1eFiWC0cRqtSr3+/3FpSiKHMZtjU1glbFyfKgLTqfzEka9OJvNeDnzz1JnCaFmqOl8ZdJY1SiDOXCiXKg1NtG5DIt0y6ov3dE/AgwAENFWYYLj4mYAAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-eraser-button:before { content: ""; display: block; width: 0; height: 100%; }
.drawing-board-control-drawingmode-filler-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnNJREFUeNp0k0trE1EUx89MJpNJooYYXBgDNtCKdRPwlbqoCKUtaNVNA0Uo7UbMxoVPEARTXEi+QWfnwn6DEAlEkrSLttTGRiULEQlJ8yChmbzI++E50yTUJA78uMy953/u/557LmOz2WDEZ2m1WrckSRJSqdR2tVrdHQyYebwHtVoNuFHqTqczhQnWKaBYLDoKhcIuzgHDMKBSqeD20qd+LNdsNocSoFhRr9ctpVLJigl4xIIJQizLAmG4cAPa7bYcy9Iug5TL5UYikbD6/X7Rbre/IUcYe3WUW5ZsnQQzW9LpNOPz+UQc5aBM5mgdh7vI9FCCAesW2tnr9YqZTAby+bw8f3AQRP6853n+Ph5hemSCntjj8YjZbFYWx2IxeS2RSEMwuA87O79eqdXquVolK+GxnP0EPbHb7RZJSGABIR6PA11zJHKIR2MhHA5DIPDj7eH3j95KpfK60Wg8Yntil8slkqgnpioLghacTidoNDpEC3q9HnheCc3s1jZeLcW943pirPw/4lKpBkqlDubnl/riycnLsLy88EKj0fhzuRyZv8RFo1E6wpBYkiqy7Z54YmIcVlYeyOKC4mYwJ0nHRaQuM5vNT6hB/iceG7sIq6sPnwmC4MerDkby40AOCCoiddie1Wp92W7zQ2KTyQSLizNP8T0EsPLBbxEDnCj0GkM2qIEwyZRCobizsfH5A1ZXFhuN52F29vpz3HkL574mk8lj24Y5wsHkvjjoX0BOIWc5jruHzbK2ufmzEwpFO3jnDhQv4JoROYdoERVyGjEgZ8iBDlF3FzXo4go6utZ9lftY4N/dXisjR0i1G0ublv8KMAA0ZoUlicxrhwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-filler-button:before { content: ""; display: block; width: 0; height: 100%; }
.drawing-board-control-navigation > button { font-family: Helvetica, Arial, sans-serif; font-size: 14px; font-weight: bold; margin-right: 2px; }
.drawing-board-control-navigation > button:last-child { margin-right: 0; }
.drawing-board-control-size[data-drawing-board-type="range"] .drawing-board-control-inner { width: 75px; }
.drawing-board-control-size[data-drawing-board-type="dropdown"] .drawing-board-control-inner { overflow: visible; }
.drawing-board-control-size-range-input { position: relative; width: 100%; z-index: 100; margin: 0; padding: 0; border: 0; }
.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span, .drawing-board-control-size-dropdown span { display: block; background: #333; opacity: .8; }
.drawing-board-control-size-range-current { display: inline-block; opacity: .15; position: absolute; pointer-events: none; left: 50%; top: 50%; z-index: 50; }
.drawing-board-control-size-dropdown-current { display: block; height: 100%; width: 40px; overflow: hidden; position: relative; }
.drawing-board-control-size-dropdown-current span { position: absolute; left: 50%; top: 50%; }
.drawing-board-control-size-dropdown { position: absolute; left: -6px; top: 33px; height: auto; list-style-type: none; margin: 0; padding: 0; z-index: 100; }
.drawing-board-control-size-dropdown li { display: block; padding: 4px; margin: 3px 0; min-height: 16px; }
.drawing-board-control-size-dropdown li:hover { background: #ccc; }
.drawing-board-control-size-dropdown span { margin: 0 auto; }
.drawing-board-control-download-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoBJREFUeNqMkr9PU1EUx7/vR1tQ3yu10hAmTawtBSYSy2YccFOcnDQm/gewOLnj5GYMg4sdXFxkMca4OBAwdUBe/ZkIGFp+9tHXvh/3/fTcAm01aLzJybnn3nM+95tzrnDl6Tb+sibuTmWUWj3C6/Juk+LySUmyvt0FCKKA02ryOCy6LBiu15ngMbZ5DDCNBqIw6gKM+n4nECUJru3glKry8CpjQaHVYmC2rVH82DIMMMdGGARdwJ+SPNdFS9chx+MXDNMp/NzagWNatk/nQU/hiYAoih6FYTBCBs9zUXMCbAhx2OYOv351lPOJ3EwH4LteL6Dcp/Rfu3FrstDyIizt+agpaYxNDU0M9gl4v7Ck+TYrCYLQqZHUyTtdQBiutPSGUflczSXHs5lVKwZdSOBMvwztxVvN0RtzsiyXBFHsAvL5PBSnCpXV2getILFiE2SjspYbuZzPiDSZ2vOXmlvX5yQqTmMfg9ZXqtls1wnT09OHEyAq0aFLg/gSXsSWq9wWk+p9PrCoYTwcijdLOfE7UsEufN9HGIYnT4EnTGIXe1KqtNNIvuNnGamxfi7SgQD/nIJCTbzOPQ/SQh1pud7T4M6W/8qFIw/5WAr5m7Ozsw9UVc069Fls2yJzSC5/lnc9RhaHZVnfSqUnEgXP2oBqtYqBgYG2+mKxmOVADnAcB4yxHgD1RzehKKns/LyV4gUHBweQy+UyRkdH6UKJ6fQDFxcXoWkaXJeRuTgUGCdLQJ9bx72lGZimGWs2m+083oN+2iiFQiGxvLy8RrDzudyltgrG3N8U2G8CrPz4sGYYRqJSqWR4H/jNWbJhUjAWi8XG8R/L87yPpGCVttVfAgwAVpZR+8tZC08AAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-download-button:before { content: ""; display: block; width: 0; height: 100%; }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
.drawing-board, .drawing-board * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; }
.drawing-board-utils-hidden { display: none !important; }
.drawing-board { position: relative; display: block; }
.drawing-board-canvas-wrapper { position: relative; margin: 0; border: 1px solid #ddd; }
.drawing-board-canvas { position: absolute; top: 0; left: 0; z-index: 10; width: auto; }
.drawing-board-canvas { cursor: crosshair; z-index: 20; }
.drawing-board-cursor { position: absolute; top: 0; left: 0; pointer-events: none; border-radius: 50%; background: #ccc; background: rgba(0, 0, 0, 0.2); z-index: 30; }

View File

@@ -0,0 +1,971 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
(function() {
'use strict';
/**
* SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything.
*
* options are: {
* * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback
* * `maxLength` : the maximum number of items in history
* * `opUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear`
* }
*
*/
var SimpleUndo = function(options) {
var settings = options ? options : {};
var defaultOptions = {
provider: function() {
throw new Error("No provider!");
},
maxLength: 30,
onUpdate: function() {}
};
this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider;
this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength;
this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate;
this.initialItem = null;
this.clear();
};
function truncate (stack, limit) {
while (stack.length > limit) {
stack.shift();
}
}
SimpleUndo.prototype.initialize = function(initialItem) {
this.stack[0] = initialItem;
this.initialItem = initialItem;
};
SimpleUndo.prototype.clear = function() {
this.stack = [this.initialItem];
this.position = 0;
this.onUpdate();
};
SimpleUndo.prototype.save = function() {
this.provider(function(current) {
truncate(this.stack, this.maxLength);
this.position = Math.min(this.position,this.stack.length - 1);
this.stack = this.stack.slice(0, this.position + 1);
this.stack.push(current);
this.position++;
this.onUpdate();
}.bind(this));
};
SimpleUndo.prototype.undo = function(callback) {
if (this.canUndo()) {
var item = this.stack[--this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
SimpleUndo.prototype.redo = function(callback) {
if (this.canRedo()) {
var item = this.stack[++this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
SimpleUndo.prototype.canUndo = function() {
return this.position > 0;
};
SimpleUndo.prototype.canRedo = function() {
return this.position < this.count();
};
SimpleUndo.prototype.count = function() {
return this.stack.length - 1; // -1 because of initial item
};
//exports
// node module
if (typeof module != 'undefined') {
module.exports = SimpleUndo;
}
// browser global
if (typeof window != 'undefined') {
window.SimpleUndo = SimpleUndo;
}
})();
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
DrawingBoard.Utils = {};
/*!
* Tim (lite)
* github.com/premasagar/tim
*//*
A tiny, secure JavaScript micro-templating script.
*/
DrawingBoard.Utils.tpl = (function(){
"use strict";
var start = "{{",
end = "}}",
path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name
pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"),
undef;
return function(template, data){
// Merge data into the template string
return template.replace(pattern, function(tag, token){
var path = token.split("."),
len = path.length,
lookup = data,
i = 0;
for (; i < len; i++){
lookup = lookup[path[i]];
// Property not found
if (lookup === undef){
throw "tim: '" + path[i] + "' not found in " + tag;
}
// Return the required value
if (i === len - 1){
return lookup;
}
}
});
};
}());
/**
* https://github.com/jeromeetienne/microevent.js
* MicroEvent - to make any js object an event emitter (server or browser)
*
* - pure javascript - server compatible, browser compatible
* - dont rely on the browser doms
* - super simple - you get it immediatly, no mistery, no magic involved
*
* - create a MicroEventDebug with goodies to debug
* - make it safer to use
*/
DrawingBoard.Utils.MicroEvent = function(){};
DrawingBoard.Utils.MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
//I know.
DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) {
withPadding = !!withPadding || true;
withMargin = !!withMargin || false;
var width = 0,
props;
if (direction == "width") {
props = ['border-left-width', 'border-right-width'];
if (withPadding) props.push('padding-left', 'padding-right');
if (withMargin) props.push('margin-left', 'margin-right');
} else {
props = ['border-top-width', 'border-bottom-width'];
if (withPadding) props.push('padding-top', 'padding-bottom');
if (withMargin) props.push('margin-top', 'margin-bottom');
}
for (var i = props.length - 1; i >= 0; i--)
width += parseInt($el.css(props[i]).replace('px', ''), 10);
return width;
};
DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width');
};
DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height');
};
DrawingBoard.Utils.isColor = function(string) {
if (!string || !string.length) return false;
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1;
};
/**
* Packs an RGB color into a single integer.
*/
DrawingBoard.Utils.RGBToInt = function(r, g, b) {
var c = 0;
c |= (r & 255) << 16;
c |= (g & 255) << 8;
c |= (b & 255);
return c;
};
/**
* Returns informations on the pixel located at (x,y).
*/
DrawingBoard.Utils.pixelAt = function(image, x, y) {
var i = (y * image.width + x) * 4;
var c = DrawingBoard.Utils.RGBToInt(
image.data[i],
image.data[i + 1],
image.data[i + 2]
);
return [
i, // INDEX
x, // X
y, // Y
c // COLOR
];
};
/**
* Compares two colors with the given tolerance (between 0 and 255).
*/
DrawingBoard.Utils.compareColors = function(a, b, tolerance) {
if (tolerance === 0) {
return (a === b);
}
var ra = (a >> 16) & 255, rb = (b >> 16) & 255,
ga = (a >> 8) & 255, gb = (b >> 8) & 255,
ba = a & 255, bb = b & 255;
return (Math.abs(ra - rb) <= tolerance)
&& (Math.abs(ga - gb) <= tolerance)
&& (Math.abs(ba - bb) <= tolerance);
};
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
}());
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
/**
* pass the id of the html element to put the drawing board into
* and some options : {
* controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default
* instead of simple strings, you can pass an object to define a control opts
* ie ['Color', { Navigation: { reset: false }}]
* controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center"
* background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default
* color: pencil color ("#000000" by default)
* size: pencil size (3 by default)
* webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back
* droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it,
* errorMessage: html string to put in the board's element on browsers that don't support canvas.
* stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default
* }
*/
DrawingBoard.Board = function(id, opts) {
this.opts = this.mergeOptions(opts);
this.ev = new DrawingBoard.Utils.MicroEvent();
this.id = id;
this.$el = $(document.getElementById(id));
if (!this.$el.length)
return false;
var tpl = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';
if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
else tpl = '<div class="drawing-board-controls"></div>' + tpl;
this.$el.addClass('drawing-board').append(tpl);
this.dom = {
$canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
$canvas: this.$el.find('.drawing-board-canvas'),
$cursor: this.$el.find('.drawing-board-cursor'),
$controls: this.$el.find('.drawing-board-controls')
};
$.each(['left', 'right', 'center'], $.proxy(function(n, val) {
if (this.opts.controlsPosition.indexOf(val) > -1) {
this.dom.$controls.attr('data-align', val);
return false;
}
}, this));
this.canvas = this.dom.$canvas.get(0);
this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
this.color = this.opts.color;
if (!this.ctx) {
if (this.opts.errorMessage)
this.$el.html(this.opts.errorMessage);
return false;
}
this.storage = this._getStorage();
this.initHistory();
//init default board values before controls are added (mostly pencil color and size)
this.reset({ webStorage: false, history: false, background: false });
//init controls (they will need the default board values to work like pencil color and size)
this.initControls();
//set board's size after the controls div is added
this.resize();
//reset the board to take all resized space
this.reset({ webStorage: false, history: false, background: true });
this.restoreWebStorage();
this.initDropEvents();
this.initDrawEvents();
};
DrawingBoard.Board.defaultOpts = {
controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
controlsPosition: "top left",
color: "#000000",
size: 1,
background: "#fff",
eraserColor: "background",
fillTolerance: 100,
fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default
webStorage: 'session',
droppable: false,
enlargeYourContainer: false,
errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>",
stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true
};
DrawingBoard.Board.prototype = {
mergeOptions: function(opts) {
opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts);
if (!opts.background && opts.eraserColor === "background")
opts.eraserColor = "transparent";
return opts;
},
/**
* Canvas reset/resize methods: put back the canvas to its default values
*
* depending on options, can set color, size, background back to default values
* and store the reseted canvas in webstorage and history queue
*
* resize values depend on the `enlargeYourContainer` option
*/
reset: function(opts) {
opts = $.extend({
color: this.opts.color,
size: this.opts.size,
webStorage: true,
history: true,
background: false
}, opts);
this.setMode('pencil');
if (opts.background) {
this.resetBackground(this.opts.background, $.proxy(function() {
if (opts.history) this.saveHistory();
}, this));
}
if (opts.color) this.setColor(opts.color);
if (opts.size) this.ctx.lineWidth = opts.size;
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
// this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
if (opts.webStorage) this.saveWebStorage();
// if opts.background we already dealt with the history
if (opts.history && !opts.background) this.saveHistory();
this.blankCanvas = this.getImg();
this.ev.trigger('board:reset', opts);
},
resetBackground: function(background, callback) {
background = background || this.opts.background;
var bgIsColor = DrawingBoard.Utils.isColor(background);
var prevMode = this.getMode();
this.setMode('pencil');
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
if (bgIsColor) {
this.ctx.fillStyle = background;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.history.initialize(this.getImg());
if (callback) callback();
} else if (background)
this.setImg(background, {
callback: $.proxy(function() {
this.history.initialize(this.getImg());
if (callback) callback();
}, this)
});
this.setMode(prevMode);
},
resize: function() {
this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length));
var canvasWidth, canvasHeight;
var widths = [
this.$el.width(),
DrawingBoard.Utils.boxBorderWidth(this.$el),
DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true)
];
var heights = [
this.$el.height(),
DrawingBoard.Utils.boxBorderHeight(this.$el),
this.dom.$controls.height(),
DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true),
DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true)
];
var that = this;
var sum = function(values, multiplier) { //make the sum of all array values
multiplier = multiplier || 1;
var res = values[0];
for (var i = 1; i < values.length; i++) {
res = res + (values[i]*multiplier);
}
return res;
};
var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one
if (this.opts.enlargeYourContainer) {
canvasWidth = this.$el.width();
canvasHeight = this.$el.height();
this.$el.width( sum(widths) );
this.$el.height( sum(heights) );
} else {
canvasWidth = sub(widths);
canvasHeight = sub(heights);
}
this.dom.$canvasWrapper.css('width', canvasWidth + 'px');
this.dom.$canvasWrapper.css('height', canvasHeight + 'px');
this.dom.$canvas.css('width', canvasWidth + 'px');
this.dom.$canvas.css('height', canvasHeight + 'px');
this.canvas.width = canvasWidth;
this.canvas.height = canvasHeight;
},
/**
* Controls:
* the drawing board can has various UI elements to control it.
* one control is represented by a class in the namespace DrawingBoard.Control
* it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization.
*
*/
initControls: function() {
this.controls = [];
if (!this.opts.controls.length || !DrawingBoard.Control) return false;
for (var i = 0; i < this.opts.controls.length; i++) {
var c = null;
if (typeof this.opts.controls[i] == "string")
c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this);
else if (typeof this.opts.controls[i] == "object") {
for (var controlName in this.opts.controls[i]) break;
c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]);
}
if (c) {
this.addControl(c);
}
}
},
//add a new control or an existing one at the position you want in the UI
//to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }"
//the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at
addControl: function(control, optsOrPos, pos) {
if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control))
return false;
var opts = typeof optsOrPos == "object" ? optsOrPos : {};
pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null);
if (typeof control == "string")
control = new window['DrawingBoard']['Control'][control](this, opts);
if (pos)
this.dom.$controls.children().eq(pos).before(control.$el);
else
this.dom.$controls.append(control.$el);
if (!this.controls)
this.controls = [];
this.controls.push(control);
this.dom.$controls.removeClass('drawing-board-controls-hidden');
},
/**
* History methods: undo and redo drawed lines
*/
initHistory: function() {
this.history = new SimpleUndo({
maxLength: 30,
provider: $.proxy(function(done) {
done(this.getImg());
}, this),
onUpdate: $.proxy(function() {
this.ev.trigger('historyNavigation');
}, this)
});
},
saveHistory: function() {
this.history.save();
},
restoreHistory: function(image) {
this.setImg(image, {
callback: $.proxy(function() {
this.saveWebStorage();
}, this)
});
},
goBackInHistory: function() {
this.history.undo($.proxy(this.restoreHistory, this));
},
goForthInHistory: function() {
this.history.redo($.proxy(this.restoreHistory, this));
},
/**
* Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download
*/
setImg: function(src, opts) {
opts = $.extend({
stretch: this.opts.stretchImg,
callback: null
}, opts);
var ctx = this.ctx;
var img = new Image();
var oldGCO = ctx.globalCompositeOperation;
img.onload = function() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (opts.stretch) {
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
} else {
ctx.drawImage(img, 0, 0);
}
ctx.globalCompositeOperation = oldGCO;
if (opts.callback) {
opts.callback();
}
};
img.src = src;
},
getImg: function() {
return this.canvas.toDataURL("image/png");
},
downloadImg: function() {
var img = this.getImg();
img = img.replace("image/png", "image/octet-stream");
window.location.href = img;
},
/**
* WebStorage handling : save and restore to local or session storage
*/
saveWebStorage: function() {
if (window[this.storage]) {
window[this.storage].setItem('drawing-board-' + this.id, this.getImg());
this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg());
}
},
restoreWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
this.setImg(window[this.storage].getItem('drawing-board-' + this.id));
this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id));
}
},
clearWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
window[this.storage].removeItem('drawing-board-' + this.id);
this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1));
}
},
_getStorage: function() {
if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false;
return this.opts.webStorage + 'Storage';
},
/**
* Drop an image on the canvas to draw on it
*/
initDropEvents: function() {
if (!this.opts.droppable)
return false;
this.dom.$canvas.on('dragover dragenter drop', function(e) {
e.stopPropagation();
e.preventDefault();
});
this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this));
},
_onCanvasDrop: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var files = e.dataTransfer.files;
if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader)
return false;
var fr = new FileReader();
fr.readAsDataURL(files[0]);
fr.onload = $.proxy(function(ev) {
this.setImg(ev.target.result, {
callback: $.proxy(function() {
this.saveHistory();
}, this)
});
this.ev.trigger('board:imageDropped', ev.target.result);
this.ev.trigger('board:userAction');
}, this);
},
/**
* set and get current drawing mode
*
* possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can)
*/
setMode: function(newMode, silent) {
silent = silent || false;
newMode = newMode || 'pencil';
this.ev.unbind('board:startDrawing', $.proxy(this.fill, this));
if (this.opts.eraserColor === "transparent")
this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over";
else {
if (newMode === "eraser") {
if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background))
this.ctx.strokeStyle = this.opts.background;
else if (DrawingBoard.Utils.isColor(this.opts.eraserColor))
this.ctx.strokeStyle = this.opts.eraserColor;
} else if (!this.mode || this.mode === "eraser") {
this.ctx.strokeStyle = this.color;
}
if (newMode === "filler")
this.ev.bind('board:startDrawing', $.proxy(this.fill, this));
}
this.mode = newMode;
if (!silent)
this.ev.trigger('board:mode', this.mode);
},
getMode: function() {
return this.mode || "pencil";
},
setColor: function(color) {
var that = this;
color = color || this.color;
if (!DrawingBoard.Utils.isColor(color))
return false;
this.color = color;
if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") {
var setStrokeStyle = function(mode) {
if (mode !== "eraser")
that.strokeStyle = that.color;
that.ev.unbind('board:mode', setStrokeStyle);
};
this.ev.bind('board:mode', setStrokeStyle);
} else
this.ctx.strokeStyle = this.color;
},
/**
* Fills an area with the current stroke color.
*/
fill: function(e) {
if (this.getImg() === this.blankCanvas) {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.ctx.fillStyle = this.color;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
return;
}
var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
// constants identifying pixels components
var INDEX = 0, X = 1, Y = 2, COLOR = 3;
// target color components
var stroke = this.ctx.strokeStyle;
var r = parseInt(stroke.substr(1, 2), 16);
var g = parseInt(stroke.substr(3, 2), 16);
var b = parseInt(stroke.substr(5, 2), 16);
// starting point
var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10));
var startColor = start[COLOR];
var tolerance = this.opts.fillTolerance;
var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38
// no need to continue if starting and target colors are the same
if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance))
return;
// pixels to evaluate
var queue = [start];
// loop vars
var pixel, x, y;
var maxX = img.width - 1;
var maxY = img.height - 1;
function updatePixelColor(pixel) {
img.data[pixel[INDEX]] = r;
img.data[pixel[INDEX] + 1] = g;
img.data[pixel[INDEX] + 2] = b;
}
while ((pixel = queue.pop())) {
if (useHack)
updatePixelColor(pixel);
if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) {
if (!useHack)
updatePixelColor(pixel);
if (pixel[X] > 0) // west
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y]));
if (pixel[X] < maxX) // east
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y]));
if (pixel[Y] > 0) // north
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1));
if (pixel[Y] < maxY) // south
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1));
}
}
this.ctx.putImageData(img, 0, 0);
},
/**
* Drawing handling, with mouse or touch
*/
initDrawEvents: function() {
this.isDrawing = false;
this.isMouseHovering = false;
this.coords = {};
this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 };
this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) {
this._onInputStart(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) {
this._onInputMove(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mousemove', $.proxy(function(e) {
}, this));
this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) {
this._onInputStop(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mouseover', $.proxy(function(e) {
this._onMouseOver(e, this._getInputCoords(e) );
}, this));
this.dom.$canvas.on('mouseout', $.proxy(function(e) {
this._onMouseOut(e, this._getInputCoords(e) );
}, this));
$('body').on('mouseup touchend', $.proxy(function(e) {
this.isDrawing = false;
}, this));
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) );
},
draw: function() {
//if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil
//todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported
//we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad.
if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) {
this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' });
var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) });
this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform });
this.dom.$cursor.removeClass('drawing-board-utils-hidden');
} else {
this.dom.$cursor.addClass('drawing-board-utils-hidden');
}
if (this.isDrawing) {
var currentMid = this._getMidInputCoords(this.coords.current);
this.ctx.beginPath();
this.ctx.moveTo(currentMid.x, currentMid.y);
this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y);
this.ctx.stroke();
this.coords.old = this.coords.current;
this.coords.oldMid = currentMid;
}
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) );
},
_onInputStart: function(e, coords) {
this.coords.current = this.coords.old = coords;
this.coords.oldMid = this._getMidInputCoords(coords);
this.isDrawing = true;
if (!window.requestAnimationFrame) this.draw();
this.ev.trigger('board:startDrawing', {e: e, coords: coords});
e.stopPropagation();
e.preventDefault();
},
_onInputMove: function(e, coords) {
this.coords.current = coords;
this.ev.trigger('board:drawing', {e: e, coords: coords});
if (!window.requestAnimationFrame) this.draw();
e.stopPropagation();
e.preventDefault();
},
_onInputStop: function(e, coords) {
if (this.isDrawing && (!e.touches || e.touches.length === 0)) {
this.isDrawing = false;
this.saveWebStorage();
this.saveHistory();
this.ev.trigger('board:stopDrawing', {e: e, coords: coords});
this.ev.trigger('board:userAction');
e.stopPropagation();
e.preventDefault();
}
},
_onMouseOver: function(e, coords) {
this.isMouseHovering = true;
this.coords.old = this._getInputCoords(e);
this.coords.oldMid = this._getMidInputCoords(this.coords.old);
this.ev.trigger('board:mouseOver', {e: e, coords: coords});
},
_onMouseOut: function(e, coords) {
this.isMouseHovering = false;
this.ev.trigger('board:mouseOut', {e: e, coords: coords});
},
_getInputCoords: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var
rect = this.canvas.getBoundingClientRect(),
width = this.dom.$canvas.width(),
height = this.dom.$canvas.height()
;
var x, y;
if (e.touches && e.touches.length == 1) {
x = e.touches[0].pageX;
y = e.touches[0].pageY;
} else {
x = e.pageX;
y = e.pageY;
}
x = x - this.dom.$canvas.offset().left;
y = y - this.dom.$canvas.offset().top;
x *= (width / rect.width);
y *= (height / rect.height);
return {
x: x,
y: y
};
},
_getMidInputCoords: function(coords) {
return {
x: this.coords.old.x + coords.x>>1,
y: this.coords.old.y + coords.y>>1
};
}
};

View File

@@ -0,0 +1,5 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
.drawing-board,.drawing-board *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.drawing-board-utils-hidden{display:none!important}.drawing-board{position:relative;display:block}.drawing-board-canvas-wrapper{position:relative;margin:0;border:1px solid #ddd}.drawing-board-canvas{position:absolute;top:0;left:0;width:auto;cursor:crosshair;z-index:20}.drawing-board-cursor{position:absolute;top:0;left:0;pointer-events:none;border-radius:50%;background:#ccc;background:rgba(0,0,0,.2);z-index:30}

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@
<form method="POST" id="frm" class="form-horizontal">
<div class="form-group">
<div class="col-xs-4">
<select class="form-control" id="agenda" name="agenda" onChange="$('#frm').submit()" tal:condition="access > 0">
<select class="form-control" id="agenda" name="agenda" onChange="$('#frm').submit()" tal:condition="access > 1">
<div tal:repeat="item agendas">
<option value="${item.CD_UTI}" tal:attributes="selected agenda==item.CD_UTI and 'selected' or None">${item.NOM}</option>
</div>

View File

@@ -61,7 +61,7 @@
<div class="col-sm-offset-3 col-sm-9">
<a class="btn btn-default" href="/agenda/${rdv.rdv_debut.strftime('%Y-%m-%d')}">
<span class="glyphicon glyphicon-arrow-left"></span> Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<button class="btn btn-primary" type="submit" name="form.submitted" tal:condition="access > 1">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
<button class="btn btn-danger" type="submit" name="form.deleted"
tal:condition="nolig != '0' and logged_in.upper()==rdv.USERMAJ.upper()">

View File

@@ -1,53 +1,146 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="container-fluid text-center">
<div class="container-fluid">
<br />
<!-- row 1 : MENU GENERAL -->
<div class="row">
<div class="col-sm-3">
<div class="col-xs-4">
<a href="${request.application_url}/agenda/today">
<span class="glyphicon glyphicon-calendar logo-small"></span><br />
<h4>MON AGENDA</h4></a>
<div class="info-box bg-prod">
<span class="info-box-icon"><i class="glyphicon glyphicon-calendar"></i></span>
<div class="info-box-content">
<span class="info-box-number">AGENDA</span>
<span class="info-box-text">RDV JOUR&nbsp;:&nbsp;<span class="badge bg-red">${nb_rdv}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<div class="col-xs-4" tal:condition="access != 1">
<a href="${request.application_url}/rdf_list">
<span class="glyphicon glyphicon-tint logo-success"></span>
<h4>RAPPORTS RDF</h4></a>
<div class="info-box bg-green">
<span class="info-box-icon"><i class="glyphicon glyphicon-tint"></i></span>
<div class="info-box-content">
<span class="info-box-number">RDF</span>
<span class="info-box-text">À VALIDER&nbsp;:&nbsp;<span class="badge bg-red">${nb_rdf}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3" tal:condition="logged_in=='CAO'">
<a href="${request.application_url}/devis_list">
<span class="glyphicon glyphicon-text-height logo-small"></span>
<h4>E-DEVIS</h4></a>
<div class="col-xs-4">
<a href="${request.application_url}/dossier_lookup" tal:condition="access > 0">
<div class="info-box bg-prod">
<span class="info-box-icon"><i class="glyphicon glyphicon-search"></i></span>
<div class="info-box-content">
<span class="info-box-number">RECHERCHE DOSSIER</span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/dossier_lookup">
<span class="glyphicon glyphicon-search logo-small"></span>
<h4>RECH. DOSSIER</h4></a>
</div>
<br>
<div class="row" tal:condition="access >= 5">
<div class="col-xs-4">
<a href="${request.application_url}/planning/today">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-calendar"></i></span>
<div class="info-box-content">
<span class="info-box-number">PLANNING</span>
</div>
</div>
</a>
</div>
<div class="col-xs-4">
<a href="${request.application_url}/demandes" tal:condition="access > 1">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-envelope"></i></span>
<div class="info-box-content">
<span class="info-box-number">EMAILS</span>
<span class="info-box-text">À IMPORTER DANS DOSSIERS</span>
</div>
</div>
</a>
</div>
<div class="col-xs-4">
<a href="${request.application_url}/upload_om" tal:condition="access > 1">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-download-alt"></i></span>
<div class="info-box-content">
<span class="info-box-number">PDF <span class="glyphicon glyphicon-arrow-right"></span> DOSSIERS</span>
</div>
</div>
</a>
</div>
<div class="col-xs-4">
</div>
</div>
<br />
<!-- row 2 : MENU GESTIONNAIRE -->
<div class="row" tal:condition="access >= 5">
<div class="col-sm-3">
<a href="${request.application_url}/planning/today">
<span class="glyphicon glyphicon-calendar logo-small"></span><br />
<h4>PLANNING</h4></a>
<div class="row">
<div class="col-xs-4">
<a href="${request.application_url}/dem_devis" tal:condition="access > 1">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-folder-open"></i></span>
<div class="info-box-content">
<span class="info-box-number">DEM. DEVIS</span>
<span class="info-box-text">À TRAITER </span>
<span class="info-box-number" tal:condition="len(nb_dd_restants) > 0"><span class="badge bg-PE">${nb_dd_restants.nb_PE}</span>
<span class="badge bg-ME">${nb_dd_restants.nb_ME}</span>
<span class="badge bg-PL">${nb_dd_restants.nb_PL}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/demandes" tal:condition="access > 0">
<span class="glyphicon glyphicon-download-alt logo-warning"></span>
<h4>EMAILS <span class="glyphicon glyphicon-arrow-right"></span> DOSSIERS</h4></a>
<div class="col-xs-4">
<a href="${request.application_url}/dern_suivis" tal:condition="access > 1">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-file"></i></span>
<div class="info-box-content">
<span class="info-box-number">CHANTIERS</span>
<span class="info-box-text">DERNIERS SUIVIS</span>
<span class="info-box-number" tal:condition="len(nb_de_restants) > 0"><span class="badge bg-PE">${nb_de_restants.nb_PE}</span>
<span class="badge bg-ME">${nb_de_restants.nb_ME}</span>
<span class="badge bg-PL">${nb_de_restants.nb_PL}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/upload_om" tal:condition="access > 0">
<span class="glyphicon glyphicon-download-alt logo-warning"></span>
<h4>PDF <span class="glyphicon glyphicon-arrow-right"></span> DOSSIERS</h4></a>
<div class="col-xs-4">
<a href="${request.application_url}/factures_en_att" tal:condition="access > 1">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-inbox"></i></span>
<div class="info-box-content">
<span class="info-box-number">FACTURES</span>
<span class="info-box-text">À RÉGLER </span>
<span class="info-box-number" tal:condition="len(nb_fa_restants) > 0"><span class="badge bg-PE">${nb_fa_restants.nb_PE}</span>
<span class="badge bg-ME">${nb_fa_restants.nb_ME}</span>
<span class="badge bg-PL">${nb_fa_restants.nb_PL}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
</div>
<br />
<div class="row">
<div class="col-xs-4">
<a href="${request.application_url}/stats" tal:condition="access > 6">
<span class="glyphicon glyphicon-stats logo-warning"></span>
<h4>STATISTIQUES</h4></a>
<div class="info-box bg-compta">
<span class="info-box-icon"><i class="glyphicon glyphicon-stats"></i></span>
<div class="info-box-content">
<span class="info-box-number">STATS</span>
<span class="info-box-text">DÉLAIS ET CA</span>
</div>
</div>
</a>
</div>
<div class="col-xs-4">
<a href="${request.application_url}/devis_list/PE/0" tal:condition="logged_in == 'CAO'">
<div class="info-box bg-prod">
<span class="info-box-icon"><i class="glyphicon glyphicon-search"></i></span>
<div class="info-box-content">
<span class="info-box-number">RECHERCHE DEVIS</span>
</div>
</div>
</a>
</div>
</div>
<br />

View File

@@ -1,155 +0,0 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="container-fluid">
<br />
<div class="row">
<div class="col-sm-3">
<a href="${request.application_url}/agenda/today">
<div class="info-box bg-prod">
<span class="info-box-icon"><i class="glyphicon glyphicon-calendar"></i></span>
<div class="info-box-content">
<span class="info-box-number">AGENDA</span>
<span class="info-box-text">Rendez-vous : </span>
<span class="info-box-number"><span class="badge bg-red">${nb_rdv.nb_rdv}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/rdf_list">
<div class="info-box bg-green">
<span class="info-box-icon"><i class="glyphicon glyphicon-tint"></i></span>
<div class="info-box-content">
<span class="info-box-number">RDF</span>
<span class="info-box-text">A traiter : </span>
<span class="info-box-number"><span class="badge bg-red">${nb_rdf.nb_rdf}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/devis_list">
<div class="info-box bg-prod">
<span class="info-box-icon"><i class="glyphicon glyphicon-text-height"></i></span>
<div class="info-box-content">
<span class="info-box-number">E-DEVIS</span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/dossier_lookup" tal:condition="access > 6">
<div class="info-box bg-prod">
<span class="info-box-icon"><i class="glyphicon glyphicon-search"></i></span>
<div class="info-box-content">
<span class="info-box-number">RECHERCHE</span>
</div>
</div>
</a>
</div>
</div>
<br>
<div class="row" tal:condition="access >= 5">
<div class="col-sm-3">
<a href="${request.application_url}/planning/today">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-calendar"></i></span>
<div class="info-box-content">
<span class="info-box-number">PLANNING</span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/demandes" tal:condition="access > 0">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-envelope"></i></span>
<div class="info-box-content">
<span class="info-box-number">EMAILS</span>
<span class="info-box-text">A traiter : </span>
<span class="info-box-number"><span class="badge bg-purple">${nb_mails}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/upload_om" tal:condition="access > 0">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-download-alt"></i></span>
<div class="info-box-content">
<span class="info-box-number">PDF <span class="glyphicon glyphicon-arrow-right"></span> DOSSIERS</span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
</div>
</div>
<br />
<div class="row">
<div class="col-sm-3">
<a href="${request.application_url}/dem_devis" tal:condition="access > 0">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-folder-open"></i></span>
<div class="info-box-content">
<span class="info-box-number">DEM. DEVIS</span>
<span class="info-box-text">A traiter : </span>
<span class="info-box-number"><span class="badge bg-PE">${nb_dd_restants.nb_PE}</span>
<span class="badge bg-ME">${nb_dd_restants.nb_ME}</span>
<span class="badge bg-PL">${nb_dd_restants.nb_PL}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/devis_en_att" tal:condition="access > 0">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-file"></i></span>
<div class="info-box-content">
<span class="info-box-number">DEVIS</span>
<span class="info-box-text">A traiter : </span>
<span class="info-box-number"><span class="badge bg-PE">${nb_de_restants.nb_PE}</span>
<span class="badge bg-ME">${nb_de_restants.nb_ME}</span>
<span class="badge bg-PL">${nb_de_restants.nb_PL}</span></span>
</div>
</div>
</a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/factures_en_att" tal:condition="access > 0">
<div class="info-box bg-gest">
<span class="info-box-icon"><i class="glyphicon glyphicon-inbox"></i></span>
<div class="info-box-content">
<span class="info-box-number">FACTURES</span>
<span class="info-box-text">A traiter : </span>
<span class="info-box-number"><span class="badge bg-PE">${nb_fa_restants.nb_PE}</span>
<span class="badge bg-ME">${nb_fa_restants.nb_ME}</span>
<span class="badge bg-PL">${nb_fa_restants.nb_PL}</span></span>
</div>
</div>
</a>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-3">
<a href="${request.application_url}/stats" tal:condition="access > 6">
<div class="info-box bg-compta">
<span class="info-box-icon"><i class="glyphicon glyphicon-stats"></i></span>
<div class="info-box-content">
<span class="info-box-number">STATS</span>
</div>
</div>
</a>
</div>
</div>
<br />
<br />
<br />
</div>
</div><!-- content -->
</metal:block>

View File

@@ -14,12 +14,9 @@
<div class="form-group">
<label class="control-label col-sm-2">Type de texte</label>
<div class="col-sm-10">
<label class="radio-inline"><input type="radio" name="ref" value="T1"
tal:attributes="checked ligne.ref=='T1'">Titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="T2"
tal:attributes="checked ligne.ref=='T2'">Sous-titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="TX"
tal:attributes="checked ligne.ref=='TX'">Texte libre</label>
<label class="radio-inline"><input type="radio" name="ref" value="T1" checked>Titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="T2">Sous-titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="TX">Texte libre</label>
</div>
</div>
<div class="form-group">
@@ -47,8 +44,11 @@
<div class="form-group">
<label class="control-label col-sm-2">Quantité</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="qte" name="qte" value="${ligne.qte}">
</div>
<div class="input-group">
<span class="input-group-addon" id="unite">.00</span>
<input class="form-control" type="text" id="qte" name="qte" value="${ligne.qte}">
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Prix HT</label>
@@ -196,11 +196,13 @@ $(document).ready(function() {
var ref = response[0]['ref'];
var libelle = response[0]['libelle'];
var prixht = response[0]['prixht'];
var unite = response[0]['unite'];
// Set value to textboxes
document.getElementById('ref').value = ref;
document.getElementById('libelle').value = libelle;
document.getElementById('prixht').value = prixht;
document.getElementById("unite").innerHTML= unite;
}
}
});

View File

@@ -27,10 +27,9 @@
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Nom ou numéro du chantier</label>
<label class="col-sm-4 control-label">Numéro du devis</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="name" value="${name}"
placeholder="Le nom ou le numéro doit avoir de 2 à 30 caractères de long" >
<input type="text" class="form-control" name="nodevis" value="${nodevis}" >
</div>
</div>
@@ -53,19 +52,20 @@
<th>Client</th>
<th>Chantier</th>
<th class="text-right">Montant</th>
<th>Sinistre</th>
<th class="text-center">Statut</th>
<th>No chantier</th>
<th class="text-center">Action</th>
</tr>
<tr tal:repeat="detail devis">
<td>
<a href="/devis_web/${societe}-DE${detail.numero}">${societe}-${detail.numero}-W</a>
</td>
<tr tal:repeat="detail dossiers">
<td>${detail.TYPE}-${detail.numero}</td>
<td>${detail.date.strftime('%d-%m-%Y')}</td>
<td>${detail.nomcli}</td>
<td>${detail.chantier}</td>
<td class="text-right">${layout.to_euro(detail.montant)}</td>
<td>${detail.nosin}</td>
<td class="text-center">${detail.status}</td>
<td>${detail.nochantier}</td>
<td class="text-center">
<a tal:condition="detail.nochantier == 0" id="modalButton" href="#confirmCreate"
data-toggle="modal" data-societe="${detail.societe}" data-nodevis="${detail.TYPE}-${detail.numero}">Joindre</a>
</td>
</tr>
</thead>
@@ -74,26 +74,53 @@
<br />
</div>
<script type="text/javascript">
$(document).ready(function() {
$('#devis-search-form').formValidation({
framework: 'bootstrap',
message: 'This value is not valid',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
});
$('form input').on('keypress', function(e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
// simuler clic bouton submit
document.getElementById("submitButton").click();
}
});
<!-- Modal : Confirmation CREATION -->
<div class="modal fade" id="confirmCreate" role="dialog" aria-labelledby="confirmCreateLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Ajouter le nochantier dans le </h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<form id="add_justif-form" class="form-horizontal" action="${url}" method="post">
<div class="form-group">
<label class="control-label col-xs-4">Document No:</label>
<div class="col-xs-8">
<input type="text" name="md_nodevis" id="md_nodevis" value="" readonly/>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-4">Chantier No :</label>
<div class="col-xs-8">
<input type="text" name="md_nochantier" id="md_nochantier" value=""/>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-4">Société:</label>
<div class="col-xs-8">
<input type="text" name="md_societe" id="md_societe" value="" readonly/>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-success" name="form.joined">Ajouter</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$('#confirmCreate').on('show.bs.modal', function(e) {
var societe = $(e.relatedTarget).data('societe');
var nodevis = $(e.relatedTarget).data('nodevis');
$(e.currentTarget).find('input[name="md_societe"]').val(societe);
$(e.currentTarget).find('input[name="md_nodevis"]').val(nodevis);
});
</script>
</div>

View File

@@ -49,7 +49,7 @@
<td style="width:18%; text-align:right">Montant HT</td>
</tr>
<tr>
<td colspan="4"><hr></td>
<td colspan="5"><hr></td>
</tr>
<!-- clignes de détail du devis -->
<div tal:replace="structure dt_html">Les lignes du devis ici</div>

View File

@@ -59,19 +59,18 @@
</table>
</div>
<div class="col-md-6">
<h4>Statut : ${entete.libelle}</h4>
<h4>
Statut : ${entete.libelle}&nbsp;&nbsp;&nbsp;
[ <a href="#" data-toggle="modal" data-target="#confirmStatut" tal:condition="access > 1">Modifier le statut</a> ]
</h4>
<div tal:condition="type_doc=='DE'">
<p>Dernière modif. le <b>${entete.DATEMAJ.strftime('%d/%m/%Y à %H:%M')}</b> par <b>${entete.USERMAJ}</b></p>
<p>
<a class="btn btn-warning" role="button" href="#"
data-toggle="modal" data-target="#confirmCloture"><span class="glyphicon glyphicon-check"></span> Modif. statut</a>
</p>
</div>
</div>
</div> <!-- row -->
<!-- ENTETE entete -->
<table class="table table-bordered table-condensed">
<table class="table table-bordered table-condensed" tal:condition="access > 1">
<tr class="well">
<th class="text-right">Total HT</th>
<th class="text-right">Total TVA</th>
@@ -98,28 +97,29 @@
<tr tal:repeat="detail details">
<td>${detail.REF}</td>
<td>${detail.LIB}</td>
<td class="text-right">${layout.to_euro(detail.QTE)}</td>
<td class="text-right">${layout.to_euroz(detail.PRIXHT)}</td>
<td class="text-right">${layout.to_euroz(detail.MTHT)}</td>
<td class="text-right">${detail.QTE}</td>
<td class="text-right"><span tal:condition="access > 1">${layout.to_euroz(detail.PRIXHT)}</span></td>
<td class="text-right"><span tal:condition="access > 1">${layout.to_euroz(detail.MTHT)}</span></td>
<td class="text-center">${detail.USERMAJ}</td>
</tr>
</table>
<!-- Modal : Confirmation CLOTURE -->
<div class="modal fade" id="confirmCloture" role="dialog" aria-labelledby="confirmClotureLabel" aria-hidden="true">
<div class="modal fade" id="confirmStatut" role="dialog" aria-labelledby="confirmStatutLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Clôturer le devis</h4>
<h4 class="modal-title">Modifier le statut du devis ou facture</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<form id="add_justif-form" class="form-horizontal" action="${url}" method="post">
<div class="form-group">
<p class="text-center"><b>Voulez-vous changer le status du devis ?</b></p>
<label class="control-label col-xs-4" for="status">Devis / Facture</label>
<p class="form-control-static col-xs-8"><b>${entete.societe}-${entete.NO_ID} - ${entete.C_QUALITE} ${entete.C_NOM}</b></p>
</div>
<div class="row">
<div class="form-group">
<label class="control-label col-xs-4" for="status">Sélectionner le statut :</label>
<div class="col-xs-8">
<select class="form-control" id="status" name="status">
@@ -129,17 +129,15 @@
</select>
</div>
</div>
<br>
<div class="row">
<div class="form-group">
<label class="control-label col-xs-4" for="motif">Motif :</label>
<div class="col-xs-8">
<input class="form-control" type="text" id="motif" name="motif" value=""
placeholder="65 caractères maximum"
data-fv-notempty="true"
data-fv-notempty-message="Veuillez remplir un motif"
data-fv-stringlength="true"
data-fv-stringlength-max="65"
data-fv-stringlength-message="65 caractères maximum"/>
<select class="form-control" id="motif" name="motif">
<option selected> </option>
<div tal:repeat="item motifs">
<option>${item.code} | ${item.libelle}</option>
</div>
</select>
</div>
</div>
<br>

View File

@@ -0,0 +1,108 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="row">
<form id="site-search-form" class="form-horizontal" role="form" action="${url}" method="post"
data-fv-framework="bootstrap"
data-fv-icon-valid="glyphicon glyphicon-ok"
data-fv-icon-invalid="glyphicon glyphicon-remove"
data-fv-icon-validating="glyphicon glyphicon-refresh">
<div class="form-group">
<label class="col-sm-4 control-label">Société</label>
<div class="col-xs-8">
<label class="radio-inline"><input type="radio" name="societe" value="PE"
tal:attributes="checked societe=='PE'">PE</label>
<label class="radio-inline"><input type="radio" name="societe" value="ME"
tal:attributes="checked societe=='ME'">ME</label>
<label class="radio-inline"><input type="radio" name="societe" value="PL"
tal:attributes="checked societe=='PL'">PL</label>
<label class="radio-inline"><input type="radio" name="societe" value="PO"
tal:attributes="checked societe=='PO'">PO</label>
<label class="radio-inline"><input type="radio" name="societe" value="CD"
tal:attributes="checked societe=='CD'">CD</label>
<button id="submitButton" class="btn btn-primary" type="submit" name="form.submitted">
<i class="glyphicon glyphicon-search"></i>&nbsp;Rechercher</button>
</div>
</div>
</form>
</div><!-- row -->
<br />
<div class="row">
<table id="dossiers_list" class="table table-bordered">
<thead>
<tr>
<th>Numéro</th>
<th>Date</th>
<th>Client</th>
<th>Chantier</th>
<th class="text-right">Montant</th>
<th>Sinistre</th>
<th class="text-center">Statut</th>
<th>Uti.</th>
</tr>
</thead>
</table>
</div>
<br />
<br />
<script type="text/javascript">
var dataSet = ${dt_data};
var goto_url = '${goto_url}';
var order_option = '${order_option}'
$(document).ready(function() {
$.fn.dataTable.moment('DD-MM-YYYY');
$('#dossiers_list').DataTable({
data: dataSet,
pageLength: 100,
bLengthChange: false,
language: {
url: 'https://cdn.datatables.net/plug-ins/1.10.16/i18n/French.json'
},
order: [[0, order_option]],
columnDefs: [
{ className: "text-right", "targets": [4] },
{ "targets": 0,
"render": function (data, type, full, meta) {
// ajouter un link vers le formulaire
return '<a href="' + goto_url + data + '">' + data + '</a>';
},
},
],
createdRow: function( row, data, dataIndex ){
if ( data[6] == "Humidité" ) {
$('td', row).eq(6).css('background-color', 'Crimson').css('color', 'white');
}
if ( data[6] == "Devis" || data[6] == "Commandé") {
$('td', row).eq(6).css('background-color', 'Orange');
}
if ( data[6] == "Facturé") {
$('td', row).eq(6).css('background-color', 'LightBlue');
}
if ( data[6] == "Réglée" || data[6] == "Régl part.") {
$('td', row).eq(6).css('background-color', 'LightGreen');
}
},
});
});
$('form input').on('keypress', function(e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
// simuler clic bouton submit
document.getElementById("submitButton").click();
}
});
</script>
</div><!-- content -->
</metal:block>

View File

@@ -1,27 +1,105 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="row">
<form id="site-search-form" class="form-horizontal" role="form" action="${url}" method="post"
data-fv-framework="bootstrap"
data-fv-icon-valid="glyphicon glyphicon-ok"
data-fv-icon-invalid="glyphicon glyphicon-remove"
data-fv-icon-validating="glyphicon glyphicon-refresh">
<div class="form-group">
<label class="col-sm-4 control-label">Société</label>
<div class="col-xs-8">
<label class="radio-inline"><input type="radio" name="societe" value="PE"
tal:attributes="checked societe=='PE'">PE</label>
<label class="radio-inline"><input type="radio" name="societe" value="ME"
tal:attributes="checked societe=='ME'">ME</label>
<label class="radio-inline"><input type="radio" name="societe" value="PL"
tal:attributes="checked societe=='PL'">PL</label>
<label class="radio-inline"><input type="radio" name="societe" value="PO"
tal:attributes="checked societe=='PO'">PO</label>
<label class="radio-inline"><input type="radio" name="societe" value="CD"
tal:attributes="checked societe=='CD'">CD</label>
<button id="submitButton" class="btn btn-primary" type="submit" name="form.submitted">
<i class="glyphicon glyphicon-search"></i>&nbsp;Rechercher</button>
</div>
</div>
</form>
</div><!-- row -->
<br />
<table class="table table-condensed">
<tr tal:repeat="item dossiers_traites">
<td>${item.DATEMAJ.strftime('%d %b')}</td>
<td><a href="${request.application_url}/dossier_view/${item.societe}-${item.NO_ID}">${item.societe}-${item.NO_ID}</td>
<td>${item.NOMCLI}</td>
<td>${item.C_NOM}</td>
<td>${item.USERMAJ}</td>
<td><span class="badge bg-${item.STATUS}">${item.libelle}</span></td>
</tr>
</table>
<div class="row">
<table id="dossiers_list" class="table table-bordered">
<thead>
<tr>
<th>Numéro</th>
<th>Date</th>
<th>Client</th>
<th>Chantier</th>
<th class="text-right">Montant</th>
<th>Sinistre</th>
<th class="text-center">Statut</th>
<th>Uti.</th>
</tr>
</thead>
</table>
</div>
<br />
<br />
<script type="text/javascript">
$('#generateButton').on('click', function(){
$('i.gly-spin').removeClass('gly-spin');
$('i').addClass('gly-spin');
});
var dataSet = ${dt_data};
var goto_url = '${goto_url}';
var order_option = '${order_option}'
$(document).ready(function() {
$.fn.dataTable.moment('DD-MM-YYYY');
$('#dossiers_list').DataTable({
data: dataSet,
pageLength: 100,
bLengthChange: false,
language: {
url: 'https://cdn.datatables.net/plug-ins/1.10.16/i18n/French.json'
},
order: [[0, order_option]],
columnDefs: [
{ className: "text-right", "targets": [4] },
{ "targets": 0,
"render": function (data, type, full, meta) {
// ajouter un link vers le formulaire
return '<a href="' + goto_url + data + '">' + data + '</a>';
},
},
],
createdRow: function( row, data, dataIndex ){
if ( data[6] == "Humidité" ) {
$('td', row).eq(6).css('background-color', 'Crimson').css('color', 'white');
}
if ( data[6] == "Devis" || data[6] == "Commandé") {
$('td', row).eq(6).css('background-color', 'Orange');
}
if ( data[6] == "Facturé") {
$('td', row).eq(6).css('background-color', 'LightBlue');
}
if ( data[6] == "Réglée" || data[6] == "Régl part.") {
$('td', row).eq(6).css('background-color', 'LightGreen');
}
},
});
});
$('form input').on('keypress', function(e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
// simuler clic bouton submit
document.getElementById("submitButton").click();
}
});
</script>
</div><!-- content -->

View File

@@ -31,6 +31,18 @@
</tr>
</table>
<br />
<h2>Dossiers générés à contrôler</h2>
<table class="table table-condensed">
<tr tal:repeat="item dossiers_traites">
<td>${item.DATEMAJ.strftime('%d %b')}</td>
<td><a href="${request.application_url}/dossier_view/${item.societe}-${item.NO_ID}">${item.societe}-${item.NO_ID}</td>
<td>${item.NOMCLI}</td>
<td>${item.C_NOM}</td>
<td>${item.USERMAJ}</td>
</tr>
</table>
<br />
<br />

View File

@@ -0,0 +1,31 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<br />
<div class="row">
<table class="table table-bordered table-condensed">
<tr>
<th>Date</th>
<th>Dossier</th>
<th>Action - commentaire</th>
<th class="text-center">Par</th>
</tr>
<tr tal:repeat="item items">
<td>${item.DATE.strftime('%d-%m-%Y')}</td>
<td>
<a href="${request.route_url('dossier_view', nodossier=item.nodossier)}">
${item.nodossier} - ${item.C_NOM}</a>
</td>
<td>${item.COMMENT}</td>
<td class="text-center">${item.USERMAJ}</td>
</tr>
</table>
</div>
<br />
<br />
</div><!-- content -->
</metal:block>

View File

@@ -0,0 +1,60 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="alert alert-danger" tal:condition="message" tal:content="message" />
<div class="row">
<form id="dessin_edit-form" action="${url}" method="post">
<div class="form-group">
<a href="${request.application_url}/dossier_view/${nodossier}#tab_attaches" class="btn btn-default" role="button">
<span class="glyphicon glyphicon-chevron-left"></span>&nbsp;Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span>&nbsp;Enregistrer</button>
</div>
<!-- this will be the input used to pass the drawingboard content to the server -->
<input type="hidden" name="image" value="">
</form>
</div> <!-- row -->
<div style="row">
<div id="dessin"></div>
</div> <!-- row -->
<!-- Drawingboard -->
<link href="${request.static_url('mondumas:static/dist/drawingboard/drawingboard.min.css')}" rel="stylesheet" media="all">
<script src="${request.static_url('mondumas:static/dist/drawingboard/drawingboard.min.js')}"></script>
<script>
var simpleBoard = new DrawingBoard.Board('dessin', {
background: 'none',
size: 10,
controls: [
'Color',
{ Size: { type: 'dropdown' } },
{ Navigation: { } },
],
size: 1,
webStorage: 'session',
enlargeYourContainer: true
});
$(document).ready(function() {
$('#dessin_edit-form').on('submit', function(e) {
//get drawingboard content
var img = simpleBoard.getImg();
//we keep drawingboard content only if it's not the 'blank canvas'
var imgInput = (simpleBoard.blankCanvas == img) ? '' : img;
// alert("imgInput : " + imgInput);
//put the drawingboard content in the form field to send it to the server
$(this).find('input[name=image]').val( imgInput );
//we can also assume that everything goes well server-side
//and directly clear webstorage here so that the drawing isn't shown again after form submission
//but the best would be to do when the server answers that everything went well
simpleBoard.clearWebStorage();
});
});
</script>
</div><!-- content -->
</metal:block>

View File

@@ -1,30 +0,0 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<br />
<table class="table table-condensed">
<tr tal:repeat="item list_devis_en_att">
<td>${item.DATEMAJ.strftime('%d %b')}</td>
<td><a href="${request.application_url}/devis_view/${item.societe}-DE${item.NO_ID}">${item.societe}-DE${item.NO_ID}</td>
<td>${item.NOMCLI}</td>
<td>${item.C_NOM}</td>
<td>${item.USERMAJ}</td>
<td><span class="badge bg-${item.STATUS}">${item.libelle}</span></td>
</tr>
</table>
<br />
<br />
<script type="text/javascript">
$('#generateButton').on('click', function(){
$('i.gly-spin').removeClass('gly-spin');
$('i').addClass('gly-spin');
});
</script>
</div><!-- content -->
</metal:block>

View File

@@ -65,6 +65,16 @@
data-fv-stringlength-message="20 caractères maximum" />
</div>
</div>
<div class="form-group">
<label class="col-xs-3 control-label">Observation</label>
<div class="col-xs-5">
<input class="form-control" type="text" name="C_OBS"
value="${dossier.C_OBS}" placeholder="40 caractères maximum"
data-fv-stringlength="true"
data-fv-stringlength-max="40"
data-fv-stringlength-message="40 caractères maximum" />
</div>
</div>
<h3 class="text-primary">EMAIL et TELEPHONES</h3>
<div class="form-group">

View File

@@ -9,7 +9,8 @@
<tr>
<td>
<h4>CHANTIER</h4>
<a href="${request.application_url}/dossier_edit/${nodossier}" class="btn btn-primary" role="button">
<a href="${request.application_url}/dossier_edit/${nodossier}" tal:condition="access > 1"
class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-pencil"></span>&nbsp;Modifier</a>
</td>
<td>
@@ -26,15 +27,17 @@
<td>
Etage - Code<br />
Ascenseur<br />
Observation<br />
Téléphone 1 et 2<br />
Portable 1 et 2
Portable 1 et 2<br />
</td>
<td>
${dossier.C_ETAGE} - ${dossier.C_CODE}<br />
<span tal:condition="dossier.c_ascenseur==0">NON<br /></span>
<span tal:condition="dossier.c_ascenseur!=0">OUI<br /></span>
${dossier.C_OBS}<br />
${dossier.C_TEL1} - ${dossier.C_TEL2}<br />
${dossier.C_TELP} - ${dossier.C_FAX}
${dossier.C_TELP} - ${dossier.C_FAX} <br />
</td>
</tr>
<tr>
@@ -54,14 +57,12 @@
Police<br />
Sinistre<br />
Votre référence<br />
Observation<br />
Travaux<br />
</td>
<td>
${dossier.NOPOL}<br />
${dossier.NOSIN}<br />
${dossier.VREF}<br />
${dossier.C_OBS}<br />
${dossier.TX_TRAV}<br />
</td>
</tr>
@@ -97,12 +98,12 @@
</table>
</div>
<div class="col-md-6">
<h4>Statut : ${dossier.libelle}</h4>
<br />
<h4>
Statut : <span class="label label-warning">${dossier.libelle}</span>&nbsp;&nbsp;&nbsp;
[ <a href="#" data-toggle="modal" data-target="#confirmStatut" tal:condition="access > 1">Modifier le statut</a> ]
</h4>
<p>Dernière modif. le <b>${dossier.DATEMAJ.strftime('%d/%m/%Y à %H:%M')}</b> par <b>${dossier.USERMAJ}</b></p>
<p>
<a class="btn btn-warning" role="button" href="#"
data-toggle="modal" data-target="#confirmCloture"><span class="glyphicon glyphicon-check"></span> Modif. statut dossier</a>
</p>
</div>
</div> <!-- row -->
@@ -129,10 +130,8 @@
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#tab_suivi"><b>SUIVI du DOSSIER</b></a></li>
<li tal:condition="access > 0">
<a data-toggle="tab" href="#tab_documents"><b>DEVIS - FACTURES</b></a></li>
<li tal:condition="access > 0">
<a data-toggle="tab" href="#tab_attaches"><b>DOCUMENTS ATTACHES</b></a></li>
<li><a data-toggle="tab" href="#tab_documents"><b>DEVIS - FACTURES</b></a></li>
<li><a data-toggle="tab" href="#tab_attaches"><b>DOCUMENTS ATTACHES</b></a></li>
<li tal:condition="nodossier.startswith('PL')">
<a data-toggle="tab" href="#tab_rdf"><b>RAPPORTS DE RDF</b></a></li>
</ul>
@@ -140,7 +139,7 @@
<div class="tab-content">
<div id="tab_suivi" class="tab-pane fade in active">
<h3>SUIVI du DOSSIER</h3>
<p>
<p tal:condition="access > 1">
<a class="btn btn-success" role="button" href="${request.route_url('suivi_edit', nodossier=nodossier, nolig='0')}">
<span class="glyphicon glyphicon-plus"></span> Nouvelle ligne</a>
<a class="btn btn-success" role="button" href="${request.route_url('rdv_edit', nodossier=nodossier, nolig='0')}">
@@ -209,8 +208,8 @@
</td>
<td>${detail.date.strftime('%d-%m-%Y')}</td>
<td>${detail.nomcli}</td>
<td class="text-right">${layout.to_euro(detail.montant)}</td>
<td class="text-center">${detail.status}</td>
<td class="text-right"><span tal:condition="access > 1">${layout.to_euro(detail.montant)}</span></td>
<td class="text-center">${detail.lib_status}</td>
</tr>
</table>
</div>
@@ -249,7 +248,6 @@
<td class="text-center">${detail.usermaj}</td>
</tr>
</table>
<h3 class="text-center">DOCUMENTS TECHNIQUES</h3>
<p>
<a href="${request.application_url}/upload_doc/${nodossier}/FRN" class="btn btn-success" role="button">
@@ -282,6 +280,25 @@
<td class="text-center">${detail.usermaj}</td>
</tr>
</table>
<h3 class="text-center">NOTES et DESSIN</h3>
<p>
<a href="${request.application_url}/note_edit/${nodossier}/0" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-text-size"></span>&nbsp;Ajouter une NOTE</a>
<a href="${request.application_url}/dessin_edit/${nodossier}/0" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-picture"></span>&nbsp;Ajouter dessin</a>
</p>
<br />
<div class="row">
<!-- Listes des Notes -->
<div class="text-center" tal:repeat="item dem_notes">
<div class="col-sm-3">
<a href="${request.application_url}/note_edit/${nodossier}/${item.noligne}">
<span class="glyphicon glyphicon-text-size logo-primary"></span>
<h4>${item.libelle}</h4></a>
</div>
</div>
</div>
</div>
<!-- PANEL RDF -->
<div id="tab_rdf" class="tab-pane fade">
@@ -315,21 +332,22 @@
</div>
<!-- Modal : Confirmation CLOTURE -->
<div class="modal fade" id="confirmCloture" role="dialog" aria-labelledby="confirmClotureLabel" aria-hidden="true">
<!-- Modal : Confirmation MODIF STATUT -->
<div class="modal fade" id="confirmStatut" role="dialog" aria-labelledby="confirmStatutLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Clôturer le dossier</h4>
<h4 class="modal-title">Modifier le statut du dossier</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<form id="add_justif-form" class="form-horizontal" action="${url}" method="post">
<div class="form-group">
<p class="text-center"><b>Voulez-vous clôturer le dossier ?</b></p>
<label class="control-label col-xs-4" for="status">Dossier</label>
<p class="form-control-static col-xs-8"><b>${nodossier} ${dossier.C_QUALITE} ${dossier.C_NOM}</b></p>
</div>
<div class="row">
<div class="form-group">
<label class="control-label col-xs-4" for="status">Sélectionner le statut :</label>
<div class="col-xs-8">
<select class="form-control" id="status" name="status">
@@ -339,23 +357,21 @@
</select>
</div>
</div>
<br>
<div class="row">
<div class="form-group">
<label class="control-label col-xs-4" for="motif">Motif :</label>
<div class="col-xs-8">
<input class="form-control" type="text" id="motif" name="motif" value=""
placeholder="65 caractères maximum"
data-fv-notempty="true"
data-fv-notempty-message="Veuillez remplir un motif"
data-fv-stringlength="true"
data-fv-stringlength-max="65"
data-fv-stringlength-message="65 caractères maximum"/>
<select class="form-control" id="motif" name="motif">
<option selected> </option>
<div tal:repeat="item motifs">
<option>${item.libelle}</option>
</div>
</select>
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-warning" name="form.close">Clôturer</button>
<button type="submit" class="btn btn-warning" name="form.close">Modifier</button>
</div>
</form>
</div>
@@ -363,5 +379,21 @@
</div>
</div>
<script type="text/javascript">
$(function() {
// Javascript to enable link to tab
var hash = document.location.hash;
if (hash) {
console.log(hash);
$('.nav-tabs a[href="'+hash+'"]').tab('show');
}
// Change hash for page-reload
$('a[data-toggle="tab"]').on('show.bs.tab', function (e) {
window.location.hash = e.target.hash;
});
});
</script>
</div>
</metal:block>

View File

@@ -1,30 +0,0 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<br />
<table class="table table-condensed">
<tr tal:repeat="item list_factures_en_att">
<td>${item.DATEMAJ.strftime('%d %b')}</td>
<td><a href="${request.application_url}/devis_view/${item.societe}-FA${item.NO_ID}">${item.societe}-FA${item.NO_ID}</td>
<td>${item.NOMCLI}</td>
<td>${item.C_NOM}</td>
<td>${item.USERMAJ}</td>
<td><span class="badge bg-${item.STATUS}">${item.libelle}</span></td>
</tr>
</table>
<br />
<br />
<script type="text/javascript">
$('#generateButton').on('click', function(){
$('i.gly-spin').removeClass('gly-spin');
$('i').addClass('gly-spin');
});
</script>
</div><!-- content -->
</metal:block>

View File

@@ -0,0 +1,37 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="alert alert-danger" tal:condition="message" tal:content="message" />
<div class="row">
<form id="text_edit-form" action="${url}" method="post">
<div class="form-group">
<a href="${request.application_url}/dossier_view/${nodossier}#tab_attaches" class="btn btn-default" role="button">
<span class="glyphicon glyphicon-chevron-left"></span>&nbsp;Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span>&nbsp;Enregistrer</button>
<button class="btn btn-warning" type="submit" name="form.deleted">
<span class="glyphicon glyphicon-remove"></span>&nbsp;Supprimer</button>
</div>
<div class="form-group">
<label class="control-label" for="text-text">Tapez ou dictez le texte :</label>
<textarea class="form-control monospace-font" rows="30" cols="40" id="notes" name="notes">${note.notes}</textarea>
</div>
<p>Modifié le : ${note.modif_le.strftime('%d-%m-%Y')} par ${note.usermaj}
</p>
</form>
</div> <!-- row -->
<br />
<br />
<script>
$(document).ready(function() {
$('#text_edit-form').formValidation();
$('form input').on('keypress', function(e) {
return e.which !== 13;
});
});
</script>
</div>
</metal:block>

View File

@@ -211,7 +211,7 @@
<label class="checkbox-inline"><input type="checkbox" name="visu_endoscope" value="${rapport.visu_endoscope}"
tal:attributes="checked rapport.visu_endoscope != 0 and 'checked' or None">Endoscope à fibre optique</label>
<label class="checkbox-inline"><input type="checkbox" name="visu_tele" value="${rapport.visu_tele}"
tal:attributes="checked rapport.visu_tele != 0 and 'checked' or None">Endoscope à fibre optique</label>
tal:attributes="checked rapport.visu_tele != 0 and 'checked' or None">Inspection télévisuelle</label>
</div>
<div class="form-group">
<label class="control-label">Recherche de réseaux :&nbsp;&nbsp;&nbsp;</label>
@@ -363,7 +363,7 @@
<br/>
<div class="form-group">
<div class="form-group">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier)}">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf'}">
<span class="glyphicon glyphicon-arrow-left"></span> Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>

View File

@@ -0,0 +1,79 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div tal:condition="message" tal:content="message" class="alert alert-danger" />
<br />
<div class="row">
<form id="rdf_nochantier-form" class="form-horizontal" action="${url}" method="post"
data-fv-framework="bootstrap"
data-fv-icon-valid="glyphicon glyphicon-ok"
data-fv-icon-invalid="glyphicon glyphicon-remove"
data-fv-icon-validating="glyphicon glyphicon-refresh">
<div class="form-group">
<label class="control-label col-sm-3">N° rapport</label>
<div class="col-sm-9">
<p class="form-control-static">${rapport.no_id}</p>
</div>
<label class="control-label col-sm-3">Date d'intervention</label>
<div class="col-sm-9">
<p class="form-control-static">${rapport.date_inter.strftime('%d-%m-%Y')}</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">Nom du chantier actuel</label>
<div class="col-sm-9">
<p class="form-control-static text-danger">${rapport.C_QUALITE} ${rapport.C_NOM}</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3">No du chantier actuel</label>
<div class="col-sm-9">
<p class="form-control-static">${rapport.nochantier}</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Nouveau no de chantier</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="new_nochantier" name="new_nochantier"
placeholder="6 chiffres"
data-fv-numeric="true"
data-fv-numeric-message="Le numero de dossier est incorrect" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<a class="btn btn-default" href="${request.route_url('rdf_view', no_id=norapport)}">
<span class="glyphicon glyphicon-arrow-left"></span> Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
</div>
</div>
</form>
</div> <!-- row -->
<br />
<br />
<br />
</div>
<div metal:fill-slot="additional_scripts">
<!-- autocomplete plugin -->
<link href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(document).ready(function() {
$('#rdf_nochantier-form').formValidation({
framework: 'bootstrap',
excluded: ':disabled',
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
});
});
</script>
</div>
</metal:block>

View File

@@ -15,9 +15,13 @@
</div>
<div class="form-group">
<label class="control-label">N° dossier</label> : ${nodossier}<br />
<label class="control-label">N° rapport</label> : ${norapport}<br />
<label class="control-label">Date d'intervention</label> : ${rapport.date_inter.strftime('%d-%m-%Y')}<br />
<label class="control-label">N° dossier</label> : ${nodossier}<br />
<p tal:condition="pt_name != 'rdf_rapport'">
<a href="${request.route_url('rdf_nochantier', no_id=norapport)}"
tal:condition="access > 0">[ Changer le No dossier ]</a>
</p>
</div>
<h4 class="text-center text-primary">ADRESSE d'INTERVENTION</h4>
@@ -201,7 +205,7 @@
<label class="checkbox-inline" disabled><input type="checkbox" name="visu_endoscope" value="${rapport.visu_endoscope}" disabled
tal:attributes="checked rapport.visu_endoscope != 0 and 'checked' or None">Endoscope à fibre optique</label>
<label class="checkbox-inline" disabled><input type="checkbox" name="visu_tele" value="${rapport.visu_tele}" disabled
tal:attributes="checked rapport.visu_tele != 0 and 'checked' or None">Endoscope à fibre optique</label>
tal:attributes="checked rapport.visu_tele != 0 and 'checked' or None">Inspection télévisuelle</label>
</div>
<div class="form-group">
<label class="control-label">Recherche de réseaux :&nbsp;&nbsp;&nbsp;</label>
@@ -348,7 +352,7 @@
<br/>
<div class="form-group" tal:condition="pt_name=='rdf_view'">
<div class="form-group">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier)}">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier)#tab_rdf}">
<span class="glyphicon glyphicon-arrow-left"></span> Retour Dossier</a>
<a class="btn btn-primary" href="/rdf_edit/${nodossier}/${rapport.date_inter.strftime('%Y-%m-%d')}"
tal:condition="date_facture=='' or access>=8">

View File

@@ -36,7 +36,7 @@
<div class="form-group">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier)}">
<span class="glyphicon glyphicon-arrow-left"></span> Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<button class="btn btn-primary" type="submit" name="form.submitted" tal:condition="access > 1">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
<button class="btn btn-danger" type="submit" name="form.deleted"
tal:condition="nolig != '0' and logged_in.upper()==suivi.USERMAJ">

View File

@@ -13,7 +13,7 @@
<input id="uploadfile" name="files" type="file" value="" required multiple />
</div>
<div class="form-group">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier)}">
<a class="btn btn-default" href="${request.route_url('dossier_view', nodossier=nodossier) + '#tab_attaches'}">
<span class="glyphicon glyphicon-arrow-left"></span> Retour au dossier</a>
<button id="uploadButton" class="btn btn-primary" type="submit" name="form.submitted">
<i class="glyphicon glyphicon-refresh"></i> Télécharger</button>

View File

@@ -9,6 +9,14 @@
data-fv-icon-invalid="glyphicon glyphicon-remove"
data-fv-icon-validating="glyphicon glyphicon-refresh">
<p>
Les ordres de missions doivent être des <span class="text-danger"><b>fichiers PDF originaux téléchargés des portails des Assureurs</b></span>. :
</p>
<ul>
<li>Portail d'AXA</li>
<li>Portail Sinapps (AXA et MAIF)</li>
</ul>
<div class="form-group">
<label class="control-label" for="uploadfile">Veuillez séléctionner un fichier :</label>
<input id="uploadfile" name="filename" type="file" value="" required />
@@ -24,7 +32,7 @@
</div>
</form>
<ul>
<li>Seuls les documents au format <b>PDF</b> seront acceptés.</li>
<li>Seuls les documents au format <b>PDF ORIGINAUX</b> seront acceptés.</li>
<li>La taille de chaque document ne doit <b>pas dépasser 4 Mo</b>.</li>
</ul>

View File

@@ -13,6 +13,18 @@
</table>
</div>
<div class="row">
<h2>Dernières modifications</h2>
<table class="table table-condensed">
<tr tal:repeat="item last_modified">
<td>${item.nomtable}</td>
<td>${item.societe}</td>
<td>${item.no_id}</td>
<td>${item.datemaj.strftime('%d %b, %H:%M')}</td>
<td>${item.usermaj}</td>
</tr>
</table>
</div>
</div><!-- content -->
</metal:block>

View File

@@ -1,124 +0,0 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<h3 class="text-center">POSTES de TRAVAIL</h3>
<br />
<div id="jquery" class="container-fluid">
<div class="row text-center">
<div class="col-sm-3">
<span class="fas fa-desktop logo-primary"></span>
<h4>15 ordinateurs</h4>
</div>
<div class="col-sm-3">
<span class="fas fa-laptop logo-primary"></span>
<h4>1 portable</h4>
</div>
<div class="col-sm-3">
<span class="fas fa-print logo-primary"></span>
<h4>4 imprimantes réseau</h4>
</div>
<div class="col-sm-3">
<span class="fas fa-tablet-alt logo-small"></span>
<h4>10 tablettes</h4>
</div>
</div>
</div>
<br />
<h3 class="text-center">SERVEURS WINDOWS</h3>
<div id="jquery" class="container-fluid">
<div class="row text-center">
<div class="col-sm-3">
<span class="fas fa-server logo-small"></span>
<h4>SRV2012</h4>
Serveur HYPER-V
</div>
<div class="col-sm-3">
<span class="fas fa-hdd logo-primary"></span>
<h4>SRVTSE</h4>
Serveur Accès à Distance
</div>
<div class="col-sm-3">
<span class="fas fa-hdd logo-primary"></span>
<h4>SRVBD / DC</h4>
Serveur Base de données
</div>
<div class="col-sm-3">
<span class="far fa-hdd logo-primary"></span>
<h4>SECOURS</h4>
Serveur de secours
</div>
</div>
</div>
<br />
<h3 class="text-center">SERVEUR LINUX</h3>
<div id="jquery" class="container-fluid">
<div class="row text-center">
<div class="col-sm-12">
<span class="fas fa-hdd logo-small"></span>
<h4>SRVWEB</h4>
</div>
</div>
</div>
<br />
<h3 class="text-center">CONNECTION INTERNET</h3>
<div id="jquery" class="container-fluid">
<div class="row text-center">
<div class="col-sm-12">
<span class="fas fa-globe logo-primary"></span>
</div>
</div>
<br />
<div class="row text-center">
<div class="col-sm-3">
<h4>Routeur SFR Fibre 10 Mbps</h4>
</div>
<div class="col-sm-3">
<span class="far fa-hdd logo-small"></span>
</div>
<div class="col-sm-3">
<span class="far fa-hdd logo-small"></span>
</div>
<div class="col-sm-3">
<h4>Router ORANGE ADSL2</h4>
</div>
</div>
<br />
<div class="row text-center">
<div class="col-sm-12">
<span class="far fa-hdd logo-small"></span>
</div>
</div>
<br />
<br />
<div class="row text-center">
<div class="col-sm-3">
<span class="fas fa-server logo-primary"></span>
<h4>Serveurs</h4>
</div>
<div class="col-sm-3">
<span class="fas fa-desktop logo-primary"></span>
<h4>Ordinateurs</h4>
</div>
<div class="col-sm-3">
<span class="fas fa-print logo-primary"></span>
<h4>Imprimantes</h4>
</div>
<div class="col-sm-3">
<span class="fas fa-wifi logo-primary"></span>
<h4>Bornes Wifi</h4>
</div>
</div>
</div>
<br />
</div><!-- content -->
<div metal:fill-slot="additional_scripts">
<script src="https://kit.fontawesome.com/15d9c0c809.js" crossorigin="anonymous"></script>
</div>
</metal:block>

View File

@@ -34,10 +34,6 @@
<a href="${request.application_url}/text_list"><span class="glyphicon glyphicon-list logo-primary"></span></a>
<h4>TEXTES EMAIL</h4>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/new_home"><span class="glyphicon glyphicon-dashboard logo-primary"></span></a>
<h4>PROTOTYPE HOME</h4>
</div>
</div>
<br />
<div class="row text-center">
@@ -54,16 +50,10 @@
<h4>CONNEXIONS</h4>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/stats_dd/PE"><span class="glyphicon glyphicon-wrench logo-primary"></span></a>
<a href="${request.application_url}/stats"><span class="glyphicon glyphicon-wrench logo-primary"></span></a>
<h4>STATS GLOBALES</h4>
</div>
</div>
<div class="row text-center">
<div class="col-sm-3">
<a href="${request.application_url}/infrastructure"><span class="glyphicon glyphicon-cog logo-primary"></span></a>
<h4>INFRASTRUCTURE</h4>
</div>
</div>
<br />
<br />
</div>

View File

@@ -58,12 +58,15 @@
</div>
<div class="form-group">
<label class="col-xs-3 control-label" for="email_from">Email</label>
<div class="col-xs-8">
<div class="col-xs-5">
<input class="form-control" type="text" name="email_from" value="${societe.email_from}"
placeholder="45 caractères maximum"
data-fv-emailaddress="true"
data-fv-emailaddress-message="L'adresse email n'est pas valide" />
</div>
<div class="col-xs-3">
<input class="form-control" type="text" name="email_cci" value="${societe.email_cci}" />
</div>
</div>
<div class="form-group">
<label class="col-xs-3 control-label" for="Corresp1">Horiares</label>

View File

@@ -26,9 +26,10 @@
<thead>
<tr>
<th>Référence</th>
<th>Fam</th>
<th>Libellé</th>
<th>Unité</th>
<th class="text-right">Prix HT 1</th>
<th class="text-right">Prix HT</th>
<th>Unit</th>
<th>Modif le</th>
</tr>
</thead>

View File

@@ -24,7 +24,7 @@
</div>
<br />
<div class="form-group">
<a class="btn btn-default" href="/">
<a class="btn btn-default" href="/tarifs/AXA">
<span class="glyphicon glyphicon-arrow-left"></span> Retour</a>
<button id="uploadButton" class="btn btn-primary" type="submit" name="form.submitted">
<i class="glyphicon glyphicon-refresh"></i> Importer</button>

View File

@@ -48,7 +48,8 @@
<div class="col-sm-9">
<select class="form-control" id="access" name="access">
<div tal:repeat="item access">
<option value="${item}" tal:attributes="selected str(individu.access)==item[0] and 'selected' or None">${item}</option>
<option value="${item.code}" tal:attributes="selected individu.access==item.code and 'selected' or None">
${item.code} | ${item.libelle}</option>
</div>
</select>
</div>

View File

@@ -63,14 +63,17 @@
var options_ca_3y_1 = {
title: '${title1}',
colors: ['0099c6', '990099', '109618', 'ff9900', '#dc3912', '3366cc'],
};
var options_ca_3y_2 = {
title: '${title2}',
colors: ['0099c6', '990099', '109618', 'ff9900', '#dc3912', '3366cc'],
};
var options_ca_3y_3 = {
title: '${title3}',
colors: ['0099c6', '990099', '109618', 'ff9900', '#dc3912', '3366cc'],
};
var chart_ca_12m = new google.visualization.ColumnChart(document.getElementById('chart_ca_12m'));

View File

@@ -37,6 +37,20 @@
</div>
</div>
<h2>Evolution du CA global sur les 3 dernières années</h2>
<div class="row">
<!-- camembert 1 -->
<div class="col-sm-4">
<div id="chart_ca_3y_1_x" style="width: 100%; height: 500px;"></div>
</div>
<div class="col-sm-4">
<div id="chart_ca_3y_2_x" style="width: 100%; height: 500px;"></div>
</div>
<div class="col-sm-4">
<div id="chart_ca_3y_3_x" style="width: 100%; height: 500px;"></div>
</div>
</div>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
@@ -48,6 +62,10 @@
var dataSet_ca_3y_2 = ${chart_ca_3y_2};
var dataSet_ca_3y_3 = ${chart_ca_3y_3};
var dataSet_ca_3y_1_x = ${chart_ca_3y_1_x};
var dataSet_ca_3y_2_x = ${chart_ca_3y_2_x};
var dataSet_ca_3y_3_x = ${chart_ca_3y_3_x};
function drawChart() {
var data_ca_12m = google.visualization.arrayToDataTable(dataSet_ca_12m);
@@ -55,6 +73,10 @@
var data_ca_3y_2 = google.visualization.arrayToDataTable(dataSet_ca_3y_2);
var data_ca_3y_3 = google.visualization.arrayToDataTable(dataSet_ca_3y_3);
var data_ca_3y_1_x = google.visualization.arrayToDataTable(dataSet_ca_3y_1_x);
var data_ca_3y_2_x = google.visualization.arrayToDataTable(dataSet_ca_3y_2_x);
var data_ca_3y_3_x = google.visualization.arrayToDataTable(dataSet_ca_3y_3_x);
var options_ca_12m = {
title: '${title}',
vAxis: {title: "Chiffre d'Affaires en €"},
@@ -62,15 +84,33 @@
};
var options_ca_3y_1 = {
title: '${title1}',
title: 'CA groupe ${annee1}',
pieHole: 0.3,
};
var options_ca_3y_2 = {
title: '${title2}',
title: 'CA groupe ${annee2}',
pieHole: 0.3,
};
var options_ca_3y_3 = {
title: '${title3}',
title: 'CA groupe ${annee3}',
pieHole: 0.3,
};
var options_ca_3y_1_x = {
title: 'CA global ${annee1}',
pieHole: 0.3,
};
var options_ca_3y_2_x = {
title: 'CA global ${annee2}',
pieHole: 0.3,
};
var options_ca_3y_3_x = {
title: 'CA global ${annee3}',
pieHole: 0.3,
};
var chart_ca_12m = new google.visualization.ColumnChart(document.getElementById('chart_ca_12m'));
@@ -82,6 +122,13 @@
chart_ca_3y_2.draw(data_ca_3y_2, options_ca_3y_2);
var chart_ca_3y_3 = new google.visualization.PieChart(document.getElementById('chart_ca_3y_3'));
chart_ca_3y_3.draw(data_ca_3y_3, options_ca_3y_3);
var chart_ca_3y_1_x = new google.visualization.PieChart(document.getElementById('chart_ca_3y_1_x'));
chart_ca_3y_1_x.draw(data_ca_3y_1_x, options_ca_3y_1_x);
var chart_ca_3y_2_x = new google.visualization.PieChart(document.getElementById('chart_ca_3y_2_x'));
chart_ca_3y_2_x.draw(data_ca_3y_2_x, options_ca_3y_2_x);
var chart_ca_3y_3_x = new google.visualization.PieChart(document.getElementById('chart_ca_3y_3_x'));
chart_ca_3y_3_x.draw(data_ca_3y_3_x, options_ca_3y_3_x);
}
</script>

View File

@@ -42,6 +42,7 @@
var options_delais_p = {
title: '${title}',
pieHole: 0.3,
};
var chart_delais_p = new google.visualization.PieChart(document.getElementById('chart_delais_p'));

View File

@@ -0,0 +1,103 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<br />
<div class="row">
<form method="POST" id="frm" class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2">Societe</label>
<div class="col-sm-4">
<select class="form-control" id="societe" name="societe" onChange="$('#frm').submit()">
<tal:block tal:repeat="item societes">
<option value="${item}" tal:attributes="selected societe==item and 'selected' or None"> ${item}</option>
</tal:block>
</select>
</div>
</div>
</form>
</div>
<div class="row">
<!-- CAMENBERT DU NOMBRE DE DEVIS FACTURES -->
<div class="col-sm-4"><div id="chart_devis_y1" style="width: 100%; height: 500px;"></div></div>
<div class="col-sm-4"><div id="chart_devis_y2" style="width: 100%; height: 500px;"></div></div>
<div class="col-sm-4"><div id="chart_devis_y3" style="width: 100%; height: 500px;"></div></div>
</div>
<div class="row">
<!-- CAMEMBERT DU NOMBRE DE FACTURES AVEC DEVIS -->
<div class="col-sm-4"><div id="chart_fact_y1" style="width: 100%; height: 500px;"></div></div>
<div class="col-sm-4"><div id="chart_fact_y2" style="width: 100%; height: 500px;"></div></div>
<div class="col-sm-4"><div id="chart_fact_y3" style="width: 100%; height: 500px;"></div></div>
</div>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load("current", {packages:["corechart"]});
google.charts.setOnLoadCallback(drawChart);
var dataSet_devis_y1 = ${chart_devis_y1};
var dataSet_devis_y2 = ${chart_devis_y2};
var dataSet_devis_y3 = ${chart_devis_y3};
var dataSet_fact_y1 = ${chart_fact_y1};
var dataSet_fact_y2 = ${chart_fact_y2};
var dataSet_fact_y3 = ${chart_fact_y3};
function drawChart() {
var data_devis_y1 = google.visualization.arrayToDataTable(dataSet_devis_y1);
var data_devis_y2 = google.visualization.arrayToDataTable(dataSet_devis_y2);
var data_devis_y3 = google.visualization.arrayToDataTable(dataSet_devis_y3);
var data_fact_y1 = google.visualization.arrayToDataTable(dataSet_fact_y1);
var data_fact_y2 = google.visualization.arrayToDataTable(dataSet_fact_y2);
var data_fact_y3 = google.visualization.arrayToDataTable(dataSet_fact_y3);
var options_devis_y1 = {
title: '${title_devis1}',
pieHole: 0.3,
};
var options_devis_y2 = {
title: '${title_devis2}',
pieHole: 0.3,
};
var options_devis_y3 = {
title: '${title_devis3}',
pieHole: 0.3,
};
var options_fact_y1 = {
title: '${title_fact1}',
pieHole: 0.3,
};
var options_fact_y2 = {
title: '${title_fact2}',
pieHole: 0.3,
};
var options_fact_y3 = {
title: '${title_fact3}',
pieHole: 0.3,
};
var chart_devis_y1 = new google.visualization.PieChart(document.getElementById('chart_devis_y1'));
chart_devis_y1.draw(data_devis_y1, options_devis_y1);
var chart_devis_y2 = new google.visualization.PieChart(document.getElementById('chart_devis_y2'));
chart_devis_y2.draw(data_devis_y2, options_devis_y2);
var chart_devis_y3 = new google.visualization.PieChart(document.getElementById('chart_devis_y3'));
chart_devis_y3.draw(data_devis_y3, options_devis_y3);
var chart_fact_y1 = new google.visualization.PieChart(document.getElementById('chart_fact_y1'));
chart_fact_y1.draw(data_fact_y1, options_fact_y1);
var chart_fact_y2 = new google.visualization.PieChart(document.getElementById('chart_fact_y2'));
chart_fact_y2.draw(data_fact_y2, options_fact_y2);
var chart_fact_y3 = new google.visualization.PieChart(document.getElementById('chart_fact_y3'));
chart_fact_y3.draw(data_fact_y3, options_fact_y3);
}
</script>
</div><!-- content -->
</metal:block>

View File

@@ -29,6 +29,11 @@
<span class="glyphicon glyphicon-equalizer logo-warning"></span>
<h4>CA / TYPE CLIENTS</h4></a>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/pourcentage_devis/PE" tal:condition="access > 0">
<span class="glyphicon glyphicon-equalizer logo-warning"></span>
<h4>NB DEVIS/FACTURES</h4></a>
</div>
</div>
</div>

View File

@@ -32,6 +32,9 @@ def rdv_edit(request):
url = request.route_url("rdv_edit", nodossier=nodossier, nolig=nolig)
message = ''
# lire son niveau d'accès
member = get_member_by_id(request, logged_in)
access = member.access
# lire la liste des users avec agenda
agendas = get_users_agenda(request, '')
# liste des types de rdv
@@ -86,6 +89,7 @@ def rdv_edit(request):
'agendas': agendas,
'rdv': rdv,
'message': message,
'access': access,
}
@view_config(route_name='agenda', renderer='../templates/agenda/agenda.pt', permission='view')

View File

@@ -23,6 +23,7 @@ import json
import locale
import hashlib
import imaplib
import msal
import email
from ..models.default import *
@@ -72,6 +73,53 @@ def to_percent(x):
"""Takes a float and returns a string"""
return ("%.2f " % x).replace('.', ',') + "%"
def generate_auth_string(user, token):
return "user=%s\x01auth=Bearer %s\x01\x01" % (user, token)
def mailbox_connect(request, societe):
# connecter au serveur IMAP de la societe
if societe == 'PE':
mbx_name = 'peinture-dumas@entreprise-dumas.com'
elif societe == 'ME':
mbx_name = 'menuiserie-dumas@entreprise-dumas.com'
elif societe == 'PL':
mbx_name = 'versanit-dumas@entreprise-dumas.com'
elif societe == 'PO':
mbx_name = 'polynet-dumas@entreprise-dumas.com'
else:
request.session.flash("Cette société est inconnue ou non traitée : %s" % societe, 'danger')
return None
conf = {
"authority": "https://login.microsoftonline.com/47f02128-acf0-47a7-a3bf-27a3619d4a4f",
"client_id": "4fdf2634-a260-4576-a442-684998f0187b", #AppID
"scope": ['https://outlook.office365.com/.default'],
"secret": "dZD8Q~GmG_PFi.t_uRiyBEzwLeMZeemQGHUFta~J", #Key-Value
"secret-id": "5cae3249-0fc0-43e2-bc0e-d78f41964970", #Key-ID
}
app = msal.ConfidentialClientApplication(conf['client_id'], authority=conf['authority'],
client_credential=conf['secret'])
result = app.acquire_token_silent(conf['scope'], account=None)
if not result:
result = app.acquire_token_for_client(scopes=conf['scope'])
if "access_token" not in result:
request.session.flash("ERREUR de connexion au serveur Exchange : %s" % result.get("error"), 'danger')
return None
try:
imap = imaplib.IMAP4_SSL('outlook.office365.com', 993)
# se connecter à la mailbox
imap.authenticate("XOAUTH2", lambda x: generate_auth_string(mbx_name, result['access_token']).encode("utf-8"))
except imaplib.IMAP4.error:
request.session.flash("ERREUR connexion au compte %s" % mbx_name, 'danger')
return None
# returne la connection
return imap
@view_config(route_name='home', renderer='../templates/default/home.pt', permission='view')
def home(request):
@@ -79,122 +127,45 @@ def home(request):
# lire la fiche de l'utilisateur
member = get_member_by_id(request, logged_in)
access = member.access
return {
'page_title': 'Bienvenue sur %s' % request.host,
'project': 'mondumas',
'access': access,
'logged_in': logged_in,
}
def mailbox_connect(request, societe):
# connecter au serveur IMAP de la societe
if societe == 'PE':
mbx_name = 'peinture-dumas@entreprise-dumas.com'
mbx_pwd = 'sasdumas'
elif societe == 'ME':
mbx_name = 'menuiserie-dumas@entreprise-dumas.com'
mbx_pwd = 'sasdumas'
elif societe == 'PL':
mbx_name = 'versanit-dumas@entreprise-dumas.com'
mbx_pwd = 'sasdumas'
elif societe == 'PO':
mbx_name = 'polynet-dumas@entreprise-dumas.com'
mbx_pwd = 'sasdumas'
else:
request.session.flash("Cette société est inconnue ou non traitée : %s" % societe, 'danger')
return None
conn = imaplib.IMAP4_SSL('imap.entreprise-dumas.com')
try:
# se connecter à la mailbox
conn.login(mbx_name, mbx_pwd)
except imaplib.IMAP4.error:
request.session.flash("ERREUR connexion au compte %s" % mbx_name, 'danger')
return None
return conn
@view_config(route_name='new_home', renderer='../templates/default/new_home.pt', permission='view')
def new_home(request):
logged_in = request.authenticated_userid.upper()
url = request.route_url('new_home')
# lire la fiche de l'utilisateur
member = get_member_by_id(request, logged_in)
access = member.access
agenda = member.agenda
datedeb = date.today().strftime("%Y-%m-%d")
nb_dd_restants = get_dd_restant(request)
nb_de_restants = get_de_restant(request)
nb_fa_restants = get_fa_restant(request)
nb_dd_restants = [] # get_dd_restant(request)
nb_de_restants = [] # get_de_restant(request)
nb_fa_restants = [] # get_fa_restant(request)
nb_rdv = get_rdv_by_date(request, datedeb, agenda)
nb_rdf = get_rdf_null(request)
nb_mails = 0
# Récupération de la listes des mails pour ensuite avoir leur nombre
def demandes_lister(societe, search_criteria):
# connecter au serveur de mail
conn = mailbox_connect(request, societe)
# select INBOX
rv, data = conn.select('INBOX', readonly =True)
# créer la liste des entêtes des messages à afficher
liste = []
for criteria in search_criteria:
rv, data = conn.search(None, criteria)
if rv != 'OK':
request.session.flash("ERREUR de lecture de la boîte de réception", 'danger')
return HTTPFound(location=request.route_url('home'))
mail_ids = data[0]
for email_UID in mail_ids.split():
rv, msg_data = conn.fetch(email_UID, '(RFC822)')
if rv != 'OK':
request.session.flash("ERREUR de lecture du message %s" % email_UID, 'danger')
return HTTPFound(location=request.route_url('home'))
msg = email.message_from_bytes(msg_data[0][1])
hdr = email.header.make_header(email.header.decode_header(msg['Subject']))
email_subject = str(hdr)
email_from = email.utils.parseaddr(msg['from'])[1]
# Now convert to local date-time
date_tuple = email.utils.parsedate_tz(msg['Date'])
if date_tuple:
email_date = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
else:
email_date = datetime.now()
d = {
"email_societe": societe,
"email_date": email_date,
"email_from": email_from.split('@')[1],
'email_subject':email_subject,
"email_uid": email_UID
}
liste.append(d)
# deconnexion du serveur
conn.close()
conn.logout()
return liste
'''
# Récupération le nombre de mails en attente
societes = ['PE','ME','PL','PO']
# critères de recherche des demandes d'interventions de la MAIF
# search_criteria = ['FROM service.sinistres@domus-services.fr SUBJECT "Ordre de mission DOMUS - Dossier" UNDELETED'] "
search_criteria = ['FROM gestionsinistre@maif.fr SUBJECT "Intervention entreprise partenaire"',
search_criteria = ['FROM gestionsinistre@maif.fr SUBJECT "Intervention entreprise partenaire" UNDELETED',
'FROM service.sinistres@domus-services.fr UNDELETED']
# 'FROM service.sinistres@domus-services.fr SUBJECT "Ordre de mission DOMUS - Dossier" UNDELETED']
emails=[]
# lister les demandes par societe
for societe in societes:
emails = emails + demandes_lister(societe, search_criteria)
conn = mailbox_connect(request, societe)
if conn != None:
# select INBOX
rv, data = conn.select('INBOX', readonly =True)
# messages lus
msglus = bool(emails)
# créer la liste des entêtes des messages à afficher
for criteria in search_criteria:
rv, data = conn.search(None, criteria)
# nb de mails dans la mailbax
mail_ids = data[0]
nb_mails += len(mail_ids.split())
# deconnexion du serveur
conn.close()
conn.logout()
'''
return {
'page_title': 'Bienvenue sur %s' % request.host,
@@ -206,8 +177,7 @@ def new_home(request):
'nb_fa_restants': nb_fa_restants,
'nb_rdv': nb_rdv,
'nb_rdf': nb_rdf,
'nb_mails': len(emails),
#'mails': emails,
'nb_mails': nb_mails,
}
@view_config(route_name='envoyer_mdp', renderer='../templates/default/envoyer_mdp.pt')
@@ -238,9 +208,11 @@ gestion.entreprise-dumas.com
""" % (request.route_url('redefinir_mdp', lien=lien))
# envoyer l'email
expediteur = request.registry.settings['mondumas.admin_email']
send_mail(request, expediteur, [member.email,], "[Ent. Dumas] Demande de ré-initialisation du mot de passe", body)
request.session.flash("Le lien permettant de redéfinir votre mot de passe vous a été envoyé à l'adresse : %s." % member.email, 'success')
error = send_mail(request, expediteur, [member.email,], "[Ent. Dumas] Demande de ré-initialisation du mot de passe", body)
if len(error) > 0:
request.session.flash(error, 'warning')
else:
request.session.flash("Le lien permettant pour redéfinir votre mot de passe vous a été envoyé à l'adresse : %s." % member.email, 'success')
return HTTPFound(location=request.route_url('affiche_message', login=login))
else:
message = "Le mot de passe fourni est incorrect."
@@ -320,7 +292,6 @@ def redefinir_mdp(request):
@forbidden_view_config(renderer='../templates/default/login.pt')
def login(request):
current_route_path = request.current_route_path()
login = ''
login_url = request.route_url('login')
@@ -331,6 +302,7 @@ def login(request):
came_from = request.params.get('came_from', referrer)
password = ''
message = ''
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
@@ -433,7 +405,7 @@ def ajax_texte(request):
# import pdb;pdb.set_trace()
# lire les articles commencant par
items = get_article(request, 'LIB', groupe, libelle)
items = get_tarif_ajax(request, 'LIB', groupe, libelle)
liste=[]
for row in items:
@@ -451,15 +423,13 @@ def ajax_article(request):
ref = request.GET['ref']
# lire l'article
items = get_article(request, 'REF', groupe, ref)
items = get_tarif_ajax(request, 'REF', groupe, ref)
# puis retourne son libellé et son prixht
liste=[]
d = {}
d['ref'] = items.ref
d['libelle'] = items.libelle
d['prixht'] = "%.2f" % items.prixht
d['unite'] = items.unite
liste.append(d)
return Response(json.dumps(liste))

View File

@@ -21,35 +21,49 @@ from ..models.devis import *
@view_config(route_name='devis_list', renderer='../templates/devis/devis_list.pt', permission='view')
def devis_list(request):
societe = request.matchdict['societe']
nodevis = request.matchdict['nodevis']
try:
int(nodevis)
except:
message = "Numero de Devis incorrect : %s" % societe + '-' + nodevis
url = request.route_url('devis_list')
logged_in = request.authenticated_userid.upper()
message = ''
member = get_member_by_id(request, logged_in)
societe_defaut = member.societe
societe = societe_defaut
access_defaut = member.access
liste=[]
name = ''
cb_tous = "non"
dossiers=None
if 'form.submitted' in request.params:
name = request.params['name']
societe = request.params['societe']
nodevis = request.params['nodevis']
if 'form.joined' in request.params:
md_nochantier = request.params['md_nochantier']
md_nodevis = request.params['md_nodevis']
md_societe = request.params['md_societe']
# modifier un devis à partir d'un dossier
update_devis_nochantier(request, md_societe, md_nodevis, md_nochantier)
message = "Le devis %s a été modifié avec succès : " % md_nodevis
url = request.route_url('devis_list', societe=societe, nodevis=nodevis)
if nodevis != '0':
# lire le devis
devis = get_devis_by_no(request, societe + '-DE' + nodevis)
if devis == None:
message = "Devis non trouvé : %s" % societe + '-' + nodevis
else:
# lire tous les dossiers du chantiers
dossiers = get_dossiers_byName(request, societe, devis.C_NOM)
# lire les devis
devis = get_devis_byName(request, societe, name)
if len(devis) == 0:
message = "Devis non trouvé : %s" % name
return {
'page_title': "Rechercher un devis",
'url': url,
'message': message,
'devis': devis,
'dossiers': dossiers,
'societe': societe,
'name': name,
'nodevis': nodevis,
}
@view_config(route_name='devis_create', permission='view')
@@ -64,6 +78,17 @@ def devis_create(request):
request.session.flash(u"Le devis %s a été créé avec succès" % no_devis.last_insert_id, 'success')
return HTTPFound(location=request.route_url("dossier_view", nodossier=nodossier) + '#tab_documents')
@view_config(route_name='devis_nochantier', permission='view')
def devis_nochantier(request):
societe = request.matchdict['societe']
nodevis = request.matchdict['nodevis']
nochantier = request.matchdict['nochantier']
# modifier un devis à partir d'un dossier
update_devis_nochantier(request, societe, nodevis, nochantier)
request.session.flash(u"Le devis %s a été modifié avec succès : " + nodevis, 'success')
return HTTPFound(location=request.route_url("devis_list", societe=societe, nodevis=nodevis))
@view_config(route_name='devis_view', renderer='../templates/devis/devis_view.pt', permission='view')
@@ -73,6 +98,10 @@ def devis_view(request):
logged_in = request.authenticated_userid.upper()
url = request.route_url("devis_view", nodevis=nodevis)
# lire son niveau d'accès
member = get_member_by_id(request, logged_in)
access = member.access
type_doc = nodevis[3:5]
if type_doc == 'DE':
page_title = "Devis no : %s" % (nodevis)
@@ -93,6 +122,7 @@ def devis_view(request):
bg_color = "bg-%s" % societe
status = get_status_by_id(request, 'DE')
motifs = get_motifs(request)
if 'form.close' in request.params:
status = request.params["status"]
@@ -119,7 +149,9 @@ def devis_view(request):
'details': details,
'bg_color': bg_color,
'status': status,
'motifs': motifs,
'type_doc': type_doc,
'access': access,
}
@view_config(route_name='devis_web', renderer='../templates/devis/devis_web.pt', permission='view')
@@ -184,7 +216,7 @@ def devis_web(request):
os.remove(dest)
# on est en developpement ou en production
if request.registry.settings["mail.username"] == "sasdumas@entreprise-dumas.com":
if request.registry.settings["mondumas.admin_email"].find('@entreprise-dumas.com') > 0:
origin = 'https://gestion.entreprise-dumas.com/devis_preview/%s' % nodevis
pdfkit.from_url(origin, dest, options=options)
else:
@@ -393,3 +425,79 @@ def devis_lig_mv(request):
move_devis_ligne(request, nodevis, int(nolig), move)
request.session.flash(u"La ligne de devis a été déplacée avec succès.", 'success')
return HTTPFound(location=request.route_url("devis_web", nodevis=nodevis))
@view_config(route_name='devis_selected', permission='view')
def devis_selected(request):
# récupérer les paramètres de l'appel de la view
goto = request.matchdict['goto']
datePlan = request.matchdict['date']
nodevis = request.matchdict['nodevis']
# fiche dossier
devis = get_devis_by_no(request, nodevis)
if goto == 'devis_view':
return HTTPFound(location=request.route_url('devis_view', nodevis=nodevis))
else:
return HTTPFound(location=request.route_url('agenda', date=datePlan))
@view_config(route_name='factures_en_att', renderer='../templates/devis/factures_en_att.pt', permission='view')
@view_config(route_name='facture_select', renderer='../templates/devis/factures_en_att.pt', permission='view')
def factures_en_att(request):
if 'facture_select' in request.current_route_path() :
# récupérer les paramètres de l'appel de la view
datePlan = request.matchdict['date']
# sélectionner devis -> goto planning
goto_url = '/facture_selected/agenda/%s/' % datePlan
url = request.route_url('devis_selected', date=datePlan)
else:
# recherche devis en att -> goto fiche devis
goto_url = '/facture_selected/devis_view/%s/' % date.today().strftime('%Y-%m-%d')
url = request.route_url('factures_en_att')
logged_in = request.authenticated_userid.upper()
url = request.route_url('factures_en_att')
member = get_member_by_id(request, logged_in)
societe_defaut = member.societe
societe = societe_defaut
access_defaut = member.access
liste=[]
if 'form.submitted' in request.params:
societe = request.params['societe']
list_factures_en_att = get_factures_en_att(request, societe)
for item in list_factures_en_att:
d = ('%s-FA%s' % (societe, item.numero),item.date.strftime('%d-%m-%Y'), item.nomcli, item.chantier, to_euro(item.montant),
item.nosin, item.libelle, item.usermaj)
liste.append(d)
order_option = 'desc'
return {
'page_title': "Factures en attente de réglement",
'url': url,
'goto_url': goto_url,
'dt_data': json.dumps(liste),
'societe': societe,
'order_option': order_option,
}
@view_config(route_name='facture_selected', permission='view')
def facture_selected(request):
# récupérer les paramètres de l'appel de la view
goto = request.matchdict['goto']
datePlan = request.matchdict['date']
nofacture = request.matchdict['nofacture']
# fiche dossier
facture = get_facture_by_no(request, nofacture)
if goto == 'devis_view':
return HTTPFound(location=request.route_url('devis_view', nodevis=nofacture))
else:
return HTTPFound(location=request.route_url('agenda', date=datePlan))

View File

@@ -1,17 +1,10 @@
# -*- coding: utf8 -*-
from pyramid.response import Response
from pyramid.renderers import render, get_renderer
from pyramid.view import (
view_config,
forbidden_view_config,
)
from pyramid.httpexceptions import (
HTTPFound,
HTTPNotFound,
HTTPForbidden,
)
from pyramid_mailer import get_mailer
from pyramid_mailer.message import Message, Attachment
from datetime import *
@@ -20,7 +13,6 @@ import os
import io
import shutil
import pdfkit
import imaplib
import base64
import email
@@ -56,10 +48,8 @@ def dossier_lookup(request):
member = get_member_by_id(request, logged_in)
societe_defaut = member.societe
societe = societe_defaut
access_defaut = member.access
liste=[]
name = ''
cb_tous = "non"
if 'form.submitted' in request.params:
name = request.params['name']
@@ -72,7 +62,7 @@ def dossier_lookup(request):
# construire la liste
for item in chantiers:
d = ('%s-%s' % (societe, item.numero),item.date.strftime('%d-%m-%Y'), item.nomcli, item.chantier, to_euro(item.montant),
item.nosin, item.status, item.usermaj)
item.nosin, item.libelle, item.usermaj)
liste.append(d)
if len(name) == 0 :
@@ -127,6 +117,9 @@ def dossier_view(request):
bg_color = "bg-%s" % societe
status = get_status_by_id(request, '')
motifs = get_motifs(request)
# lire les notes du dossier
dem_notes = get_dem_notes(request, nodossier, '0')
if 'form.close' in request.params:
status = request.params["status"]
@@ -159,6 +152,9 @@ def dossier_view(request):
'bg_color': bg_color,
'access': access,
'status': status,
'motifs': motifs,
'motif': '',
'dem_notes': dem_notes,
}
@view_config(route_name='dossier_selected', permission='view')
@@ -184,6 +180,10 @@ def suivi_edit(request):
nolig = request.matchdict['nolig']
url = request.route_url("suivi_edit", nodossier=nodossier, nolig=nolig)
# lire son niveau d'accès
member = get_member_by_id(request, logged_in)
access = member.access
message = ''
if nolig == '0':
# nouveau
@@ -230,6 +230,7 @@ def suivi_edit(request):
'nolig': nolig,
'suivi': suivi,
'message': message,
'access': access,
}
@view_config(route_name='dossier_edit', renderer='../templates/dossier/dossier_edit.pt', permission='view')
@@ -365,7 +366,7 @@ def upload_img(request):
societe = rapport.societe
nochantier = rapport.NO_ID
url = request.route_url("upload_img", norapport=nodossier, origine=origine)
url_retour = request.route_url('dossier_view', nodossier=nodossier)
url_retour = request.route_url('dossier_view', nodossier=nodossier) + '#tab_attaches'
titre = "Gérer les photos du dossier %s" % (nodossier)
else:
norapport = request.matchdict['norapport']
@@ -515,7 +516,7 @@ def rdf_edit(request):
if new_values:
update_rapport(request, nodossier, date_inter, new_values)
request.session.flash(u"Le dossier a été mis à jour avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf')
return {
'page_title': "Rapport de RDF : %s du %s" % (nodossier, rapport.date_inter.strftime('%d-%m-%Y')),
@@ -585,7 +586,7 @@ def rdf_client(request):
if len(nomClient) == 2:
update_rapport_client(request, norapport, nomClient[0], nomClient[1])
request.session.flash("Le client du rapporta été modifié avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf')
else:
message = "Veuillez saisir un nom de client ou Annuler"
@@ -598,6 +599,45 @@ def rdf_client(request):
'rapport': rapport,
}
@view_config(route_name='rdf_nochantier', renderer='../templates/dossier/rdf_nochantier.pt', permission='view')
def rdf_nochantier(request):
logged_in = request.authenticated_userid.upper()
norapport = request.matchdict['no_id']
message = ''
# lire code accès du user
access = get_userAccess(request, logged_in)
if access == 0:
request.session.flash("Vous n'avez pas les droits nécessaires pour changer de client.", 'danger')
return HTTPFound(location=request.route_url('rdf_view', no_id=norapport))
url = request.route_url('rdf_nochantier', no_id=norapport)
# lire le rapport
rapport = get_rapport_by_no_id(request, norapport)
nodossier = 'PL-' + str(rapport.nochantier)
if 'form.submitted' in request.params:
new_nochantier = request.params['new_nochantier']
if len(new_nochantier) == 6:
retour = update_rapport_nochantier(request, norapport, new_nochantier)
if retour == "OK":
request.session.flash("Le numéro du dossier été modifié avec succès.", 'success')
return HTTPFound(location=request.route_url('rdf_view', no_id=norapport))
else:
message = "Le numéro du dossier n'existe pas : %s" % new_nochantier
else:
message = "Un numero de dossier doit avoir 6 chiffres"
return {
'page_title': "Changer le no du dossier du rapport n° %s" % norapport,
'url': url,
'message': message,
'access': access,
'norapport': norapport,
'rapport': rapport,
}
@view_config(route_name='rdf_view', renderer='../templates/dossier/rdf_view.pt', permission='view')
def rdf_view(request):
logged_in = request.authenticated_userid.upper()
@@ -651,7 +691,7 @@ def rdf_view(request):
os.remove(dest)
# developpement ou production
if request.registry.settings["mail.username"].find('@entreprise-dumas.com') > 0:
if request.registry.settings["mondumas.admin_email"].find('@entreprise-dumas.com') > 0:
origin = 'https://gestion.entreprise-dumas.com/rdf_rapport/%s' % norapport
pdfkit.from_url(origin, dest, options=options)
else:
@@ -666,12 +706,12 @@ def rdf_view(request):
update_rapport(request, nodossier, rapport.date_inter.strftime('%Y-%m-%d'), new_values)
request.session.flash(u"Le rapport a été généré avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf')
if 'form.validate' in request.params:
update_rapport_validate(request, norapport)
request.session.flash(u"Le rapporta été validé avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf')
if 'form.delete' in request.params:
# le rapport a-t-il des photos ?
@@ -681,7 +721,7 @@ def rdf_view(request):
delete_rapport(request, norapport)
request.session.flash(u"Le rapport a été supprimé avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf')
return {
'page_title': "Rapport no %s du %s" % (norapport, rapport.date_inter.strftime('%d-%m-%Y')),
@@ -830,7 +870,7 @@ def rdf_bill(request):
update_rapport_facture(request, norapport)
request.session.flash("Le rapport a été généré avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_rdf')
return {
'page_title': "Générer une facture pour le rapport du %s" % (rapport.date_inter.strftime('%d-%m-%Y')),
@@ -849,13 +889,16 @@ def rdf_bill(request):
def demandes(request):
def demandes_lister(societe, search_criteria):
# créer la liste des entêtes des messages à afficher
liste = []
# connecter au serveur de mail
conn = mailbox_connect(request, societe)
if conn == None:
return liste
# select INBOX
rv, data = conn.select('INBOX', readonly =True)
# créer la liste des entêtes des messages à afficher
liste = []
for criteria in search_criteria:
rv, data = conn.search(None, criteria)
if rv != 'OK':
@@ -904,21 +947,20 @@ def demandes(request):
# critères de recherche des demandes d'interventions de la MAIF
# search_criteria = ['FROM service.sinistres@domus-services.fr SUBJECT "Ordre de mission DOMUS - Dossier" UNDELETED'] "
search_criteria = ['FROM gestionsinistre@maif.fr SUBJECT "Intervention entreprise partenaire"',
'FROM service.sinistres@domus-services.fr UNDELETED']
# 'FROM service.sinistres@domus-services.fr SUBJECT "Ordre de mission DOMUS - Dossier" UNDELETED']
'FROM service.sinistres@domus-services.fr SUBJECT "Ordre de mission DOMUS" UNDELETED']
emails=[]
# lister les demandes par societe
for societe in societes:
emails = emails + demandes_lister(societe, search_criteria)
# messages lus
msglus = bool(emails)
dossiers_traites = get_dossiers_importes(request)
return {
'page_title': "Liste des emails de demandes d'intervention",
'url': url,
'emails': emails,
'dossiers_traites': dossiers_traites,
}
@view_config(route_name='demandes_dl', permission='view')
@@ -932,24 +974,21 @@ def demandes_dl(request):
if 'maif.fr' in mbx_search:
# extraire les infos de la demmande MAIF
dem_info = get_pdf_infos1(extracted_file)
dem_info = get_pdf_infos_maif(extracted_file)
if societe == 'PE':
cd_cli = 2813
elif societe == 'ME':
cd_cli = 3428
cd_cli = 589
elif societe == 'PO':
cd_cli = 31
else:
# VERSANIT
cd_cli = 1743
elif 'domus-services.fr' in mbx_search:
# extraire les infos de la demmande DOMUS
dem_info = get_pdf_infos2(extracted_file)
if societe == 'PE':
cd_cli = 8991
elif societe == 'ME':
cd_cli = 5276
else:
# VERSANIT
cd_cli = 3209
dem_info = get_pdf_infos_domus(extracted_file)
# toutes les demandes sont pour la société PE
cd_cli = 8991
# extraction OK ? oui, créer une dem_devis et récupèrer son no_id
traite = 0
@@ -983,7 +1022,7 @@ def demandes_dl(request):
def generer_annul_maif(request, societe, extracted_file, temp_file_path):
# extraire les infos de la demmande MAIF
dem_info = get_pdf_infos1(extracted_file)
dem_info = get_pdf_infos_maif(extracted_file)
# extraction OK ? oui, rechercher la dem_devis concerné
traite = 0
@@ -1029,12 +1068,16 @@ def demandes_dl(request):
email_from = request.matchdict['email_from']
email_uid = request.matchdict['email_uid']
logged_in = request.authenticated_userid.upper()
message = ''
if logged_in == 'TST':
request.session.flash("Cette fonction est désactivée dans cette version de test", 'warning')
return HTTPFound(request.route_url('demandes'))
if 'maif' in email_from:
search_criteria = 'FROM gestionsinistre@maif.fr SUBJECT "Intervention entreprise partenaire" UNDELETED'
else:
search_criteria = 'FROM service.sinistres@domus-services.fr SUBJECT "Ordre de mission DOMUS - Dossier" UNDELETED'
search_criteria = 'FROM ne-pas-repondre@domus-services.fr SUBJECT "Ordre de mission DOMUS" UNDELETED'
# connecter au serveur de mail
conn = mailbox_connect(request, societe)
@@ -1046,13 +1089,13 @@ def demandes_dl(request):
rv, data = conn.search(None, search_criteria)
if rv != 'OK':
request.session.flash("ERREUR de lecture de la boîte de réception", 'danger')
return HTTPFound(location=request.route_url('messages'))
return HTTPFound(location=request.route_url('demandes'))
# lire le message avec UID
rv, msg_data = conn.fetch(email_uid, '(RFC822)')
if rv != 'OK':
request.session.flash("ERREUR de lecture du message %S-%s" % (email_from, email_uid), 'danger')
return HTTPFound(location=request.route_url('messages'))
return HTTPFound(location=request.route_url('demandes'))
email_message = email.message_from_bytes(msg_data[0][1])
@@ -1075,18 +1118,18 @@ def demandes_dl(request):
# mission annulée ?
if 'Objet : ANNULATION MISSION' in texte :
# genere ANNULATION mission MAIF
#import pdb;pdb.set_trace()
n = generer_annul_maif(request, societe, extracted_file, temp_file_path)
if n > 0:
# déplacer le message dans la poubelle
# import pdb;pdb.set_trace()
conn.store(email_uid, '+FLAGS', '\\Deleted')
elif 'ANNULATION ORDRE DE MISSION' in texte:
# genere ANNULATION mission DOMUS
nosin = texte.split('\n')[3] # 4ème ligne de texte
#import pdb;pdb.set_trace()
n = generer_annul_domus(request, societe, nosin, temp_file_path)
if n > 0:
# déplacer le message dans la poubelle
# import pdb;pdb.set_trace()
conn.store(email_uid, '+FLAGS', '\\Deleted')
else:
#import pdb;pdb.set_trace()
@@ -1094,6 +1137,7 @@ def demandes_dl(request):
n = generer_mission(request, societe, email_from, extracted_file, temp_file_path)
if n > 0:
# déplacer le message dans la poubelle
# import pdb;pdb.set_trace()
conn.store(email_uid, '+FLAGS', '\\Deleted')
conn.expunge()
@@ -1206,13 +1250,13 @@ def pdf_convert_to_txt(path):
return extracted_text, extracted_file
def get_pdf_infos1(extracted_file):
def get_pdf_infos_maif(extracted_file):
# à partir du fichier texte du pdf
# parcourir les lignes pour retrouver les infos utiles
with open(extracted_file, encoding="utf-8") as fp:
cnt = 1
line = fp.readline()
# première ligne doit être "MAIF"
# première ligne doit contenir "MAIF" ou "FILIA-MAIF"
if line[:-1] not in ['MAIF', 'FILIA-MAIF']:
return {'c_nom': ''}
@@ -1224,7 +1268,8 @@ def get_pdf_infos1(extracted_file):
if line.find('Nos références') == 0:
line = fp.readline()
line = fp.readline()
no_sinistre = line[:-1]
# ne prendre que les 11 premiers car. du no sinistre MAIF
no_sinistre = line[:11]
if line.find('Bénéficiaire des travaux :') == 0:
elt = line[:-1].split(' :')
if len(elt) == 1:
@@ -1288,7 +1333,7 @@ def get_pdf_infos1(extracted_file):
'no_sinistre': no_sinistre,
}
def get_pdf_infos2(extracted_file):
def get_pdf_infos_domus(extracted_file):
# à partir du fichier texte du pdf de DOMUS
# parcourir les lignes pour retrouver les infos utiles
with open(extracted_file, encoding="utf-8") as fp:
@@ -1349,7 +1394,7 @@ def get_pdf_infos2(extracted_file):
'no_sinistre': no_sinistre,
}
def get_pdf_infos3(extracted_file):
def get_pdf_infos_axa(extracted_file):
# à partir du fichier texte du pdf de AXA
# parcourir les lignes pour retrouver les infos utiles
with open(extracted_file, encoding="utf-8") as fp:
@@ -1357,19 +1402,19 @@ def get_pdf_infos3(extracted_file):
cnt = 1
line = fp.readline()
# première ligne doit être :
if line[:-1] != 'Assurance et Banque':
fp.close()
return {'c_nom': ''}
#if line[:-1] != 'Assurance et Banque':
# fp.close()
# return {'c_nom': ''}
c_nom = ''
c_telp = ''
c_email = ''
while line:
if line[:-1] == 'LibellØ':
# import pdb;pdb.set_trace()
line = fp.readline()
if 'PEINTURE' in line or 'PAPIER PEINT' in line or 'CARRELAGE' in line or 'CERAMIQUE' in line:
# déterminer la société
if '@entreprise-dumas.com' in line :
if 'peinture' in line :
societe = 'PE'
elif 'MENUISERIE' in line or 'FERMETURE' in line or 'PARQUET' in line:
elif 'menuiserie' in line :
societe = 'ME'
else:
societe = ''
@@ -1429,6 +1474,176 @@ def get_pdf_infos3(extracted_file):
'no_police': no_police,
'no_sinistre': no_sinistre,
'societe': societe,
'assureur': 'AXA'
}
def get_pdf_infos_sinapps_AXA(extracted_file):
# à partir du fichier texte du pdf de SINAPPS
# parcourir les lignes pour retrouver les infos utiles
with open(extracted_file, encoding="utf-8") as fp:
line = fp.readline()
# première ligne doit être :
if line[:-1] != 'Mission':
fp.close()
return {'c_nom': ''}
else:
line = fp.readline()
if 'AVANSSUR' in line:
assureur = 'AVANSSUR'
else:
assureur = 'AXA'
c_nom = ''
c_telp = ''
c_email = ''
while line:
# déterminer la société
if 'Prestataire' in line :
line = fp.readline()
line = fp.readline()
line = fp.readline()
line = fp.readline()
# import pdb;pdb.set_trace()
if line[:-1] == 'DUMAS' :
societe = 'PE'
elif line[:-1] == 'MENUISERIE DUMAS' :
societe = 'ME'
else :
societe = ''
if line[:-1] == 'Numéro du contrat':
line = fp.readline()
no_police = line[:-1].lstrip("0")
if 'Référence sinistre : ' in line:
# import pdb;pdb.set_trace()
no_sinistre = line[:-1].split(':')[1]
no_sinistre = no_sinistre.strip().lstrip("0")
if line[:-1] == 'Assuré/client':
# import pdb;pdb.set_trace()
line = fp.readline()
c_nom = line[:-1].strip()
line = fp.readline()
c_email = line[:-1].strip()
line = fp.readline()
# tél = les 10 derniers caratères
c_telp = line[-12:-1].strip()
line = fp.readline()
c_adr = line[:-1].strip()
line = fp.readline()
c_adr2 = line[:-1].strip()
line = fp.readline()
c_adr3 = line[:-1]
# début 3ème ligne adr est un code postal ?
if to_int(c_adr3[0:5]) > 0 :
# oui, mémoriser le code postal et la ville
c_cp = c_adr3[0:5]
c_ville = c_adr3[6:].strip()
else:
# non, le code postal et la ville se trouvent dans la 2è ligne
c_cp = c_adr2[0:5]
c_ville = c_adr2[6:]
c_adr2 = ''
# lire ligne suivante
line = fp.readline()
fp.close()
# import pdb;pdb.set_trace()
return {'c_nom': c_nom,
'c_adr': c_adr,
'c_adr2': c_adr2,
'c_cp': c_cp,
'c_ville': c_ville,
'c_telp': c_telp,
'c_email': c_email,
'no_police': no_police,
'no_sinistre': no_sinistre,
'societe': societe,
'assureur': assureur,
}
def get_pdf_infos_sinapps_MAIF(extracted_file):
# à partir du fichier texte du pdf de SINAPPS
# parcourir les lignes pour retrouver les infos utiles
with open(extracted_file, encoding="utf-8") as fp:
line = fp.readline()
# première ligne doit être :
if line[:-1] != 'Mission':
fp.close()
return {'c_nom': ''}
c_nom = ''
c_telp = ''
c_email = ''
no_police = ''
while line:
# déterminer la société
if 'Prestataire' in line :
# import pdb;pdb.set_trace()
line = fp.readline()
if 'VER SANIT' in line :
societe = 'PL'
else:
line = fp.readline()
line = fp.readline()
line = fp.readline()
if 'DUMAS' in line :
societe = 'PE'
elif 'MENUISERIE DUMAS' in line :
societe = 'ME'
elif 'VER SANIT' in line :
societe = 'PL'
else:
societe = ''
if 'Référence sinistre : ' in line:
# import pdb;pdb.set_trace()
no_sinistre = line[:-1].split(':')[1]
no_sinistre = no_sinistre.strip().lstrip("0")
if line[:-1] == 'Assuré/client':
# import pdb;pdb.set_trace()
line = fp.readline()
c_nom = line[:-1].strip()
line = fp.readline()
c_email = line[:-1].strip()
line = fp.readline()
# tél = les 10 derniers caratères
c_telp = line[-12:-1].strip()
line = fp.readline()
c_adr = line[:-1].strip()
line = fp.readline()
c_adr2 = line[:-1].strip()
line = fp.readline()
c_adr3 = line[:-1]
# début 3ème ligne adr est un code postal ?
if to_int(c_adr3[0:5]) > 0 :
# oui, mémoriser le code postal et la ville
c_cp = c_adr3[0:5]
c_ville = c_adr3[6:].strip()
else:
# non, le code postal et la ville se trouvent dans la 2è ligne
c_cp = c_adr2[0:5]
c_ville = c_adr2[6:]
c_adr2 = ''
# lire ligne suivante
line = fp.readline()
fp.close()
# import pdb;pdb.set_trace()
return {'c_nom': c_nom,
'c_adr': c_adr,
'c_adr2': c_adr2,
'c_cp': c_cp[:5],
'c_ville': c_ville,
'c_telp': c_telp,
'c_email': c_email,
'no_police': no_police,
'no_sinistre': no_sinistre,
'societe': societe,
'assureur': 'MAIF'
}
def resize_photos(image_file):
@@ -1436,12 +1651,9 @@ def resize_photos(image_file):
img_org = Image.open(image_file)
# get the size of the original image
width_org, height_org = img_org.size
# set the resizing factor so the aspect ratio can be retained
# factor > 1.0 increases size
# factor < 1.0 decreases size
factor = 0.50
width = int(width_org * factor)
height = int(height_org * factor)
# set the max width
width = 1366
height = int(height_org / width_org * width)
# best down-sizing filter
img_anti = img_org.resize((width, height), Image.ANTIALIAS)
# split image filename into name and extension
@@ -1466,32 +1678,61 @@ def upload_om(request):
UPLOAD d'un ordre de mission en PDF
"""
def generer_mission(request, extracted_file, temp_file_path):
# extraire les infos de la demmande AXA
dem_info = get_pdf_infos3(extracted_file)
def generer_mission(request, dem_info, temp_file_path):
# import pdb;pdb.set_trace()
societe = dem_info['societe']
if societe == 'PE':
cd_cli = 15207
elif societe == 'ME':
cd_cli = 1190
else:
if societe == '':
return "Descriptif de travaux non prevu par le programme. Prévenir M. CAO."
if dem_info['assureur'] == 'AXA':
if societe == 'PE':
cd_cli = 9150
else :
# menuiserie
cd_cli = 5858
elif dem_info['assureur'] == 'AVANSSUR':
if societe == 'PE':
cd_cli = 10149
else :
# menuiserie
cd_cli = 1929
else:
# assureur = MAIF
if societe == 'PE':
cd_cli = 2813
elif societe == 'ME':
# menuiserie
cd_cli = 589
else:
cd_cli = 1743
# extraction OK ? oui, créer une dem_devis et récupèrer son no_id
message = ''
if dem_info['c_nom'] != '':
# dem_devis n'existe pas, creer nouveau dossier
nochantier = insert_dossier(request, societe, cd_cli, dem_info['c_nom'], dem_info['c_adr'], dem_info['c_adr2'], dem_info['c_cp'], \
dem_info['c_ville'], dem_info['c_telp'], dem_info['c_email'], dem_info['no_sinistre'], dem_info['no_police'], '', '')
nodossier = "%s-%s" % (societe, nochantier)
# log de nuit
print('--> CREER DOSSIER sinistre %s <--' % nodossier)
# oui, rechercher la dem_devis concerné par le no de sinistre
nosin = dem_info['no_sinistre']
dem_devis = get_dossier_by_sinistre(request,societe, nosin)
if dem_devis:
# dem_devis existe, ajouter le PDF dans ce dossier
nochantier = dem_devis.NO_ID
nodossier = "%s-%s" % (societe, nochantier)
# insérer une ligne de suivi ANNULATION
insert_suivi(request, nodossier, '!!MISSION CONFIRMEE ou MODIFIEE PAR la MAIF')
# log de nuit
print('--> MODIFIER DOSSIER sinistre %s <--' % nodossier)
else:
# dem_devis n'existe pas, creer nouveau dossier
nochantier = insert_dossier(request, societe, cd_cli, dem_info['c_nom'], dem_info['c_adr'], dem_info['c_adr2'], dem_info['c_cp'], \
dem_info['c_ville'], dem_info['c_telp'], dem_info['c_email'], dem_info['no_sinistre'], dem_info['no_police'], '', '')
nodossier = "%s-%s" % (societe, nochantier)
# log de nuit
print('--> CREER DOSSIER sinistre %s <--' % nodossier)
# récupère le nom du fichier et ajouter le no de dossier
filename = os.path.basename(temp_file_path)
filename = '%s-DD%s-%s' % (societe, nochantier, filename)
tempFile2Dossier(request, societe, nochantier, '0', 'CLT', temp_file_path, filename, 'EMAIL')
# récupère le nom du fichier et ajouter le no de dossier
filename = os.path.basename(temp_file_path)
filename = '%s-DD%s-%s' % (societe, nochantier, filename)
tempFile2Dossier(request, societe, nochantier, '0', 'CLT', temp_file_path, filename, 'EMAIL')
return message
@@ -1522,22 +1763,34 @@ def upload_om(request):
# importer le pdf
if 'form.submitted' in request.params:
# origine du PDF = AXA ?
if 'AXA France GESTION SINISTRES' in extracted_text :
# import pdb;pdb.set_trace()
# Déterminer l'origine du PDF
if 'Votre conseiller AXA' in extracted_text and 'bon de commande pour les travaux' in extracted_text:
# PDF = ordre de mission AXA ?
if 'bon de commande pour les travaux' in extracted_text :
# genere le dossier d'après le fichier PDF
message = generer_mission(request, extracted_file, temp_file)
if message == '':
request.session.flash("Le fichier PDF a été importé avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_lookup'))
else:
message = "ERREUR : L'importation de ce document AXA n'est pas prévue."
# extraire les infos de la demmande AXA
dem_info = get_pdf_infos_axa(extracted_file)
elif 'Envoyé par MAIF le ' in extracted_text :
# PDF envoyé par sur SINAPPS
# extraire les infos de la mission
dem_info = get_pdf_infos_sinapps_MAIF(extracted_file)
elif 'Envoyé par AXA FRANCE IRD le' in extracted_text or 'Envoyé par AVANSSUR - DIRECT ASSURANCE le' in extracted_text:
# PDF envoyé par AXA ou AVANSSUR sur SINAPPS
# extraire les infos de la mission
dem_info = get_pdf_infos_sinapps_AXA(extracted_file)
else:
message = "ERREUR : L'importation de ce type de document n'est pas prévue."
message = "ERREUR : Ce document n'est pas une demande de prestations AXA ou MAIF."
# genere le dossier d'après le fichier PDF
if message == '':
message = generer_mission(request, dem_info, temp_file)
if message :
request.session.flash(message, 'danger')
else:
request.session.flash("Le fichier PDF de %s a été importé dans la societe %s avec succès." % (dem_info['assureur'], dem_info['societe']), 'success')
return HTTPFound(location=request.route_url('dossier_lookup'))
return {
'page_title': 'Importer un ordre de mission AXA',
'page_title': 'Importer un ordre de mission AXA ou MAIF',
'url': url,
'message': message,
'html_text': html_text,
@@ -1546,38 +1799,130 @@ def upload_om(request):
@view_config(route_name='dem_devis', renderer='../templates/dossier/dem_devis.pt', permission='view')
def dem_devis(request):
logged_in = request.authenticated_userid.upper()
goto_url = '/dossier_selected/dossier_view/%s/' % date.today().strftime('%Y-%m-%d')
url = request.route_url('dem_devis')
member = get_member_by_id(request, logged_in)
societe_defaut = member.societe
societe = societe_defaut
access_defaut = member.access
liste=[]
dossiers_traites = get_dossiers_traites(request)
if 'form.submitted' in request.params:
societe = request.params['societe']
dossiers_traites = get_dossiers_traites(request, societe)
for item in dossiers_traites:
d = ('%s-%s' % (societe, item.numero),item.date.strftime('%d-%m-%Y'), item.nomcli, item.chantier, to_euro(item.montant),
item.nosin, item.libelle, item.usermaj)
liste.append(d)
order_option = 'desc'
return {
'page_title': 'Dossiers générés à traiter',
'page_title': 'Dossiers avec statut: "A TRAITER"',
'url': url,
'dossiers_traites':dossiers_traites,
'goto_url': goto_url,
'dt_data': json.dumps(liste),
'societe': societe,
'order_option': order_option,
}
@view_config(route_name='devis_en_att', renderer='../templates/dossier/devis_en_att.pt', permission='view')
def dem_devis(request):
@view_config(route_name='note_edit', renderer='../templates/dossier/note_edit.pt', permission='view')
def note_edit(request):
logged_in = request.authenticated_userid.upper()
url = request.route_url('devis_en_att')
nodossier = request.matchdict['nodossier']
noligne = request.matchdict['noligne']
url = request.route_url('note_edit', nodossier=nodossier, noligne=noligne)
list_devis_en_att = get_devis_en_att(request)
message = ""
if noligne == '0':
page_title = nodossier + ' : Nouvelle note'
note = {}
note['notes'] = ''
note['usermaj'] = logged_in
note['modif_le'] = date.today()
else:
note = get_dem_notes(request, nodossier, noligne)
page_title = nodossier + " : " + note.libelle
if 'form.submitted' in request.params:
notes = request.params["notes"]
update_dem_note(request, nodossier, noligne, notes, logged_in)
request.session.flash("La note a été modifiée avec succès.", 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_attaches')
if 'form.deleted' in request.params:
delete_dem_note(request, nodossier, noligne)
request.session.flash("'%s' a été supprimée avec succès." % note.libelle, 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_attaches')
return {
'page_title': "Devis en attente d'acceptation",
'page_title': page_title,
'url': url,
'list_devis_en_att':list_devis_en_att,
'message': message,
'nodossier': nodossier,
'note': note,
}
@view_config(route_name='factures_en_att', renderer='../templates/dossier/factures_en_att.pt', permission='view')
def dem_devis(request):
@view_config(route_name='dessin_edit', renderer='../templates/dossier/dessin_edit.pt', permission='view')
def dessin_edit(request):
logged_in = request.authenticated_userid.upper()
url = request.route_url('factures_en_att')
nodossier = request.matchdict['nodossier']
noligne = request.matchdict['noligne']
url = request.route_url('dessin_edit', nodossier=nodossier, noligne=noligne)
societe = nodossier[0:2]
nochantier = nodossier[3:]
list_factures_en_att = get_factures_en_att(request)
message = ""
if noligne == '0':
page_title = nodossier + ' : Nouveau dessin'
else:
page_title = nodossier + " : "
if 'form.submitted' in request.params:
# get the base64-encoded-canvas image
img_data = request.params["image"]
img_data = img_data.replace('data:image/png;base64', '')
img_data = img_data.replace(' ', '+')
# convertir image de string en bytes
img_bytes = img_data.encode('utf-8')
decoded_image = base64.decodebytes(img_bytes)
# lire le nombre de dessins déjà créés
nb_dessins = get_nb_dessins(request, nodossier)
# fabriquer le nom du dessin
filename = '%s-DD%s-%s' % (societe, nochantier, 'DESSIN No ' + str(nb_dessins + 1) + ".png")
path = '%s/%s/%s' % (request.registry.settings['mondumas.devfac_dir'], societe, nochantier)
# créer le répertoire du chantier
os.makedirs(path, exist_ok=True)
filepath = os.path.join('%s/%s' % (path, filename))
# ecrire l'image dans un fichier PNG
f = open(filepath, "wb")
f.write(decoded_image)
f.close
filesize = round(os.path.getsize(filepath) / 1024)
insert_dossier_attaches(request, '%s-%s' % (societe, nochantier), 0, 'FRN', filename, '%s Ko' % str(filesize), logged_in)
request.session.flash('%s est enregistré dans les DOC. TECHNIQUES.' % filename, 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier) + '#tab_attaches')
return {
'page_title': "Factures en attente de réglement",
'page_title': page_title,
'url': url,
'list_factures_en_att':list_factures_en_att,
'message': message,
'nodossier': nodossier,
}
@view_config(route_name='dern_suivis', renderer='../templates/dossier/dern_suivis.pt', permission='view')
def dern_suivis(request):
# lire les derniers suivis créés par les attachés de clientèle
items = get_derniers_suivis(request)
return {
'page_title': 'Derniers suivis créés',
'items': items,
}

View File

@@ -11,8 +11,6 @@ from pyramid.httpexceptions import (
HTTPForbidden,
)
from pyramid_mailer import get_mailer
from pyramid_mailer.message import Message, Attachment
from datetime import *
from dateutil.relativedelta import *
from docutils.core import publish_parts
@@ -37,14 +35,7 @@ def parametres(request):
'logged_in': logged_in,
}
@view_config(route_name='infrastructure', renderer='../templates/parametres/infrastructure.pt', permission='manage')
def infrastructure(request):
logged_in = request.authenticated_userid.lower()
return {
'page_title': "Infrastructure",
'logged_in': logged_in,
}
@view_config(route_name='users', renderer='../templates/parametres/users.pt', permission='manage')
def users(request):
@@ -59,16 +50,7 @@ def users(request):
else:
etat = ''
if item.access == 5:
role = 'Gestion'
elif item.access == 8:
role = 'Compta'
elif item.access == 9:
role = 'Admin'
else:
role = 'Production'
d = (item.CD_UTI, item.NOM, item.email, item.agenda, role, item.societe, etat)
d = (item.CD_UTI, item.NOM, item.email, item.agenda, item.libelle, item.societe, etat)
liste.append(d)
return {
@@ -113,7 +95,8 @@ def user_edit(request):
cd_uti = request.matchdict['cd_uti']
url = request.route_url('user_edit', cd_uti=cd_uti)
message = ''
access = ["0 | Production", "5 | Gestion", "8 | Comptabilité", "9 | Administration"]
access = get_p_acces(request)
societes = ['PE','ME','PL','PO','CD']
if cd_uti == '0':
@@ -122,7 +105,7 @@ def user_edit(request):
individu['CD_UTI'] = '0'
individu['NOM'] = ''
individu['email'] = ''
individu['access'] = '0 | Production'
individu['access'] = 0
individu['actif'] = 1
individu['agenda'] = ''
individu['societe'] = ''
@@ -174,10 +157,13 @@ def dashboard(request):
# lire le log de nuit
log_nuit = get_log_nuit(request)
# lire les dernières modifs dans tables
last_modified = get_last_modified(request)
return {
'page_title': "Tableau de bord",
'log_nuit': log_nuit,
'last_modified': last_modified,
}
@view_config(route_name='rappels_rdv', renderer='../templates/parametres/rappels_rdv.pt', permission='manage')
@@ -393,7 +379,11 @@ def articles(request):
def article_edit(request):
ref = request.matchdict['ref']
url = request.route_url('article_edit', ref=ref)
logged_in = request.authenticated_userid
logged_in = request.authenticated_userid.upper()
# modification d'article temporairement désactivé
# if logged_in != 'CAO':
# return HTTPFound(location=request.route_url('articles'))
message = ''
familles = ["Article", "Texte"]
@@ -415,7 +405,7 @@ def article_edit(request):
page_title= 'Nouvel article'
else:
# lire l'article
item = get_article(request, 'REF', ref)
item = get_article(request, ref)
if not item:
request.session.flash("article non trouvé : %s" % ref, 'warning')
return HTTPFound(location=request.route_url('articles'))
@@ -628,7 +618,7 @@ def societes(request):
liste=[]
for item in items:
modif_le = item.modif_le.strftime('%d/%m/%Y')
d = (item.societe, item.NOM, item.email_from, item.TEL, item.bic, item.iban, modif_le)
d = (item.societe, item.NOM, item.email_from, item.TEL, item.email_cci, item.iban, modif_le)
liste.append(d)
@@ -687,7 +677,7 @@ def societe_edit(request):
def tarifs(request):
groupe = request.matchdict['groupe']
groupes = ["AXA", "MAIF", "TEXTE"]
groupes = ["AXA", "MAIF", "IMH", "TEXTE"]
# si table a changé
if 'groupe' in request.params:
@@ -699,7 +689,7 @@ def tarifs(request):
# construire la liste
liste=[]
for item in items:
d = (item.ref, item.libelle, item.unite, to_euro(item.prixht), item.modif_le.strftime("%d-%m-%Y"))
d = (item.ref, item.famille, item.libelle[:100], to_euro(item.prixht), item.unite, item.modif_le.strftime("%d-%m-%Y"))
liste.append(d)
return {
@@ -775,7 +765,7 @@ def tarifs_import(request):
url = request.route_url("tarifs_import")
message = ''
groupes = ['AXA','MAIF']
groupes = ['AXA','MAIF', 'IMH']
groupe = 'AXA'
@@ -794,39 +784,134 @@ def tarifs_import(request):
# readxl returns a pylightxl database that holds all worksheets and its data
book = xlrd.open_workbook(temp_file)
# lire la 1ère feuille et contôler que c'est fichier AXA
sh = book.sheet_by_index(0)
ctl_cellA1 = sh.cell_value(rowx=0, colx=0)
if ctl_cellA1 != 'PEN_2_MISEEN':
request.session.flash(temp_file + " -> Ce fichier ne semble pas être un tarif AXA", 'danger')
return HTTPFound(location=url)
if groupe == "AXA":
# lire la 1ère feuille et contôler que c'est fichier AXA
sh = book.sheet_by_index(0)
ctl_cellA1 = sh.cell_value(rowx=0, colx=0)
if ctl_cellA1 != 'PEN_2_MISEEN':
request.session.flash(temp_file + " -> Ce fichier ne semble pas être un tarif AXA", 'danger')
return HTTPFound(location=url)
# import pdb;pdb.set_trace()
for nsheet in range(book.nsheets):
# pour chaque feuille dans le tableau XLS
sh = book.sheet_by_index(nsheet)
for rx in range(sh.nrows):
ref = sh.cell_value(rx, 0).strip()
# ligne ayant un ref de tarifs ?
if len(ref) >= 12 and len(ref) < 15 and ref[3]=='_':
new_values = {}
new_values['groupe'] = groupe
new_values['ref'] = ref
new_values['famille'] = ref[0:3]
libelle = sh.cell_value(rx, 1)
new_values['libelle'] = libelle.replace(' F+P M²', '').replace(' F+P U', '')
new_values['prixht'] = sh.cell_value(rx, 5)
# col Unité renseigné ?
if len(sh.cell_value(rx, 3)) > 0:
unite = sh.cell_value(rx, 3)
else:
if sh.cell_value(rx, 1).find(' M2'):
unite = ''
# import pdb;pdb.set_trace()
for nsheet in range(book.nsheets):
# pour chaque feuille dans le tableau XLS
sh = book.sheet_by_index(nsheet)
for rx in range(sh.nrows):
ref = sh.cell_value(rx, 0).strip()
# ligne ayant un ref de tarifs ?
if len(ref) >= 12 and len(ref) < 15 and ref[3]=='_':
new_values = {}
new_values['groupe'] = groupe
new_values['ref'] = ref
new_values['famille'] = ref[0:3]
libelle = sh.cell_value(rx, 1)
new_values['libelle'] = libelle.replace(' F+P M²', '').replace(' F+P U', '')
new_values['prixht'] = sh.cell_value(rx, 5)
# col Unité renseigné ?
if len(sh.cell_value(rx, 3)) > 0:
unite = sh.cell_value(rx, 3)
else:
unite = 'U'
new_values['unite'] = unite
if sh.cell_value(rx, 1).find(' M2'):
unite = ''
else:
unite = 'U'
new_values['unite'] = unite
update_tarif(request, '0', new_values)
elif groupe == "MAIF":
# lire la 1ère feuille et contôler que c'est fichier MAIF
sh = book.sheet_by_index(0)
ctl_cellA6 = sh.cell_value(rowx=5, colx=0)
if ctl_cellA6 != 'PEN_01':
request.session.flash(temp_file + " -> Ce fichier ne semble pas être un tarif MAIF", 'danger')
return HTTPFound(location=url)
# import pdb;pdb.set_trace()
for nsheet in range(book.nsheets):
# pour chaque feuille dans le tableau XLS
sh = book.sheet_by_index(nsheet)
for rx in range(sh.nrows):
ref = sh.cell_value(rx, 0).strip()
# ligne ayant un ref de tarifs ?
if len(ref) >= 6 and len(ref) < 15 and ref[3]=='_':
new_values = {}
new_values['groupe'] = groupe
new_values['ref'] = ref
new_values['famille'] = ref[0:3]
libelle = sh.cell_value(rx, 1)
new_values['libelle'] = libelle.replace(' F+P M²', '').replace(' F+P U', '')
if sh.cell_type(rx, 5) == 2:
new_values['prixht'] = sh.cell_value(rx, 5)
# col Unité renseigné ?
if sh.cell_type(rx, 3) != 0:
unite = sh.cell_value(rx, 3)
else:
if sh.cell_value(rx, 1).find(' m2'):
unite = ''
else:
unite = 'U'
new_values['unite'] = unite
update_tarif(request, '0', new_values)
elif groupe == "IMH":
# lire la 1ère feuille et contôler que c'est fichier IMH
sh = book.sheet_by_index(0)
ctl_cellA1 = sh.cell_value(rowx=0, colx=0)
if ctl_cellA1 != 'CO-FORI0':
request.session.flash(temp_file + " -> Ce fichier ne semble pas être un tarif IMH", 'danger')
return HTTPFound(location=url)
# import pdb;pdb.set_trace()
for nsheet in range(book.nsheets):
# pour chaque feuille dans le tableau XLS
sh = book.sheet_by_index(nsheet)
libCol, prixCol, unitCol = 1, 5, 2
if nsheet > 73:
libCol, prixCol, unitCol = 2, 4, 3
for rx in range(sh.nrows):
ref = ""
if sh.cell_type(rx, 0) != 2:
ref = sh.cell_value(rx, 0).strip()
# ligne ayant un ref de tarifs ?
if len(ref) >= 8 and len(ref) < 15 and (ref[2] == '-' or ref[3] == '-'):
new_values = {}
new_values['groupe'] = groupe
new_values['ref'] = ref
if ref[2] == '-':
new_values['famille'] = ref[0:2]
else:
new_values['famille'] = ref[0:3]
if '\n' in sh.cell_value(rx, libCol) and nsheet < 73:
libelleSP = sh.cell_value(rx, libCol).split('\n') # split string with \n as separator if it exists
if libelleSP[1][0] == '(':
libelle = ' '.join(libelleSP[2:]) # if the first character of the second elt is '(' then we remove it
else:
libelle = ' '.join(libelleSP[1:])
else:
libelle = sh.cell_value(rx, libCol)
if len(libelle) > 400:
new_values['libelle'] = 'DATA TOO LONG ! SEE THE ADMIN FOR FURTHER INFOS'
else:
new_values['libelle'] = libelle
unit = sh.cell_value(rx, unitCol)
if unit == 'DEVIS' or 'FORFAIT URGENCE' in unit:
new_values['prixht'] = 0
else:
if sh.cell_type(rx, prixCol) != 2:
new_values['prixht'] = 0
else:
new_values['prixht'] = sh.cell_value(rx, prixCol)
# col Unité renseigné ?
if 'FORFAIT URGENCE' in unit:
unit = 'F. URG.'
new_values['unite'] = unit
update_tarif(request, '0', new_values)
update_tarif(request, '0', new_values)
request.session.flash("Le fichier PDF a été importé avec succès.", 'success')
return HTTPFound(location=url)

View File

@@ -176,8 +176,7 @@ def ca_groupes(request):
print(datedeb)
chart_ca_12m = []
# titre des colonnes
chart_ca_12m.append(('Mois', 'AXA', { 'type':'string','role': 'tooltip'},'DOMUS', { 'type':'string','role': 'tooltip'},
'GMF', { 'type':'string','role': 'tooltip'},'MACIF', { 'type':'string','role': 'tooltip'}, 'MAIF', { 'type':'string','role': 'tooltip'}))
chart_ca_12m.append(('Mois', 'MAIF', { 'type':'string','role': 'tooltip'}, 'MACIF', { 'type':'string','role': 'tooltip'}, 'GMF', { 'type':'string','role': 'tooltip'},'DOMUS', { 'type':'string','role': 'tooltip'}, 'AXA', { 'type':'string','role': 'tooltip'}))
title = 'CA sur 12 mois'
for item in items:
date_aff = item.date[:3] + ' ' + item.date[-4:]
@@ -188,7 +187,7 @@ def ca_groupes(request):
tooltipGMF = "GMF - " + date_aff + ' \nCA: '+str(item.GMF_ca) + '\nDossiers: '+str(round(item.GMF_nb))
tooltipMACIF = "MACIF - " + date_aff + ' \nCA: '+str(item.MACIF_ca) + '\nDossiers: '+str(round(item.MACIF_nb))
# ('+str(item.population)+')/n'
d = (item.mois[:3], float(item.AXA_ca), tooltipAXA, float(item.DOMUS_ca), tooltipDOMUS, float(item.GMF_ca), tooltipGMF, float(item.MACIF_ca), tooltipMACIF, float(item.MAIF_ca), tooltipMAIF)
d = (item.mois[:3], float(item.MAIF_ca), tooltipMAIF, float(item.MACIF_ca), tooltipMACIF, float(item.GMF_ca), tooltipGMF, float(item.DOMUS_ca), tooltipDOMUS, float(item.AXA_ca), tooltipAXA)
chart_ca_12m.append(d)
# debut = aujourd'hui - 11 mois
@@ -203,9 +202,6 @@ def ca_groupes(request):
chart_ca_3y_1.append(('Groupe', 'CA', { 'type':'string','role': 'tooltip'}))
chart_ca_3y_2.append(('Groupe', 'CA', { 'type':'string','role': 'tooltip'}))
chart_ca_3y_3.append(('Groupe', 'CA', { 'type':'string','role': 'tooltip'}))
title1 = 'CA ' + str(thisyear - 2)
title2 = 'CA ' + str(thisyear - 1)
title3 = 'CA ' + str(thisyear)
for item in items:
# construire la liste pour donut cible
tooltip_y1 = item.groupe + ' \nCA: '+str(item.Annee1) + '\nDossiers: '+str(round(item.Count1))
@@ -219,6 +215,32 @@ def ca_groupes(request):
d3 = (item.groupe, float(item.Annee3), tooltip_y3)
chart_ca_3y_3.append(d3)
# lire les CA par mois
items = get_ca_groupe_3y_with_others(request, societe, thisyear)
chart_ca_3y_1_x = []
chart_ca_3y_2_x = []
chart_ca_3y_3_x = []
# titre des colonnes
chart_ca_3y_1_x.append(('Groupe', 'CA', { 'type':'string','role': 'tooltip'}))
chart_ca_3y_2_x.append(('Groupe', 'CA', { 'type':'string','role': 'tooltip'}))
chart_ca_3y_3_x.append(('Groupe', 'CA', { 'type':'string','role': 'tooltip'}))
annee1 = str(thisyear - 2)
annee2 = str(thisyear - 1)
annee3 = str(thisyear)
for item in items:
# construire la liste pour donut cible
tooltip_y1 = item.groupe + ' \nCA: '+str(item.Annee1) + '\nDossiers: '+str(round(item.Count1))
tooltip_y2 = item.groupe + ' \nCA: '+str(item.Annee2) + '\nDossiers: '+str(round(item.Count1))
tooltip_y3 = item.groupe + ' \nCA: '+str(item.Annee3) + '\nDossiers: '+str(round(item.Count1))
# ('+str(item.population)+')/n'
d1_x = (item.groupe, float(item.Annee1), tooltip_y1)
chart_ca_3y_1_x.append(d1_x)
d2_x = (item.groupe, float(item.Annee2), tooltip_y2)
chart_ca_3y_2_x.append(d2_x)
d3_x = (item.groupe, float(item.Annee3), tooltip_y3)
chart_ca_3y_3_x.append(d3_x)
return {
'page_title': "CA par groupe",
'url': url,
@@ -226,10 +248,13 @@ def ca_groupes(request):
'chart_ca_3y_1': json.dumps(chart_ca_3y_1),
'chart_ca_3y_2': json.dumps(chart_ca_3y_2),
'chart_ca_3y_3': json.dumps(chart_ca_3y_3),
'chart_ca_3y_1_x': json.dumps(chart_ca_3y_1_x),
'chart_ca_3y_2_x': json.dumps(chart_ca_3y_2_x),
'chart_ca_3y_3_x': json.dumps(chart_ca_3y_3_x),
'title': title,
'title1': title1,
'title2': title2,
'title3': title3,
'annee1': annee1,
'annee2': annee2,
'annee3': annee3,
'societes': societes,
'societe': societe,
}
@@ -254,9 +279,9 @@ def ca_clients(request):
print(datedeb)
chart_ca_12m = []
# titre des colonnes
chart_ca_12m.append(('Mois', 'ASSURANCES', { 'type':'string','role': 'tooltip'}, 'EXPERTS', { 'type':'string','role': 'tooltip'},
'GROUPEMENT', { 'type':'string','role': 'tooltip'}, 'PARTICULIER', { 'type':'string','role': 'tooltip'},
'REGIES', { 'type':'string','role': 'tooltip'}, 'SOCIETE', { 'type':'string','role': 'tooltip'}))
chart_ca_12m.append(('Mois', 'SOCIETE', { 'type':'string','role': 'tooltip'}, 'REGIES', { 'type':'string','role': 'tooltip'},
'PARTICULIER', { 'type':'string','role': 'tooltip'}, 'GROUPEMENT', { 'type':'string','role': 'tooltip'}, 'EXPERTS',
{ 'type':'string','role': 'tooltip'}, 'ASSURANCES', { 'type':'string','role': 'tooltip'}))
title = 'CA sur 12 mois'
for item in items:
date_aff = item.date[:3] + ' ' + item.date[-4:]
@@ -268,8 +293,8 @@ def ca_clients(request):
tooltipR = date_aff + ' \nCA: '+str(item.R_ca) + '\nDossiers: '+str(round(item.R_nb))
tooltipS = date_aff + ' \nCA: '+str(item.S_ca) + '\nDossiers: '+str(round(item.S_nb))
# ('+str(item.population)+')/n'
d = (item.mois[:3], float(item.A_ca), tooltipA, float(item.E_ca), tooltipE, float(item.G_ca), tooltipG,
float(item.P_ca), tooltipP, float(item.R_ca), tooltipR, float(item.S_ca), tooltipS)
d = (item.mois[:3], float(item.S_ca), tooltipS, float(item.R_ca), tooltipR, float(item.P_ca), tooltipP,
float(item.G_ca), tooltipG, float(item.E_ca), tooltipE, float(item.A_ca), tooltipA)
chart_ca_12m.append(d)
# debut = aujourd'hui - 11 mois
@@ -379,3 +404,92 @@ def delais_pourcentage(request):
'groupe': groupe,
}
@view_config(route_name='pourcentage_devis', renderer='../templates/stats/pourcentage_devis.pt', permission='view')
def pourcentage_devis(request):
societe = request.matchdict['societe']
url = request.route_url('delais_pourcentage', societe = societe)
# année aujourd'hui
thisyear = date.today().year
societes = ['PE','ME','PL']
# si societe a été changé par le user
if 'societe' in request.params:
societe = request.params["societe"]
# lire le nb de devis facturés
items = get_nb_devis_fact(request, societe, thisyear)
chart_devis_y1 = []
chart_devis_y2 = []
chart_devis_y3 = []
# titre des colonnes
chart_devis_y1.append(('Catégorie', 'Nb Devis facturés'))
chart_devis_y2.append(('Catégorie', 'Nb Devis facturés'))
chart_devis_y3.append(('Catégorie', 'Nb Devis facturés'))
title_devis1 = 'NB DEVIS ' + str(thisyear - 2)
title_devis2 = 'NB DEVIS ' + str(thisyear - 1)
title_devis3 = 'NB DEVIS ' + str(thisyear)
for item in items:
d1 = ('Nb de devis facturés', float(item.devis_fact_y1))
chart_devis_y1.append(d1)
d1 = ('Nb de devis non facturés', float(item.devis_non_fact_y1))
chart_devis_y1.append(d1)
d2 = ('Nb de devis facturés', float(item.devis_fact_y2))
chart_devis_y2.append(d2)
d2 = ('Nb de devis non facturés', float(item.devis_non_fact_y2))
chart_devis_y2.append(d2)
d3 = ('Nb de devis facturés', float(item.devis_fact_y3))
chart_devis_y3.append(d3)
d3 = ('Nb de devis non facturés', float(item.devis_non_fact_y3))
chart_devis_y3.append(d3)
# lire le nb de devis facturés
items = get_nb_fact_with_devis(request, societe, thisyear)
chart_fact_y1 = []
chart_fact_y2 = []
chart_fact_y3 = []
# titre des colonnes
chart_fact_y1.append(('Catégorie', 'Nb Factures avec devis'))
chart_fact_y2.append(('Catégorie', 'Nb Factures avec devis'))
chart_fact_y3.append(('Catégorie', 'Nb Factures avec devis'))
title_fact1 = 'NB FACTURES ' + str(thisyear - 2)
title_fact2 = 'NB FACTURES ' + str(thisyear - 1)
title_fact3 = 'NB FACTURES ' + str(thisyear)
for item in items:
d1 = ("Nb de factures avec devis", float(item.fact_w_devis_y1))
chart_fact_y1.append(d1)
d1 = ("Nb de factures sans devis", float(item.fact_wo_devis_y1))
chart_fact_y1.append(d1)
d2 = ("Nb de factures avec devis", float(item.fact_w_devis_y2))
chart_fact_y2.append(d2)
d2 = ("Nb de factures sans devis", float(item.fact_wo_devis_y2))
chart_fact_y2.append(d2)
d3 = ("Nb de factures avec devis", float(item.fact_w_devis_y3))
chart_fact_y3.append(d3)
d3 = ("Nb de factures sans devis", float(item.fact_wo_devis_y3))
chart_fact_y3.append(d3)
return {
'page_title': "Nombre de devis facturés et de factures avec devis",
'url': url,
'chart_devis_y1': json.dumps(chart_devis_y1),
'chart_devis_y2': json.dumps(chart_devis_y2),
'chart_devis_y3': json.dumps(chart_devis_y3),
'chart_fact_y1': json.dumps(chart_fact_y1),
'chart_fact_y2': json.dumps(chart_fact_y2),
'chart_fact_y3': json.dumps(chart_fact_y3),
'title_devis1': title_devis1,
'title_devis2': title_devis2,
'title_devis3': title_devis3,
'title_fact1': title_fact1,
'title_fact2': title_fact2,
'title_fact3': title_fact3,
'societes': societes,
'societe': societe,
}

View File

@@ -7,6 +7,10 @@ from pyramid.view import (
from pyramid_mailer import get_mailer
from pyramid_mailer.message import Message, Attachment
import smtplib
import ssl
from email.message import EmailMessage
from time import sleep
from datetime import *
from dateutil.relativedelta import *
@@ -32,6 +36,7 @@ def batch_nuit(request):
# ----- effacer le log
truncate_log(request)
# ----- PURGE des données obsolètes
insert_log(request, 'PURGE','- Début PURGE DES DONNEES OBSOLETES')
TODAY = date.today()
@@ -39,15 +44,17 @@ def batch_nuit(request):
if TODAY.day == 1 :
# données de + 10 ans
until_date = date(TODAY.year - 10, TODAY.month, 1)
# purge_mensuelle(request, until_date)
# purge_clients(request)
# delete_orphan_attached_files(request)
purge_mensuelle(request, until_date)
purge_clients(request)
delete_orphan_attached_files(request)
"""
# ----- MAJ STATS DELAIS de traitements des dossiers sur 1 an
insert_log(request, 'STATS', '- Début MAJ STATS DES PERFORMANCES')
update_chantiers_delais(request, date(TODAY.year, TODAY.month, 1))
update_chantiers_delais(request, date(TODAY.year, TODAY.month-1, 1))
update_chantiers_delais(request, date(TODAY.year, TODAY.month-2, 1))
datejour = TODAY
for i in range(12):
update_chantiers_delais(request, datejour)
datejour = datejour + relativedelta(months=-1)
societes = ['PL', 'ME', 'PE']
datejour = TODAY
@@ -58,12 +65,14 @@ def batch_nuit(request):
update_stats_delais(request, societe, datejour.strftime('%Y-%m-%d'), groupe)
# recule d'un mois
datejour = datejour + relativedelta(months=-1)
"""
# ----- RAPPELS DES RENDEZ-VOUS
update_rappels(request)
# attendre 3 secondes
sleep(3)
# ----- envoyer les rappels
notifier_rappels(request)
@@ -92,6 +101,7 @@ def notifier_rappels(request):
# RDV ayant une heure
date_heure = item.rdv_date.strftime('%d/%m/%Y - %H:%M')
szBody = """
<!DOCTYPE html>
<html><body>
<p>Bonjour %s %s,</p>
<p>Lentreprise %s vous rappelle votre prochain rendez-vous, pris d'un commun accord</p>
@@ -122,20 +132,51 @@ def notifier_rappels(request):
return
def email_rappels(request, objet, corps, destinataire, societe):
# import pdb;pdb.set_trace()
# Create a secure SSL context
context = ssl.create_default_context()
smtp_server = "smtp.office365.com"
smtp_port = 587 # For TLS
# lire la societe
soc = get_societes(request, societe)
if soc:
expediteur = soc.email_from
smtp_user = soc.email_from
smtp_pass = soc.email_cci
else:
expediteur = "peinture@entreprise-dumas.com"
# envoyer le rappel
error = send_mail(request, expediteur, destinataire, "[Ent. Dumas] " + objet, corps)
smtp_user = request.registry.settings['mail.username']
smtp_pass = request.registry.settings['mail.password']
return error
# construire le message
msg = EmailMessage()
msg['Subject'] = "[Ent. Dumas] " + objet
msg['From'] = smtp_user
msg['To'] = destinataire
# msg['Bcc'] = "phuoc@caotek.fr"
msg.set_content(corps, subtype='html')
# Try to log in to server and send email
err = ''
try:
server = smtplib.SMTP(smtp_server,smtp_port)
server.starttls(context=context) # Secure the connection
server.login(smtp_user, smtp_pass)
# envoyer l'email
server.send_message(msg)
print("sendmail -> OK")
except Exception as e:
# Just print(e) is cleaner and more likely what you want,
# but if you insist on printing message specifically whenever possible...
err = repr(e)[0:400]
insert_log(request, 'MAILER', "- ERROR : %s, TO : %s" % (err, destinataire))
finally:
server.quit()
return len(err)
def email_rapport(request):
NOW = datetime.now()
corps = "<html><body><p>=============================================</p>"
corps = "<!DOCTYPE html><html><body><p>=============================================</p>"
corps += "<p>Rapport du traitement de nuit du " + NOW.strftime('%d/%m/%Y - %H:%M') + "</p>"
corps += "<p>=============================================</p><p></p><p>"
@@ -147,24 +188,20 @@ def email_rapport(request):
corps += "</p><p></p><p>=============================================</p><p></p>"
expediteur = request.registry.settings['mondumas.admin_email']
destinataire = ["phuoc@caotek.fr","peinture@entreprise-dumas.com"]
destinataire = ["phuoc@caotek.fr","peinture-dumas@entreprise-dumas.com"]
send_mail(request, expediteur, destinataire, "[DEV_NUIT] Rapport des traitements de nuit", corps)
return
def send_mail(request, expediteur, destinataires, objet, corps):
body = """
%s
""" % (corps)
body_html = Attachment(data=corps, transfer_encoding="base64", disposition='inline')
message = Message(subject=objet,
sender=expediteur,
recipients=destinataires,
html=body)
html=body_html)
mailer = get_mailer(request)
# import pdb;pdb.set_trace()
msg = ''
try:
mailer.send_immediately(message)
@@ -172,14 +209,10 @@ def send_mail(request, expediteur, destinataires, objet, corps):
except Exception as e:
# Just print(e) is cleaner and more likely what you want,
# but if you insist on printing message specifically whenever possible...
if hasattr(e, 'message'):
msg = e.message
else:
msg = e
# logguer l'erreur
msg = repr(e)[0:400]
insert_log(request, 'MAILER', "- ERROR : %s, TO : %s" % (msg, destinataires))
return len(msg)
return str(msg)
@view_config(route_name='batch_test')
def batch_test(request):
@@ -194,15 +227,25 @@ def batch_test(request):
if par != 'Sansa5tark':
return Response('Erreur : paramètre incorrect')
# à revoir
update_chantiers_status(request)
TODAY = date.today()
until_date = date(TODAY.year - 10, TODAY.month, 1)
# purge_mensuelle(request, until_date)
# purge_clients(request)
# delete_orphan_attached_files(request)
# ------ UPDATE statut DEVIS COMMANDE
# update_devis_statut_4(request)
"""
# ----- MAJ STATS DELAIS de traitements des dossiers sur 1 an
datejour = TODAY
for i in xrange(12):
update_chantiers_delais(request, datejour)
datejour = datejour + relativedelta(months=-1)
"""
# données de + 10 ans
until_date = date(TODAY.year - 10, TODAY.month, 1)
purge_mensuelle(request, until_date)
purge_clients(request)
delete_orphan_attached_files(request)
# update_chantiers_delais(request, date(TODAY.year - 1, TODAY.month, 1))
insert_log(request, 'TEST','- Fin -')
@@ -240,41 +283,6 @@ def delete_orphan_attached_files(request):
if nbLus > 0:
insert_log(request, 'DELETE', '%s : %d Répertoires lues, %d supprimées' % (ste, nbLus, nbSupp))
def update_chantiers_status(request):
"""
Ce traitement parcourt tous les chantiers de chacune des 5 sociétés
pour mettre à jour son STATUS selon l'avancement du chantier.
"""
societes = ['PE','ME','PL','PO','CD']
for ste in societes:
# lire tous les chantiers
chantiers = get_all_chantiers(request, ste)
for item in chantiers:
status = item.STATUS
# si le chantier est annulé, ne rien faire
if status <= 10:
# lire la dernière facture du chantier
facture = get_last_facture(request, ste, item.NO_ID)
if facture :
# remonte le status de la facture ('','Régl part.', 'Réglée')
status = facture.STATUS
else:
# lire le dernier devis du chantier ('','Commandé, 'Facturé')
devis = get_last_devis(request, ste, item.NO_ID)
if devis:
# remonte le status de la facture
status = devis.STATUS
else:
if item.DATEVI:
status = 2
# maj le status du chantier
if status != item.STATUS:
update_chantier_status(request, ste, item.NO_ID, status)
def update_chantiers_delais(request, date):
"""
Ce traitement calcul les delais :
@@ -335,29 +343,30 @@ def update_chantiers_delais(request, date):
update_chantier_delais(request, societe, item.NO_ID, delai_contact, delai_rdv, delai_devis, delai_facture)
def purge_clients(request):
# lire tous les clients
clients = get_all_clients(request)
for client in clients:
dern_operation = None
# lire la facture la + récente
facture = get_last_facture_client(request, client.societe, client.CD_CLI)
if facture:
dern_operation = facture.DATE
# lire le chantier le + récent
chantier = get_last_chantier_client(request, client.societe, client.CD_CLI)
if chantier:
dern_operation = chantier.DATE
else:
# lire le réglemnet le + récent
payment = get_last_payment_client(request, client.societe, client.CD_CLI)
if payment:
dern_operation = payment.DATE
# lire le devis le + récent
devis = get_last_devis_client(request, client.societe, client.CD_CLI)
if devis:
dern_operation = devis.DATE
else:
# lire le devis le + récent
devis = get_last_devis_client(request, client.societe, client.CD_CLI)
if devis:
dern_operation = devis.DATE
# lire le proforma le + récent
proforma = get_last_proforma_client(request, client.societe, client.CD_CLI)
if proforma:
dern_operation = proforma.DATE
else:
# lire le chantier le + récent
chantier = get_last_chantier_client(request, client.societe, client.CD_CLI)
if chantier:
dern_operation = chantier.DATE
# lire la facture la + récente
facture = get_last_facture_client(request, client.societe, client.CD_CLI)
if facture:
dern_operation = facture.DATE
# mémoriser dernière opération
update_client_dern_operation(request, client.societe, client.CD_CLI, dern_operation)

View File

@@ -20,21 +20,23 @@ pyramid.includes =
sqlalchemy.url = mysql://phuoc:phuoc!@192.168.0.31/bddevfac?charset=utf8
sqlalchemy.pool_recycle = 3600
mondumas.admin_email = sasdumas@entreprise-dumas.com
mondumas.admin_email = peinture-dumas@entreprise-dumas.com
mondumas.devfac_url = mondumas:static/DEVFAC/DOCS_ATTACHES/
mondumas.devfac_dir = /DEVFAC14/DOCS_ATTACHES
# Mailer configuration
mail.host = mail1.sfrbusinessteam.fr
mail.port = 25
mail.ssl = False
# SMTP alinto quota limit 30 emails
# mail.host = v5.alinto.net
mail.host = smtp.office365.com
mail.port = 587
mail.tls = True
mail.username = peinture-dumas@entreprise-dumas.com
mail.password = Nar50611
# SMTP RELAY alinto
# mail.host = gatewayxl.alinto.net
# mail.port = 465
# mail.ssl = True
# ATTENTION : mail.username est utilisé dans rdf_view
mail.username = peinture-dumas@entreprise-dumas.com
mail.password = sasdumas
# mail.username = smtpsasdumas@entreprise-dumas.com
[server:main]
use = egg:waitress#main

View File

@@ -9,17 +9,17 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
'pyramid',
'pyramid == 1.10',
'pyramid_chameleon',
'pyramid_debugtoolbar',
'pyramid_layout',
'pyramid_mailer',
'pyramid_tm',
'SQLAlchemy',
'SQLAlchemy == 1.2.19',
'transaction',
'zope.sqlalchemy == 1.1',
'waitress',
'mysqlclient',
'mysqlclient == 1.4',
'docutils',
'pdfkit',
'python-dateutil',

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3
import smtplib
import ssl
# Fabrication du corps du email_passwordMessage
sender = "peinture@entreprise-dumas.com"
receiver = "phuoc@caotek.fr"
message = """\
Subject: Test d'envoi d'un message en TLS
Voici le contenu du message. Phuoc Cao"""
# Create a secure SSL context
context = ssl.create_default_context()
smtp_server = "smtp.office365.com"
smtp_port = 587 # For TLS
smtp_user = "peinture-dumas@entreprise-dumas.com"
smtp_pass = "Nar50611"
# Try to log in to server and send email
try:
server = smtplib.SMTP(smtp_server,smtp_port)
# server.ehlo() # Can be omitted
server.starttls(context=context) # Secure the connection
# server.ehlo() # Can be omitted
server.login(smtp_user, smtp_pass)
# envoyer l'email
server.sendmail(sender, receiver, message)
print("sendmail -> OK")
except Exception as e:
# Print any error messages to stdout
print(e)
finally:
server.quit()