Enviar correo

This commit is contained in:
Mauricio Baeza 2017-10-16 00:02:51 -05:00
parent 28bb6a74d1
commit 8ff857ec73
9 changed files with 508 additions and 8 deletions

View File

@ -14,8 +14,15 @@
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
import re
import smtplib
import collections
from collections import OrderedDict
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from email.utils import formatdate
class CaseInsensitiveDict(collections.MutableMapping):
@ -223,3 +230,80 @@ class NumLet(object):
return re.sub('$', 's', palabra)
else:
return palabra + 'es'
class SendMail(object):
def __init__(self, config):
self._config = config
self._server = None
self._error = ''
self._is_connect = self._login()
@property
def is_connect(self):
return self._is_connect
@property
def error(self):
return self._error
def _login(self):
try:
if self._config['ssl']:
self._server = smtplib.SMTP_SSL(
self._config['servidor'],
self._config['puerto'], timeout=10)
else:
self._server = smtplib.SMTP(
self._config['servidor'],
self._config['puerto'], timeout=10)
self._server.login(self._config['usuario'], self._config['contra'])
return True
except smtplib.SMTPAuthenticationError as e:
if '535' in str(e):
self._error = 'Nombre de usuario o contraseña inválidos'
return False
if '534' in str(e) and 'gmail' in self._config['servidor']:
self._error = 'Necesitas activar el acceso a otras ' \
'aplicaciones en tu cuenta de GMail'
return False
except smtplib.SMTPException as e:
self._error = str(e)
return False
except Exception as e:
self._error = str(e)
return False
return
def send(self, options):
try:
message = MIMEMultipart()
message['From'] = self._config['usuario']
message['To'] = options['para']
message['CC'] = options['copia']
message['Subject'] = options['asunto']
message['Date'] = formatdate(localtime=True)
message.attach(MIMEText(options['mensaje'], 'html'))
for f in options['files']:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f[0])
encoders.encode_base64(part)
part.add_header(
'Content-Disposition',
"attachment; filename={}".format(f[1]))
message.attach(part)
receivers = options['para'].split(',') + options['copia'].split(',')
self._server.sendmail(
self._config['usuario'], receivers, message.as_string())
return ''
except Exception as e:
return str(e)
def close(self):
try:
self._server.quit()
except:
pass
return

View File

@ -76,12 +76,33 @@ class AppValues(object):
if file_object is None:
session = req.env['beaker.session']
values = req.params
req.context['result'] = self._db.validate_cert(values, session)
if table == 'correo':
req.context['result'] = self._db.validate_email(values)
elif table == 'sendmail':
req.context['result'] = self._db.send_email(values, session)
else:
req.context['result'] = self._db.validate_cert(values, session)
else:
req.context['result'] = self._db.add_cert(file_object)
resp.status = falcon.HTTP_200
class AppConfig(object):
def __init__(self, db):
self._db = db
def on_get(self, req, resp):
values = req.params
req.context['result'] = self._db.get_config(values)
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
values = req.params
req.context['result'] = self._db.add_config(values)
resp.status = falcon.HTTP_200
class AppPartners(object):

View File

@ -15,7 +15,9 @@ import time
import unicodedata
import uuid
import zipfile
from io import BytesIO
from smtplib import SMTPException, SMTPAuthenticationError
from xml.etree import ElementTree as ET
import uno
@ -25,7 +27,7 @@ from com.sun.star.awt import Size
import pyqrcode
from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet
from .helper import CaseInsensitiveDict, NumLet, SendMail
from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PRE
@ -926,3 +928,33 @@ def to_zip(*files):
return zip_buffer.getvalue()
def make_fields(xml):
doc = ET.fromstring(xml)
data = CaseInsensitiveDict(doc.attrib.copy())
data.pop('certificado')
data.pop('sello')
version = data['version']
receptor = doc.find('{}Receptor'.format(PRE[version]))
receptor = CaseInsensitiveDict(receptor.attrib.copy())
data['receptor_nombre'] = receptor['nombre']
data['receptor_rfc'] = receptor['rfc']
data = {k.lower(): v for k, v in data.items()}
return data
def make_info_mail(data, fields):
return data.format(**fields).replace('\n', '<br/>')
def send_mail(data):
msg = ''
server = SendMail(data['server'])
is_connect = server.is_connect
if is_connect:
msg = server.send(data['options'])
else:
msg = server.error
server.close()
return {'ok': is_connect, 'msg': msg}

