From 7864e236db745066e1ef7aca6c99700c3e0d6bac Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 10 Jan 2019 23:37:37 -0600 Subject: [PATCH] PDF a partir de JSON --- .gitignore | 2 + source/app/controllers/util.py | 48 +++++++++++- source/app/main_debug.ini | 2 +- source/app/models/main.py | 55 +++++++++++--- source/app/settings.py | 1 + source/static/css/invoice.css | 46 ++++++------ source/static/js/controller/admin.js | 106 +++++++++++++++++++++++++++ source/static/js/ui/admin.js | 23 +++++- 8 files changed, 248 insertions(+), 35 deletions(-) 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}, + {}]}, {}]