diff --git a/.gitignore b/.gitignore
index ba31efa..dd92c9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ source/media
docs/bk/
source/docs/
site/
+vedev/
cache/
credenciales.conf
*.sqlite
@@ -24,4 +25,5 @@ credenciales.conf
*.service
*.orig
rfc.db
+Dockerfile
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index 885f6d6..dd38ed8 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -31,6 +31,7 @@ import sqlite3
import socket
import subprocess
import tempfile
+import textwrap
import threading
import time
import unicodedata
@@ -43,6 +44,10 @@ from pathlib import Path
from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString
+# ~ import pdfkit
+# ~ from weasyprint import HTML, CSS
+# ~ from weasyprint.fonts import FontConfiguration
+
try:
import uno
from com.sun.star.beans import PropertyValue
@@ -1556,7 +1561,7 @@ class LIBO(object):
return rows, ''
-def to_pdf(data, emisor_rfc, ods=False):
+def to_pdf(data, emisor_rfc, ods=False, pdf_from='1'):
rfc = data['emisor']['rfc']
if DEBUG:
rfc = emisor_rfc
@@ -1569,6 +1574,9 @@ def to_pdf(data, emisor_rfc, ods=False):
version = '1.0'
pagos = 'pagos_'
+ if pdf_from == '2':
+ return to_pdf_from_json(rfc, version, data)
+
if APP_LIBO:
app = LIBO()
if app.is_running:
@@ -1580,6 +1588,10 @@ def to_pdf(data, emisor_rfc, ods=False):
if path:
return app.pdf(path, data, ods)
+ return to_pdf_from_json(rfc, version, data)
+
+
+def to_pdf_from_json(rfc, version, data):
name = '{}_{}.json'.format(rfc, version)
custom_styles = get_custom_styles(name)
@@ -1609,9 +1621,38 @@ def to_html(data):
template = template_lookup.get_template('plantilla_factura.html')
data['rfc'] = 'invoice'
+ # ~ data['cfdi_sello'] = textwrap.fill(data['cfdi_sello'], 50)
+ # ~ data['timbre_sellosat'] = textwrap.fill(data['timbre_sellosat'], 110)
+ # ~ data['timbre_cadenaoriginal'] = textwrap.fill(data['timbre_cadenaoriginal'], 140)
+
+ # ~ data['cfdi_sello'] = 'X'*100 + '
' + 'X'*100 + '
' + 'X'*100
+ # ~ data['timbre_sellosat'] = 'X'*100 + '
' + 'X'*100 + '
' + 'X'*100
+
return template.render(**data)
+def html_to_pdf(data):
+ path_pdf = '/home/mau/test.pdf'
+ css = '/home/mau/projects/empresa-libre/source/static/css/invoice.css'
+
+ # ~ font_config = FontConfiguration()
+ # ~ html = HTML(string=data)
+ # ~ css = CSS(filename=path_css)
+ # ~ html.write_pdf(path_pdf, stylesheets=[css], font_config=font_config)
+ options = {
+ 'page-size': 'Letter',
+ 'margin-top': '0.50in',
+ 'margin-right': '0.50in',
+ 'margin-bottom': '0.50in',
+ 'margin-left': '0.50in',
+ 'encoding': "UTF-8",
+ }
+
+ pdfkit.from_string(data.decode(), path_pdf, options=options, css=css)
+
+ return
+
+
def import_employees(rfc):
name = '{}_employees.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'tmp', name)
@@ -2146,6 +2187,7 @@ def upload_file(rfc, opt, file_obj):
'txt_plantilla_factura_33': EXT['ODS'],
'txt_plantilla_factura_html': EXT['HTML'],
'txt_plantilla_factura_css': EXT['CSS'],
+ 'txt_plantilla_factura_json': EXT['JSON'],
}
if opt in EXTENSIONS:
@@ -2160,7 +2202,8 @@ def upload_file(rfc, opt, file_obj):
'txt_plantilla_factura_32': f"{rfc}_3.2.ods",
'txt_plantilla_factura_33': f"{rfc}_3.3.ods",
'txt_plantilla_factura_html': f"{rfc}_3.3.html",
- 'txt_plantilla_factura_css': f"{rfc}.css"
+ 'txt_plantilla_factura_css': f"{rfc}.css",
+ 'txt_plantilla_factura_json': f"{rfc}_3.3.json",
}
name = NAMES[opt]
paths = {
@@ -2168,6 +2211,7 @@ def upload_file(rfc, opt, file_obj):
'txt_plantilla_factura_33': _join(PATHS['USER'], name),
'txt_plantilla_factura_html': _join(PATHS['USER'], name),
'txt_plantilla_factura_css': _join(PATHS['CSS'], name),
+ 'txt_plantilla_factura_json': _join(PATHS['USER'], name),
}
if save_file(paths[opt], file_obj.file.read()):
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
diff --git a/source/app/main_debug.ini b/source/app/main_debug.ini
index e6090c5..a9e0a87 100644
--- a/source/app/main_debug.ini
+++ b/source/app/main_debug.ini
@@ -8,4 +8,4 @@ threads = 4
py-autoreload = 1
thunder-lock = true
static-map = /static=../static
-http-timeout = 300
\ No newline at end of file
+http-timeout = 300
diff --git a/source/app/models/main.py b/source/app/models/main.py
index cd3b7b7..c42f14a 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -314,9 +314,38 @@ class Configuracion(BaseModel):
clave = TextField(unique=True)
valor = TextField(default='')
+ class Meta:
+ order_by = ('clave',)
+ indexes = (
+ (('clave', 'valor'), True),
+ )
+
def __str__(self):
return '{} = {}'.format(self.clave, self.valor)
+ def _clean_email(self, values={}):
+ fields = (
+ 'correo_asunto',
+ 'correo_confirmacion',
+ 'correo_contra',
+ 'correo_directo',
+ 'correo_mensaje',
+ 'correo_puerto',
+ 'correo_servidor',
+ 'correo_ssl',
+ 'correo_usuario',
+ )
+
+ q = (Configuracion
+ .update(**{'valor': ''})
+ .where(Configuracion.clave.in_(fields))
+ )
+ result = q.execute()
+ msg = 'Configuración guardada correctamente'
+ if not result:
+ msg = 'No se pudo guardar la configuración'
+ return {'ok': result, 'msg': msg}
+
@classmethod
def get_bool(cls, key):
data = (Configuracion
@@ -473,10 +502,12 @@ class Configuracion(BaseModel):
'txt_plantilla_factura_33',
'txt_plantilla_factura_html',
'txt_plantilla_factura_css',
+ 'txt_plantilla_factura_json',
'txt_plantilla_nomina1233',
'txt_plantilla_pagos10',
'txt_plantilla_ticket',
'txt_plantilla_donataria',
+ 'make_pdf_from',
)
data = (Configuracion
.select()
@@ -500,6 +531,10 @@ class Configuracion(BaseModel):
@classmethod
def add(cls, values):
+ opt = values.pop('opt', '')
+ if opt:
+ return getattr(cls, f'_{opt}')(cls, values)
+
# ~ print (values)
try:
for k, v in values.items():
@@ -517,12 +552,6 @@ class Configuracion(BaseModel):
q = Configuracion.delete().where(Configuracion.clave==key)
return bool(q.execute())
- class Meta:
- order_by = ('clave',)
- indexes = (
- (('clave', 'valor'), True),
- )
-
class Tags(BaseModel):
tag = TextField(index=True, unique=True)
@@ -3704,9 +3733,10 @@ class Facturas(BaseModel):
if obj.uuid is None:
return b'', name
+ pdf_from = Configuracion.get_('make_pdf_from') or '1'
values = cls._get_not_in_xml(cls, obj, emisor)
data = util.get_data_from_xml(obj, values)
- doc = util.to_pdf(data, emisor.rfc)
+ doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from)
if sync:
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
@@ -3808,9 +3838,13 @@ class Facturas(BaseModel):
data['totales'].append((
'Total', util.format_currency(data['cfdi_total'], currency)))
- data['timbre_cadenaoriginal'] = f"""||{data['timbre_version']}|
- {data['timbre_uuid']}|{data['timbre_fechatimbrado']}|
- {data['timbre_sellocfd']}|{data['timbre_nocertificadosat']}||"""
+ data['timbre_cadenaoriginal'] = (
+ f"||{data['timbre_version']}|"
+ f"{data['timbre_uuid']}|"
+ f"{data['timbre_fechatimbrado']}|"
+ f"{data['timbre_sellocfd']}|"
+ f"{data['timbre_nocertificadosat']}||"
+ )
qr_data = (
f"https://verificacfdi.facturaelectronica.sat.gob.mx/"
@@ -3837,6 +3871,7 @@ class Facturas(BaseModel):
data = cls._get_others_values(cls, obj, emisor)
doc = util.to_html(data)
+ # ~ util.html_to_pdf(doc)
return doc, name
diff --git a/source/app/settings.py b/source/app/settings.py
index 76b9086..5db1316 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -200,6 +200,7 @@ EXT = {
'HTML': 'html',
'ODS': 'ods',
'PNG': 'png',
+ 'JSON': 'json',
}
MXN = 'MXN'
PATHS = {
diff --git a/source/static/css/invoice.css b/source/static/css/invoice.css
index e3b4384..3eaa732 100644
--- a/source/static/css/invoice.css
+++ b/source/static/css/invoice.css
@@ -1,3 +1,18 @@
+@page{
+ size: Letter;
+ margin: 0.5cm;
+ background-color: white !important;
+}
+
+
+@media print {
+ thead {display: table-header-group;}
+ body {
+ background-color: #fff;
+ }
+}
+
+
.emisor-cintilla{
background-color: #975759;
color: #fff;
@@ -115,21 +130,10 @@ table.subtotal td{
}
-@page{
- size: 8.5in 11in;
- margin: 1cm ;
- background-color: white !important;
-}
-
-@media print {
- thead {display: table-header-group;}
- body {
- background-color: #fff;
- }
-}
-
body {
+/*
font-family: 'Avenir Next';
+*/
}
#plantilla {
@@ -138,8 +142,8 @@ body {
background: #fff;
display: block;
margin: 0 auto;
- width: 21.5cm;
- height: 27.9cm;
+ width: 19.5cm;
+ height: 25.9cm;
position: relative;
}
@@ -241,7 +245,7 @@ header .receptor{
padding-bottom: 5px;
padding-left: 45px;
padding-top: 5px;
- width: 65%;
+ width: 60%;
}
.receptor .nombre{
@@ -316,7 +320,7 @@ header .cfdi{
.cfdi .folio-fiscal{
color: #000;
- font-size: 10px;
+ font-size: 9px;
font-weight: bold;
line-height: 15px;
text-align: center;
@@ -463,7 +467,7 @@ table.subtotal td{
border: 1px solid #000;
height: auto;
margin-top: 10px;
- margin-right: 1%;
+ margin-right: 1px;
width: 18%;
}
@@ -474,13 +478,13 @@ table.subtotal td{
font-weight: bold;
line-height: 15px;
width: 79%;
- margin-left: 2px;
- margin-right: 2px;
+ margin-left: 5px;
+ margin-right: 5px;
word-break: break-all;
}
.sello .cadenas-sello .cadena{
- margin: 10px 0;
+ margin: 5px 0;
}
.sello .cadenas-sello .cadena div{
diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js
index e17be00..7610f18 100644
--- a/source/static/js/controller/admin.js
+++ b/source/static/js/controller/admin.js
@@ -37,6 +37,7 @@ var controllers = {
$$('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)
+ $$('cmd_clean_email').attachEvent('onItemClick', cmd_clean_email_click)
$$('emisor_logo').attachEvent('onItemClick', emisor_logo_click)
$$('cmd_emisor_agregar_cuenta').attachEvent('onItemClick', cmd_emisor_agregar_cuenta_click)
$$('cmd_emisor_eliminar_cuenta').attachEvent('onItemClick', cmd_emisor_eliminar_cuenta_click)
@@ -70,11 +71,14 @@ var controllers = {
$$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click)
$$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click)
$$('txt_plantilla_factura_html').attachEvent('onItemClick', txt_plantilla_factura_html_click)
+ $$('txt_plantilla_factura_json').attachEvent('onItemClick', txt_plantilla_factura_json_click)
$$('txt_plantilla_factura_css').attachEvent('onItemClick', txt_plantilla_factura_css_click)
$$('txt_plantilla_ticket').attachEvent('onItemClick', txt_plantilla_ticket_click)
$$('txt_plantilla_donataria').attachEvent('onItemClick', txt_plantilla_donataria_click)
$$('txt_plantilla_nomina1233').attachEvent('onItemClick', txt_plantilla_nomina1233_click)
$$('txt_plantilla_pagos10').attachEvent('onItemClick', txt_plantilla_pagos10_click)
+ $$('make_pdf_from').attachEvent('onChange', opt_make_pdf_from_on_change)
+
//~ Partners
$$('chk_config_change_balance_partner').attachEvent('onItemClick', chk_config_item_click)
@@ -841,6 +845,46 @@ function cmd_guardar_correo_click(){
}
+function clean_config_mail(){
+ var form = $$('form_correo')
+
+ webix.ajax().sync().post('/config', {'opt': 'clean_email'}, {
+ 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){
+ form.setValues({})
+ msg_ok(values.msg)
+ }else{
+ msg_error(values.msg)
+ }
+ }
+ })
+}
+
+
+function cmd_clean_email_click(){
+ msg = '¿Estás seguro de limpiar todos los datos?
'
+ msg += 'ESTA ACCIÓN NO SE PUEDE DESHACER'
+
+ webix.confirm({
+ title: 'Configuración de correo',
+ ok: 'Si',
+ cancel: 'No',
+ type: 'confirm-error',
+ text: msg,
+ callback:function(result){
+ if(result){
+ clean_config_mail()
+ }
+ }
+ })
+}
+
+
function emisor_logo_click(id, e){
var w = webix.ui({
@@ -1061,6 +1105,47 @@ function txt_plantilla_factura_css_click(e){
}
+function txt_plantilla_factura_json_click(e){
+
+ var body_elements = [
+ {cols: [
+ {width: 100},
+ {view: 'uploader', id: 'up_template', autosend: true,
+ link: 'lst_files', value: 'Seleccionar archivo',
+ upload: '/files/txt_plantilla_factura_json', width: 200},
+ {width: 100}]},
+ {view: 'list', id: 'lst_files', type: 'uploader', autoheight: true,
+ borderless: true},
+ {},
+ {cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true,
+ click:("$$('win_template').close();")}, {}]}
+ ]
+
+ var w = webix.ui({
+ view: 'window',
+ id: 'win_template',
+ modal: true,
+ position: 'center',
+ head: 'Subir Plantilla 3.3 JSON',
+ body: {
+ view: 'form',
+ elements: body_elements,
+ }
+ })
+
+ w.show()
+
+ $$('up_template').attachEvent('onUploadComplete', function(response){
+ if(response.ok){
+ $$('txt_plantilla_factura_json').setValue(response.name)
+ msg_ok('Plantilla cargada correctamente')
+ }else{
+ msg_error(response.name)
+ }
+ })
+}
+
+
function txt_plantilla_donataria_click(e){
var body_elements = [
@@ -2389,3 +2474,24 @@ function grid_admin_bancos_before_edit_stop(state, editor){
admin_sat_bank_update_rfc(row.id, state.value)
}
}
+
+
+function opt_make_pdf_from_on_change(new_value, old_value){
+ var values = {'make_pdf_from': new_value}
+
+ 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_error(values.msg)
+ }
+ }
+ })
+
+}
+
+
diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js
index 29f05d5..f04eebc 100644
--- a/source/static/js/ui/admin.js
+++ b/source/static/js/ui/admin.js
@@ -511,9 +511,12 @@ var emisor_correo = [
{cols: [{},
{view: 'button', id: 'cmd_probar_correo', label: 'Probar Configuración',
autowidth: true, type: 'form'},
- {maxWidth: 100},
+ {maxWidth: 50},
{view: 'button', id: 'cmd_guardar_correo', label: 'Guardar Configuración',
autowidth: true, type: 'form'},
+ {maxWidth: 50},
+ {view: 'button', id: 'cmd_clean_email', label: 'Limpiar Configuración',
+ autowidth: true, type: 'form'},
{}]}
]
@@ -579,6 +582,12 @@ var form_correo = {
}
+var type_make_pdf = [
+ {id: 1, value: 'ODS'},
+ {id: 2, value: 'JSON'},
+]
+
+
var options_templates = [
{maxHeight: 15},
{cols: [{maxWidth: 20},
@@ -596,6 +605,14 @@ var options_templates = [
{view: 'search', id: 'txt_plantilla_factura_css', name: 'plantilla_factura_css',
label: 'Archivo de estilos (CSS): ', labelPosition: 'top',
icon: 'file'}, {maxWidth: 20} ]},
+
+ {maxHeight: 20},
+ {cols: [{maxWidth: 20},
+ {view: 'search', id: 'txt_plantilla_factura_json', name: 'plantilla_factura_json',
+ label: 'Plantilla Factura v3.3 (JSON): ', labelPosition: 'top',
+ icon: 'file'}, {maxWidth: 25},
+ {}, {maxWidth: 20} ]},
+
{maxHeight: 20},
{cols: [{maxWidth: 20},
{view: 'search', id: 'txt_plantilla_nomina1233', name: 'plantilla_nomina1233',
@@ -616,6 +633,10 @@ var options_templates = [
icon: 'file'},
{}]},
{maxHeight: 20},
+ {cols: [{maxWidth: 20},
+ {view: 'radio', id: 'make_pdf_from', name: 'make_pdf_from', labelWidth: 150,
+ label: 'Generar PDF desde: ', value: 1, options: type_make_pdf},
+ {}]},
{}]