View File

@ -13,7 +13,7 @@ from middleware import (
)
from models.db import StorageEngine
from controllers.main import (
AppLogin, AppLogout, AppAdmin, AppEmisor,
AppLogin, AppLogout, AppAdmin, AppEmisor, AppConfig,
AppMain, AppValues, AppPartners, AppProducts, AppInvoices, AppFolios,
AppDocumentos
)
@ -38,6 +38,7 @@ api.add_route('/emisor', AppEmisor(db))
api.add_route('/folios', AppFolios(db))
api.add_route('/main', AppMain(db))
api.add_route('/values/{table}', AppValues(db))
api.add_route('/config', AppConfig(db))
api.add_route('/doc/{type_doc}/{id_doc}', AppDocumentos(db))
api.add_route('/partners', AppPartners(db))
api.add_route('/products', AppProducts(db))

View File

@ -14,12 +14,24 @@ class StorageEngine(object):
def get_values(self, table, values=None):
return getattr(self, '_get_{}'.format(table))(values)
def get_config(self, values):
return main.Configuracion.get_(values)
def add_config(self, values):
return main.Configuracion.add(values)
def add_cert(self, file_object):
return main.Certificado.add(file_object)
def validate_cert(self, values, session):
return main.Certificado.validate(values, session)
def validate_email(self, values):
return main.test_correo(values)
def send_email(self, values, session):
return main.Facturas.send(values['id'], session['rfc'])
def _get_cert(self, values):
return main.Certificado.get_data()

View File

@ -59,9 +59,34 @@ def desconectar():
class Configuracion(BaseModel):
clave = TextField()
clave = TextField(unique=True)
valor = TextField(default='')
@classmethod
def get_(cls, keys):
if keys['fields'] == 'correo':
fields = ('correo_servidor', 'correo_puerto', 'correo_ssl',
'correo_usuario', 'correo_contra', 'correo_copia',
'correo_asunto', 'correo_mensaje', 'correo_directo')
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: r.valor for r in data}
return values
@classmethod
def add(cls, values):
try:
for k, v in values.items():
obj, created = Configuracion.get_or_create(clave=k)
obj.valor = v
obj.save()
return {'ok': True}
except Exception as e:
log.error(str(e))
return {'ok': False, 'msg': str(e)}
class Meta:
order_by = ('clave',)
indexes = (
@ -1059,6 +1084,47 @@ class Facturas(BaseModel):
return file_zip, name_zip
@classmethod
def send(cls, id, rfc):
values = Configuracion.get_({'fields': 'correo'})
#~ print (server)
obj = Facturas.get(Facturas.id==id)
if obj.uuid is None:
msg = 'La factura no esta timbrada'
return {'ok': False, 'msg': msg}
if not obj.cliente.correo_facturas:
msg = 'El cliente no tiene configurado el correo para facturas'
return {'ok': False, 'msg': msg}
files = (cls.get_zip(id, rfc),)
fields = util.make_fields(obj.xml)
server = {
'servidor': values['correo_servidor'],
'puerto': values['correo_puerto'],
'ssl': bool(int(values['correo_ssl'])),
'usuario': values['correo_usuario'],
'contra': values['correo_contra'],
}
options = {
'para': obj.cliente.correo_facturas,
'copia': values['correo_copia'],
'asunto': util.make_info_mail(values['correo_asunto'], fields),
'mensaje': util.make_info_mail(values['correo_mensaje'], fields),
'files': files,
}
data= {
'server': server,
'options': options,
}
result = util.send_mail(data)
if not result['ok'] or result['msg']:
return {'ok': False, 'msg': result['msg']}
msg = 'Factura enviada correctamente'
return {'ok': True, 'msg': msg}
@classmethod
def get_(cls, values):
rows = tuple(Facturas
@ -1450,6 +1516,28 @@ def get_sat_key(key):
return util.get_sat_key('products', key)
def test_correo(values):
server = {
'servidor': values['correo_servidor'],
'puerto': values['correo_puerto'],
'ssl': bool(values['correo_ssl'].replace('0', '')),
'usuario': values['correo_usuario'],
'contra': values['correo_contra'],
}
options = {
'para': values['correo_usuario'],
'copia': values['correo_copia'],
'asunto': values['correo_asunto'],
'mensaje': values['correo_mensaje'].replace('\n', '<br/>'),
'files': [],
}
data= {
'server': server,
'options': options,
}
return util.send_mail(data)
def _init_values():
data = (
{'key': 'version', 'value': VERSION},

View File

@ -16,6 +16,8 @@ var controllers = {
$$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
$$('cmd_agregar_serie').attachEvent('onItemClick', cmd_agregar_serie_click)
$$('grid_folios').attachEvent('onItemClick', grid_folios_click)
$$('cmd_probar_correo').attachEvent('onItemClick', cmd_probar_correo_click)
$$('cmd_guardar_correo').attachEvent('onItemClick', cmd_guardar_correo_click)
}
}
@ -175,6 +177,24 @@ function get_table_folios(){
}
function get_config_correo(){
var form = $$('form_correo')
var fields = form.getValues()
webix.ajax().get('/config', {'fields': 'correo'}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
form.setValues(values)
}
})
}
function multi_admin_change(prevID, nextID){
//~ webix.message(nextID)
if(nextID == 'app_emisor'){
@ -188,6 +208,11 @@ function multi_admin_change(prevID, nextID){
get_table_folios()
return
}
if(nextID == 'app_correo'){
get_config_correo()
return
}
}
@ -442,10 +467,120 @@ function grid_folios_click(id, e, node){
msg_error(msg)
}
})
}
}
})
}
function validar_correo(values){
if(!values.correo_servidor.trim()){
msg = 'El servidor de salida no puede estar vacío'
msg_error(msg)
return false
}
if(!values.correo_puerto){
msg = 'El puerto no puede ser cero'
msg_error(msg)
return false
}
if(!values.correo_usuario.trim()){
msg = 'El nombre de usuario no puede estar vacío'
msg_error(msg)
return false
}
if(!values.correo_contra.trim()){
msg = 'La contraseña no puede estar vacía'
msg_error(msg)
return false
}
if(!values.correo_asunto.trim()){
msg = 'El asunto del correo no puede estar vacío'
msg_error(msg)
return false
}
if(!values.correo_mensaje.trim()){
msg = 'El mensaje del correo no puede estar vacío'
msg_error(msg)
return false
}
return true
}
function cmd_probar_correo_click(){
var form = $$('form_correo')
var values = form.getValues()
if(!validar_correo(values)){
return
}
webix.ajax().sync().post('/values/correo', values, {
error: function(text, data, xhr) {
msg = 'Error al probar el correo'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
msg = 'Correo de prueba enviado correctamente. Ya puedes \
guardar esta configuración'
msg_sucess(msg)
}else{
msg_error(values.msg)
}
}
})
}
function save_config_mail(values){
webix.ajax().sync().post('/config', values, {
error: function(text, data, xhr) {
msg = 'Error al guardar la configuración'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json();
if (values.ok){
msg = 'Configuración guardada correctamente'
msg_sucess(msg)
}else{
msg_error(values.msg)
}
}
})
}
function cmd_guardar_correo_click(){
var form = $$('form_correo')
var values = form.getValues()
if(!validar_correo(values)){
return
}
msg = 'Asegurate de haber probado la configuración<BR><BR>\
¿Estás seguro de guardar estos datos?'
webix.confirm({
title: 'Configuración de correo',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
save_config_mail(values)
}
}
})
}

View File

@ -642,6 +642,41 @@ function cmd_invoice_timbrar_click(){
}
function enviar_correo(row){
if(!row.uuid){
msg_error('La factura no esta timbrada')
return
}
msg = '¿Estás seguro de enviar por correo esta factura?'
webix.confirm({
title: 'Enviar Factura',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
webix.ajax().post('/values/sendmail', {'id': row.id}, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
msg_sucess(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
}
})
}
function grid_invoices_click(id, e, node){
var row = this.getItem(id)
@ -652,7 +687,7 @@ function grid_invoices_click(id, e, node){
}else if(id.column == 'zip'){
location = '/doc/zip/' + row.id
}else if(id.column == 'email'){
show('Correo')
enviar_correo(row)
}
}

View File

@ -3,6 +3,7 @@ var menu_data = [
{id: 'app_home', icon: 'dashboard', value: 'Inicio'},
{id: 'app_emisor', icon: 'user-circle', value: 'Emisor'},
{id: 'app_folios', icon: 'sort-numeric-asc', value: 'Folios'},
{id: 'app_correo', icon: 'envelope-o', value: 'Correo'},
]
@ -205,6 +206,55 @@ var emisor_folios = [
]
var emisor_correo = [
{template: 'Servidor de Salida', type: 'section'},
{cols: [
{view: 'text', id: 'correo_servidor', name: 'correo_servidor',
label: 'Servidor SMTP: '},
{}]},
{cols: [
{view: 'counter', id: 'correo_puerto', name: 'correo_puerto',
label: 'Puerto: ', value: 26, step: 1},
{}]},
{cols: [
{view: 'checkbox', id: 'correo_ssl', name: 'correo_ssl',
label: 'Usar TLS/SSL: '},
{}]},
{cols: [
{view: 'text', id: 'correo_usuario', name: 'correo_usuario',
label: 'Usuario: '},
{}]},
{cols: [
{view: 'text', id: 'correo_contra', name: 'correo_contra',
label: 'Contraseña: ', type: 'password'},
{}]},
{cols: [
{view: 'text', id: 'correo_copia', name: 'correo_copia',
label: 'Con copia a: '}
]},
{cols: [
{view: 'text', id: 'correo_asunto', name: 'correo_asunto',
label: 'Asunto del correo: '}
]},
{cols: [
{view: 'textarea', id: 'correo_mensaje', name: 'correo_mensaje',
label: 'Mensaje del correo: ', height: 200}
]},
{cols: [
{view: 'checkbox', id: 'correo_directo', name: 'correo_directo',
label: 'Enviar directamente: '},
{}]},
{minHeight: 25},
{cols: [{},
{view: 'button', id: 'cmd_probar_correo', label: 'Probar Configuración',
autowidth: true, type: 'form'},
{maxWidth: 100},
{view: 'button', id: 'cmd_guardar_correo', label: 'Guardar Configuración',
autowidth: true, type: 'form'},
{}]}
]
var controls_folios = [
{
view: 'tabview',
@ -219,6 +269,20 @@ var controls_folios = [
]
var controls_correo = [
{
view: 'tabview',
id: 'tab_correo',
tabbar: {options: ['Correo Electrónico']},
animate: true,
cells: [
{id: 'Correo Electrónico', rows: emisor_correo},
{},
]
}
]
var form_folios = {
type: 'space',
cols: [{
@ -239,6 +303,22 @@ var form_folios = {
}
var form_correo = {
type: 'space',
cols: [{
view: 'form',
id: 'form_correo',
complexData: true,
elements: controls_correo,
elementsConfig: {
labelWidth: 150,
labelAlign: 'right'
},
autoheight: true
}]
}
var app_emisor = {
id: 'app_emisor',
rows:[
@ -267,6 +347,17 @@ var app_folios = {
}
var app_correo = {
id: 'app_correo',
rows:[
{view: 'template', id: 'th_correo', type: 'header',
template: 'Configuración de correo'},
form_correo,
{},
]
}
var multi_admin = {
id: 'multi_admin',
animate: true,
@ -278,6 +369,7 @@ var multi_admin = {
},
app_emisor,
app_folios,
app_correo,
]
}