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/CHANGELOG.md b/CHANGELOG.md index a71e4f9..aec701c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v 1.26.0 [15-ene-2019] +---------------------- + - Mejora: Generar PDF desde plantilla JSON + + v 1.25.0 [07-nov-2018] ---------------------- - Fix: Al importar días de pago en nómina diff --git a/VERSION b/VERSION index ad21919..5ff8c4f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.25.0 +1.26.0 diff --git a/source/app/controllers/helper.py b/source/app/controllers/helper.py index efc7274..5b1aa8f 100644 --- a/source/app/controllers/helper.py +++ b/source/app/controllers/helper.py @@ -29,6 +29,9 @@ from reportlab.platypus import Paragraph, Table, TableStyle, Spacer from reportlab.pdfgen import canvas +CANCEL = False + + #~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37 class CaseInsensitiveDict(collections.MutableMapping): """A case-insensitive ``dict``-like object. @@ -340,6 +343,7 @@ class SendMail(object): class NumberedCanvas(canvas.Canvas): X = 20.59 * cm + XC = 21.6 * cm / 2 + 1.5 * cm Y = 1.5 * cm def __init__(self, *args, **kwargs): @@ -357,19 +361,33 @@ class NumberedCanvas(canvas.Canvas): for state in self._saved_page_states: self.__dict__.update(state) self.draw_page_number(page_count) + self.draw_cancel() canvas.Canvas.showPage(self) canvas.Canvas.save(self) return def draw_page_number(self, page_count): self.setFont('Helvetica', 8) - self.setFillColor(colors.darkred) - text = 'Página {} de {}'.format(self._pageNumber, page_count) + # ~ self.setFillColor(colors.darkred) + text = f'Página {self._pageNumber} de {page_count}' self.drawRightString(self.X, self.Y, text) - text = 'Factura elaborada con software libre: www.empresalibre.net' + + text = 'Este documento es una representación impresa de un CFDI' + self.drawCentredString(self.XC, self.Y, text) + + text = 'Factura elaborada con software libre' self.drawString(1.5 * cm, 1.5 * cm, text) return + def draw_cancel(self): + global CANCEL + if CANCEL: + self.rotate(45) + self.setFont('Helvetica', 100) + self.setFillColor(colors.red) + text = 'Cancelada' + self.drawCentredString(20 * cm, 3 * cm, text) + return class TemplateInvoice(BaseDocTemplate): @@ -418,6 +436,9 @@ class TemplateInvoice(BaseDocTemplate): def _emisor(self, styles, data): logo_path = data.pop('logo', '') logo_style = styles.pop('logo', {}) + logo_path2 = data.pop('logo2', '') + logo_style2 = styles.pop('logo2', {}) + sucursales = styles.pop('sucursales', {}) for k, v in styles.items(): self._set_text(styles[k], data.get(k, '')) @@ -428,6 +449,28 @@ class TemplateInvoice(BaseDocTemplate): for k in keys: rect[k] = rect[k] * cm self.canv.drawImage(logo_path, **rect) + + if logo_path2 and logo_style2: + rect = logo_style2['rectangulo'] + keys = ('x', 'y', 'width', 'height') + for k in keys: + rect[k] = rect[k] * cm + self.canv.drawImage(logo_path2, **rect) + + if sucursales: + for k, sucursal in sucursales.items(): + values = sucursal.pop('textos') + x = sucursal['rectangulo']['x'] + y = sucursal['rectangulo']['y'] + w = sucursal['rectangulo']['width'] + h = sucursal['rectangulo']['height'] + for v in values: + self._set_text(sucursal, v) + y -= h + sucursal['rectangulo']['x'] = x + sucursal['rectangulo']['y'] = y + sucursal['rectangulo']['width'] = w + sucursal['rectangulo']['height'] = h return def _receptor(self, styles, data): @@ -449,6 +492,7 @@ class TemplateInvoice(BaseDocTemplate): def _comprobante1(self, styles, data): title = styles.pop('titulo', {}) + self.canv.setTitle(f"Factura {data.get('seriefolio', '')}") for k, v in styles.items(): self._set_text(styles[k], data.get(k, '')) @@ -480,6 +524,12 @@ class TemplateInvoice(BaseDocTemplate): fields = ('valorunitario', 'importe') if field in fields: return self._currency(value) + + if field == 'descripcion': + html = '{}' + style_bt = getSampleStyleSheet()['BodyText'] + return Paragraph(html.format(value), style_bt) + return value def _conceptos(self, conceptos): @@ -515,8 +565,8 @@ class TemplateInvoice(BaseDocTemplate): ('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white), ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), ('FONTSIZE', (0, 0), (-1, -1), 8), - ('BACKGROUND', (1, 0), (-1, -1), colors.linen), - ('TEXTCOLOR', (1, 0), (-1, -1), colors.darkred), + ('BACKGROUND', (1, 0), (-1, -1), colors.lightgrey), + ('TEXTCOLOR', (1, 0), (-1, -1), colors.black), ('FACE', (1, 0), (-1, -1), 'Helvetica-Bold'), ] table = Table(rows, colWidths=widths, spaceBefore=0.25*cm) @@ -524,7 +574,7 @@ class TemplateInvoice(BaseDocTemplate): return table def _comprobante2(self, styles, data): - leyenda = styles.pop('leyenda', {}) + leyendas = styles.pop('leyendas', {}) ls = [] for k, v in styles.items(): @@ -542,7 +592,7 @@ class TemplateInvoice(BaseDocTemplate): style_bt = getSampleStyleSheet()['BodyText'] style_bt.leading = 8 html_t = '{}' - html = '{}' + html = '{}' msg = 'Cadena original del complemento de certificación digital del SAT' rows = [ (cbb, Paragraph(html_t.format('Sello Digital del CFDI'), style_bt)), @@ -558,13 +608,13 @@ class TemplateInvoice(BaseDocTemplate): ('FONTSIZE', (0, 0), (-1, -1), 6), ('SPAN', (0, 0), (0, -1)), ('FACE', (1, 0), (1, 0), 'Helvetica-Bold'), - ('BACKGROUND', (1, 1), (1, 1), colors.linen), + ('BACKGROUND', (1, 1), (1, 1), colors.lightgrey), ('TEXTCOLOR', (1, 1), (1, 1), colors.darkred), ('FACE', (1, 2), (1, 2), 'Helvetica-Bold'), - ('BACKGROUND', (1, 3), (1, 3), colors.linen), + ('BACKGROUND', (1, 3), (1, 3), colors.lightgrey), ('TEXTCOLOR', (1, 3), (1, 3), colors.darkred), ('FACE', (1, 4), (1, 4), 'Helvetica-Bold'), - ('BACKGROUND', (1, 5), (1, 5), colors.linen), + ('BACKGROUND', (1, 5), (1, 5), colors.lightgrey), ('TEXTCOLOR', (1, 5), (1, 5), colors.darkred), ('ALIGN', (0, 0), (0, 0), 'CENTER'), ('VALIGN', (0, 0), (0, 0), 'MIDDLE'), @@ -573,14 +623,14 @@ class TemplateInvoice(BaseDocTemplate): table.setStyle(TableStyle(table_styles)) ls.append(table) - if leyenda: - if 'spaceBefore' in leyenda['estilo']: - leyenda['estilo']['spaceBefore'] = \ - leyenda['estilo']['spaceBefore'] * cm - msg = 'Este documento es una representación impresa de un CFDI' - ps = ParagraphStyle(**leyenda['estilo']) - p = Paragraph(msg, ps) - ls.append(p) + if leyendas: + if 'spaceBefore' in leyendas['estilo']: + leyendas['estilo']['spaceBefore'] = \ + leyendas['estilo']['spaceBefore'] * cm + for t in leyendas['textos']: + ps = ParagraphStyle(**leyendas['estilo']) + p = Paragraph(t, ps) + ls.append(p) return ls @@ -596,8 +646,10 @@ class TemplateInvoice(BaseDocTemplate): return self._data @data.setter def data(self, values): - #~ print (values) + global CANCEL + # ~ print (values) self._data = values + CANCEL = self._data['cancelada'] rows = self._conceptos(self._data['conceptos']) widths = [2 * cm, 9 * cm, 1.5 * cm, 2 * cm, 2 * cm, 3 * cm] @@ -607,13 +659,13 @@ class TemplateInvoice(BaseDocTemplate): ('ALIGN', (0, 0), (-1, 0), 'CENTER'), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('FACE', (0, 0), (-1, 0), 'Helvetica-Bold'), - ('BACKGROUND', (0, 0), (-1, 0), colors.darkred), + ('BACKGROUND', (0, 0), (-1, 0), colors.grey), ('FONTSIZE', (0, 1), (-1, -1), 7), ('VALIGN', (0, 1), (-1, -1), 'TOP'), ('ALIGN', (0, 1), (0, -1), 'CENTER'), ('ALIGN', (2, 1), (2, -1), 'CENTER'), ('ALIGN', (3, 1), (5, -1), 'RIGHT'), - ('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.darkred), + ('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.grey), ('LINEBEFORE', (0, 1), (-1, -1), 0.05 * cm, colors.white), ] table_conceptos = Table(rows, colWidths=widths, repeatRows=1) @@ -623,7 +675,7 @@ class TemplateInvoice(BaseDocTemplate): comprobante = self._comprobante2( self._custom_styles['comprobante'], self.data['comprobante']) - self._rows = [Spacer(0, 6*cm), table_conceptos, totales] + comprobante + self._rows = [Spacer(0, 5.7 * cm), table_conceptos, totales] + comprobante def render(self): frame = Frame(self.leftMargin, self.bottomMargin, diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py index 353f7ad..5952af9 100644 --- a/source/app/controllers/main.py +++ b/source/app/controllers/main.py @@ -510,7 +510,7 @@ class AppDocumentos(object): session = req.env['beaker.session'] req.context['result'], file_name, content_type = \ self._db.get_doc(type_doc, id_doc, session['rfc']) - if not type_doc in ('pdf', 'pre', 'tpdf', 'pdfpago'): + if not type_doc in ('pdf', 'pre', 'tpdf', 'pdfpago', 'html'): resp.append_header('Content-Disposition', 'attachment; filename={}'.format(file_name)) resp.content_type = content_type diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 5338e94..673b7ea 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -16,9 +16,11 @@ # ~ You should have received a copy of the GNU General Public License # ~ along with this program. If not, see . +import base64 import datetime import getpass import hashlib +import io import json import locale import mimetypes @@ -29,6 +31,7 @@ import sqlite3 import socket import subprocess import tempfile +import textwrap import threading import time import unicodedata @@ -41,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 @@ -52,6 +59,11 @@ except ImportError: import pyqrcode from dateutil import parser +from lxml import etree + +import mako.runtime +from mako.exceptions import TopLevelLookupException +mako.runtime.UNDEFINED = '' from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \ SeaFileAPI, PrintTicket @@ -63,6 +75,14 @@ from settings import SEAFILE_SERVER, USAR_TOKEN, API, DECIMALES_TAX from .configpac import AUTH +# ~ v2 +from settings import ( + EXT, + MXN, + PATHS, +) + + def _call(args): return subprocess.check_output(args, shell=True).decode() @@ -645,7 +665,7 @@ class LIBO(object): self._ctx = None self._sm = None self._desktop = None - self._currency = 'MXN' + self._currency = MXN self._total_cantidades = 0 if self.is_running: ctx = uno.getComponentContext() @@ -1541,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 @@ -1554,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: @@ -1565,9 +1588,19 @@ 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): + rfc = rfc.lower() name = '{}_{}.json'.format(rfc, version) custom_styles = get_custom_styles(name) + path_logo = _join(PATHS['IMG'], f"{rfc}.png") + data['emisor']['logo'] = path_logo + path_logo = _join(PATHS['IMG'], f"{rfc}_2.png") + data['emisor']['logo2'] = path_logo + path = get_path_temp() pdf = TemplateInvoice(path) pdf.custom_styles = custom_styles @@ -1576,6 +1609,56 @@ def to_pdf(data, emisor_rfc, ods=False): return read_file(path) +def format_currency(value, currency, digits=2): + c = { + MXN: '$', + 'USD': '$', + 'EUR': '€', + } + s = c.get(currency, MXN) + return f'{s} {float(value):,.{digits}f}' + + +def to_html(data): + name = f"{data['rfc']}_{data['version']}.html" + try: + template = template_lookup.get_template(name) + except TopLevelLookupException: + 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) @@ -1621,15 +1704,20 @@ def get_dict(data): return CaseInsensitiveDict(data) -def to_letters(value, moneda): - return NumLet(value, moneda).letras +def to_letters(value, currency): + return NumLet(value, currency).letras -def get_qr(data): - path = get_path_temp('.qr') +def get_qr(data, p=True): qr = pyqrcode.create(data, mode='binary') - qr.png(path, scale=7) - return path + if p: + path = get_path_temp('.qr') + qr.png(path, scale=7) + return path + + buffer = io.BytesIO() + qr.png(buffer, scale=8) + return base64.b64encode(buffer.getvalue()).decode() def _get_relacionados(doc, version): @@ -1881,6 +1969,7 @@ def _timbre(doc, version, values): 'sello': '&fe={}'.format(data['sellocfd'][-8:]), } qr_data = '{url}{uuid}{emisor}{receptor}{total}{sello}'.format(**qr_data) + data['path_cbb'] = get_qr(qr_data) data['cadenaoriginal'] = CADENA.format(**data) return data @@ -2083,39 +2172,58 @@ def get_date(value, next_day=False): return d +class UpFile(object): + + + def __init__(self): + self._init_values() + + def _init_values(self): + + return + + def upload_file(rfc, opt, file_obj): - if opt == 'emisorlogo': - tmp = file_obj.filename.split('.') - name = '{}.{}'.format(rfc.lower(), tmp[-1].lower()) - path = _join(PATH_MEDIA, 'logos', name) - elif opt == 'txt_plantilla_factura_32': - tmp = file_obj.filename.split('.') - ext = tmp[-1].lower() - if ext != 'ods': - msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS' + rfc = rfc.lower() + tmp = file_obj.filename.split('.') + ext = tmp[-1].lower() + + EXTENSIONS = { + 'txt_plantilla_factura_32': EXT['ODS'], + '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: + if ext != EXTENSIONS[opt]: + msg = ( + f"Extensión de archivo incorrecta, " + f"selecciona un archivo {EXTENSIONS[opt].upper()}" + ) return {'status': 'server', 'name': msg, 'ok': False} - name = '{}_3.2.ods'.format(rfc.lower()) - path = _join(PATH_MEDIA, 'templates', name) - elif opt == 'txt_plantilla_factura_33': - tmp = file_obj.filename.split('.') - ext = tmp[-1].lower() - if ext != 'ods': - msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS' - return {'status': 'server', 'name': msg, 'ok': False} + NAMES = { + '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_json': f"{rfc}_3.3.json", + } + name = NAMES[opt] + paths = { + 'txt_plantilla_factura_32': _join(PATHS['USER'], name), + '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} + return {'status': 'error', 'ok': False} - name = '{}_3.3.ods'.format(rfc.lower()) - path = _join(PATH_MEDIA, 'templates', name) - elif opt == 'txt_plantilla_factura_33j': - tmp = file_obj.filename.split('.') - ext = tmp[-1].lower() - if ext != 'json': - msg = 'Extensión de archivo incorrecta, selecciona un archivo JSON' - return {'status': 'server', 'name': msg, 'ok': False} - - name = '{}_3.3.json'.format(rfc.lower()) - path = _join(PATH_MEDIA, 'templates', name) - elif opt == 'txt_plantilla_ticket': + if opt == 'txt_plantilla_ticket': tmp = file_obj.filename.split('.') ext = tmp[-1].lower() if ext != 'ods': @@ -3648,3 +3756,8 @@ def validate_rfc(value): return '' except: return msg + + +def parse_xml2(xml_str): + return etree.fromstring(xml_str.encode('utf-8')) + diff --git a/source/app/main.py b/source/app/main.py index ec12f0c..e7afd80 100644 --- a/source/app/main.py +++ b/source/app/main.py @@ -8,7 +8,6 @@ from middleware import ( AuthMiddleware, JSONTranslator, ConnectionMiddleware, - static, handle_404 ) from models.db import StorageEngine @@ -64,9 +63,6 @@ api.add_route('/satformapago', AppSATFormaPago(db)) api.add_route('/socioscb', AppSociosCuentasBanco(db)) -# ~ Activa si usas waitress y NO estas usando servidor web -# ~ api.add_sink(static, '/static') - session_options = { 'session.type': 'file', 'session.cookie_expires': 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/middleware.py b/source/app/middleware.py index bbe031b..9c873c1 100644 --- a/source/app/middleware.py +++ b/source/app/middleware.py @@ -3,7 +3,7 @@ import falcon from controllers import util from models import main -from settings import MV, PATH_STATIC +from settings import MV def handle_404(req, resp): @@ -20,14 +20,14 @@ def get_template(req, resp, resource): resp.body = util.get_template(resource.template, data) -def static(req, res): - path = PATH_STATIC + req.path - if util.is_file(path): - res.content_type = util.get_mimetype(path) - res.stream, res.stream_len = util.get_stream(path) - res.status = falcon.HTTP_200 - else: - res.status = falcon.HTTP_404 +# ~ def static(req, res): + # ~ path = PATH_STATIC + req.path + # ~ if util.is_file(path): + # ~ res.content_type = util.get_mimetype(path) + # ~ res.stream, res.stream_len = util.get_stream(path) + # ~ res.status = falcon.HTTP_200 + # ~ else: + # ~ res.status = falcon.HTTP_404 class AuthMiddleware(object): diff --git a/source/app/models/main.py b/source/app/models/main.py index 71eb26f..db985fe 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -37,6 +37,12 @@ from settings import log, DEBUG, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \ DEFAULT_CFDIPAY, CURRENCY_MN +from settings import ( + EXT, + MXN, + PATHS, + VALUES_PDF, +) FORMAT = '{0:.2f}' FORMAT3 = '{0:.3f}' @@ -97,9 +103,11 @@ def upload_file(rfc, opt, file_obj): else: return Facturas.import_cfdi(xml, sxml) + if opt == 'emisorlogo': + return Emisor.save_logo(file_obj) + result = util.upload_file(rfc, opt, file_obj) if result['ok']: - names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods') if not opt in names: Configuracion.add({opt: file_obj.filename}) @@ -204,12 +212,15 @@ def get_doc(type_doc, id, rfc): 'ods': 'application/octet-stream', 'zip': 'application/octet-stream', 'nomlog': 'application/txt', + 'html': 'text/html', } content_type = types.get(type_doc, 'application/pdf') if type_doc == 'xml': data, file_name = Facturas.get_xml(id) elif type_doc == 'pdf': data, file_name = Facturas.get_pdf(id, rfc) + elif type_doc == 'html': + data, file_name = Facturas.get_html(id) elif type_doc == 'ods': data, file_name = Facturas.get_ods(id, rfc) elif type_doc == 'zip': @@ -303,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 @@ -460,11 +500,14 @@ class Configuracion(BaseModel): fields = ( 'txt_plantilla_factura_32', 'txt_plantilla_factura_33', - 'txt_plantilla_factura_33j', + '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() @@ -488,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(): @@ -505,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) @@ -762,6 +803,10 @@ class SATRegimenes(BaseModel): def __str__(self): return '{} ({})'.format(self.name, self.key) + @classmethod + def get_by_key(cls, key): + return SATRegimenes.get(SATRegimenes.key==key) + @classmethod def get_(cls, ids): if isinstance(ids, int): @@ -814,12 +859,30 @@ class Emisor(BaseModel): registro_patronal = TextField(default='') regimenes = ManyToManyField(SATRegimenes, related_name='emisores') + class Meta: + order_by = ('nombre',) + def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) - class Meta: - order_by = ('nombre',) + @classmethod + def save_logo(cls, file_obj): + result = {'status': 'error', 'ok': False} + name = file_obj.filename.split('.') + if name[-1].lower() != EXT['PNG']: + return result + + emisor = Emisor.select()[0] + rfc = emisor.rfc.lower() + name = f'{rfc}.png' + path = util._join(PATHS['IMG'], name) + if util.save_file(path, file_obj.file.read()): + emisor.logo = file_obj.filename + emisor.save() + result = {'status': 'server', 'name': file_obj.filename, 'ok': True} + + return result @classmethod def get_(cls, rfc): @@ -847,6 +910,7 @@ class Emisor(BaseModel): 'emisor_telefono': obj.telefono, 'emisor_correo': obj.correo, 'emisor_web': obj.web, + 'emisor_logo': obj.logo, 'es_escuela': obj.es_escuela, 'es_ong': obj.es_ong, 'ong_autorizacion': obj.autorizacion, @@ -1366,6 +1430,10 @@ class SATMonedas(BaseModel): def __str__(self): return 'Moneda: ({}) {}'.format(self.key, self.name) + @classmethod + def get_by_key(cls, key): + return SATMonedas.get(SATMonedas.key==key) + @classmethod def get_multi_currency(cls): count_currencies = len(cls.get_activos()) @@ -1946,6 +2014,10 @@ class SATUsoCfdi(BaseModel): def __str__(self): return 'Uso del CFDI: {} ({})'.format(self.name, self.key) + @classmethod + def get_by_key(cls, key): + return SATUsoCfdi.get(SATUsoCfdi.key==key) + @classmethod def actualizar(self, values): id = int(values['id']) @@ -3647,6 +3719,7 @@ class Facturas(BaseModel): values['receptor'] = {} for k, v in receptor.items(): values['receptor'][k] = v + return values @classmethod @@ -3661,9 +3734,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('-', '/') @@ -3671,6 +3745,137 @@ class Facturas(BaseModel): return doc, name + def _get_concepto(self, node, currency): + d = util.get_dict(node.attrib) + concepto = { + 'clave': f"{d['noidentificacion']}
({d['claveprodserv']})", + 'descripcion': self._get_description(self, node, d), + 'unidad': f"{d['unidad']}
({d['claveunidad']})", + 'cantidad': d['cantidad'], + 'valorunitario': util.format_currency(d['valorunitario'], currency), + 'importe': util.format_currency(d['importe'], currency), + } + return concepto + + def _get_description(self, node, data): + return data['descripcion'] + + def _get_tax(self, node, data, currency): + type_tax = 'Traslado' + if 'Retenciones' in node.tag: + type_tax = 'Retención' + + for n in node: + d = util.get_dict(n.attrib) + name = VALUES_PDF['TAX'].get(d['impuesto']) + tasa = f"{float(d['tasaocuota']):.2f}" + title = f"{type_tax} {name} {tasa}" + importe = util.format_currency(d['importe'], currency) + data['totales'].append((title, importe)) + return + + def _get_others_values(self, invoice, emisor): + data = { + 'rfc': emisor.rfc.lower(), + 'version': invoice.version, + 'cancelada': VALUES_PDF['CANCEL'].get(invoice.cancelada), + 'cfdi_notas': invoice.notas, + } + + xml = util.parse_xml2(invoice.xml) + d = util.get_dict(xml.attrib) + d.pop('Certificado', '') + currency = d.get('Moneda', 'MXN') + + data.update({f'cfdi_{k.lower()}': v for k, v in d.items()}) + + data['cfdi_tipodecomprobante'] = \ + VALUES_PDF['TYPE'][data['cfdi_tipodecomprobante']] + + if data.get('cfdi_formapago', ''): + data['cfdi_formapago'] = str( + SATFormaPago.get_by_key(data['cfdi_formapago'])) + if data.get('cfdi_metodopago', ''): + data['cfdi_metodopago'] = 'Método de Pago: ({}) {}'.format( + data['cfdi_metodopago'], + VALUES_PDF['METHOD'][data['cfdi_metodopago']]) + + data['cfdi_moneda'] = str(SATMonedas.get_by_key(currency)) + data['cfdi_tipocambio'] = util.format_currency( + data['cfdi_tipocambio'], currency, 4) + data['cfdi_totalenletras'] = util.to_letters( + float(data['cfdi_total']), currency) + + data['totales'] = [( + 'Subtotal', util.format_currency(data['cfdi_subtotal'], currency))] + + for node in xml: + if 'Emisor' in node.tag: + d = {f'emisor_{k.lower()}': v for k, v in node.attrib.items()} + data.update(d) + elif 'Receptor' in node.tag: + d = {f'receptor_{k.lower()}': v for k, v in node.attrib.items()} + data.update(d) + elif 'Conceptos' in node.tag: + data['conceptos'] = [] + for subnode in node: + concepto = self._get_concepto(self, subnode, currency) + data['conceptos'].append(concepto) + elif 'Impuestos' in node.tag: + for subnode in node: + self._get_tax(self, subnode, data, currency) + elif 'Complemento' in node.tag: + for sn in node: + if 'TimbreFiscalDigital' in sn.tag: + d = {f'timbre_{k.lower()}': v for k, v in sn.attrib.items()} + data.update(d) + + data['emisor_logo'] = data['emisor_rfc'].lower() + data['emisor_regimenfiscal'] = str( + SATRegimenes.get_by_key(data['emisor_regimenfiscal'])) + data['receptor_usocfdi'] = str( + SATUsoCfdi.get_by_key(data['receptor_usocfdi'])) + + data['totales'].append(( + 'Total', util.format_currency(data['cfdi_total'], currency))) + + 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/" + f"default.aspx?&id={data['timbre_uuid']}&re={data['emisor_rfc']}" + f"&rr={data['receptor_rfc']}&tt={data['cfdi_total']}" + f"&fe={data['cfdi_sello'][-8:]}" + ) + cbb = util.get_qr(qr_data, False) + data['cbb'] = f'data:image/png;base64,{cbb}' + + return data + + @classmethod + def get_html(cls, id): + try: + emisor = Emisor.select()[0] + except IndexError: + return '', 'sin_datos_de_emisor.html' + + obj = Facturas.get(Facturas.id==id) + name = '{}{}_{}.html'.format(obj.serie, obj.folio, obj.cliente.rfc) + if obj.uuid is None: + return '', name + + data = cls._get_others_values(cls, obj, emisor) + doc = util.to_html(data) + # ~ util.html_to_pdf(doc) + + return doc, name + @classmethod def get_ods(cls, id, rfc): try: diff --git a/source/app/settings.py b/source/app/settings.py index df4130f..5db1316 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -47,13 +47,21 @@ except ImportError: DEBUG = DEBUG -VERSION = '1.25.0' +VERSION = '1.26.0' EMAIL_SUPPORT = ('soporte@empresalibre.net',) TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION) BASE_DIR = os.path.abspath(os.path.dirname(__file__)) -PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..')) +path_static = os.path.abspath(os.path.join(BASE_DIR, '..', 'static')) +path_docs = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs')) + +path_css = os.path.join(path_static, 'css') +path_img = os.path.join(path_static, 'img') +path_user_template = os.path.join(path_docs, 'templates') + +# ~ PATH_STATIC = os.path.abspath(os.path.join(BASE_DIR, '..')) + PATH_TEMPLATES = os.path.abspath(os.path.join(BASE_DIR, '..', 'templates')) PATH_MEDIA = os.path.abspath(os.path.join(BASE_DIR, '..', 'docs')) @@ -74,7 +82,10 @@ TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT)) PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt')) PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin')) -template_lookup = TemplateLookup(directories=[PATH_TEMPLATES], +PATH_TEMPLATES_USER = os.path.abspath(os.path.join( + BASE_DIR, '..', 'docs', 'templates')) +directories=[PATH_TEMPLATES, PATH_TEMPLATES_USER] +template_lookup = TemplateLookup(directories=directories, input_encoding='utf-8', output_encoding='utf-8') @@ -182,3 +193,29 @@ DEFAULT_SAT_NOMINA = { API = 'https://api.empresalibre.net{}' CURRENCY_MN = 'MXN' + +# ~ v2 +EXT = { + 'CSS': 'css', + 'HTML': 'html', + 'ODS': 'ods', + 'PNG': 'png', + 'JSON': 'json', +} +MXN = 'MXN' +PATHS = { + 'STATIC': path_static, + 'CSS': path_css, + 'IMG': path_img, + 'DOCS': path_docs, + 'USER': path_user_template, +} +VALUES_PDF = { + 'CANCEL': {True: 'inline', False: 'none'}, + 'TYPE': {'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado'}, + 'TAX': {'001': 'ISR', '002': 'IVA', '003': 'IEPS'}, + 'METHOD': { + 'PUE': 'Pago en una sola exhibición', + 'PPD': 'Pago en parcialidades o diferido', + }, +} diff --git a/source/static/css/invoice.css b/source/static/css/invoice.css new file mode 100644 index 0000000..3eaa732 --- /dev/null +++ b/source/static/css/invoice.css @@ -0,0 +1,519 @@ +@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; +} + +.fiscales-emisor{ + color: #7d1916; +} + +.fiscales-emisor .telefono, .fiscales-emisor .correo, .fiscales-emisor .web{ + color: #333; +} + +header .titulo-vertical{ + background-color: #dbc5c6; + color: #000; +} + +.receptor .nombre{ + color: #000; +} + +.receptor .rfc{ + color: #000; +} + +.receptor .direccion{ + color: #000; +} + +.receptor .estado{ + color: #000; +} + +.cfdi .folio span{ + color: #ed483d; +} + +.cfdi .tipo span{ + color: #ed483d; +} + +.cfdi .folio-fiscal span{ + color: #ed483d; +} + +.conceptos th{ + background-color: #975759; +} + +.conceptos td.clave, .conceptos td.unidad, .conceptos td.valor-unitario{ + background-color: #dbc5c6; +} + +.conceptos td.descripcion, .conceptos td.cantidad, .conceptos td.importe{ + background-color: #f0e7e7; +} + +table.subtotal th{ + background-color: #dbc5c6; +} + +table.subtotal td{ + background-color: #f0e7e7; +} + +.sello .cadenas-sello .cadena div { + background-color: #dbc5c6; + color: #7d1916; +} + +.rfc-pac { + background-color: #dbc5c6; + color: #7d1916; +} + +.cancelada{ + color: #ed3833; +} + +@font-face { + font-family: 'Avenir Next'; + src: url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot'); + src: url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot?#iefix') format('embedded-opentype'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff2') format('woff2'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff') format('woff'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.ttf') format('truetype'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Regular.svg#AvenirNextLTPro-Regular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Avenir Next'; + src: url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot'); + src: url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot?#iefix') format('embedded-opentype'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff2') format('woff2'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff') format('woff'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.ttf') format('truetype'), + url('/static/fonts/avenir_next/AvenirNextLTPro-Bold.svg#AvenirNextLTPro-Bold') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Avenir Next'; + src: url('/static/fonts/avenir_next/AvenirNextLTPro-It.eot'); + src: url('/static/fonts/avenir_next/AvenirNextLTPro-It.eot?#iefix') format('embedded-opentype'), + url('/static/fonts/avenir_next/AvenirNextLTPro-It.woff2') format('woff2'), + url('/static/fonts/avenir_next/AvenirNextLTPro-It.woff') format('woff'), + url('/static/fonts/avenir_next/AvenirNextLTPro-It.ttf') format('truetype'), + url('/static/fonts/avenir_next/AvenirNextLTPro-It.svg#AvenirNextLTPro-It') format('svg'); + font-weight: normal; + font-style: italic; +} + + +body { +/* + font-family: 'Avenir Next'; +*/ +} + +#plantilla { + border: 1px solid #fff; + border-color: rgb(204,204,204); + background: #fff; + display: block; + margin: 0 auto; + width: 19.5cm; + height: 25.9cm; + position: relative; +} + +.cancelada{ + -ms-transform: rotate(320deg); + -webkit-transform: rotate(320deg); + transform: rotate(320deg); + color: #ed3833; + font-size: 100px; + font-weight: bold; + left: 18%; + position: absolute; + text-align: center; + top: 30%; + z-index: 1000; +} + +.cancelada div{ + font-size: 20px; +} + +.emisor-cintilla{ + background-color: #975759; + color: #fff; + font-size: 7pt; + padding: 5px; + text-align: center; +} + +.datos-emisor .logo{ + margin-left: 20px; + max-width: 180px; +} + +.datos-emisor .fiscales-emisor{ + float: right; +} + +.fiscales-emisor{ + color: #7d1916; + font-weight: bold; + margin-top: 20px; + text-align: right; +} + +.fiscales-emisor .nombre{ + font-size: 18px; + padding-right: 30px; +} + +.fiscales-emisor .rfc{ + font-size: 16px; + padding-right: 30px; +} + +.fiscales-emisor .regimen{ + font-size: 12px; + padding-right: 30px; +} + +.fiscales-emisor .telefono, .fiscales-emisor .correo, .fiscales-emisor .web{ + color: #333; + font-size: 14px; + line-height: 15px; + padding-right: 10px; +} + +.fiscales-emisor img{ + margin-left: 10px; + width: 10px; +} + +.clear{ + clear: both; +} + +header .titulo-vertical{ + background-color: #dbc5c6; + -ms-transform: rotate(270deg); + -webkit-transform: rotate(270deg); + transform: rotate(270deg); + font-size: 15px; + font-weight: bold; + left: -31px; + line-height: 35px; + position: absolute; + text-align: center; + top: 35px; + width: 105px; +} + +header .receptor{ + box-sizing: border-box; + float: left; + font-size: 10px; + height: 106px; + margin-top: 10px; + position: relative; + padding-bottom: 5px; + padding-left: 45px; + padding-top: 5px; + width: 60%; +} + +.receptor .nombre{ + color: #000; + font-size: 17px; + font-weight: bold; +} + +.receptor .rfc{ + color: #000; + font-size: 15px; + font-weight: bold; +} + +.receptor .direccion, +.receptor .estado{ + color: #000; + font-size: 10px; +} + +.receptor img{ + width: 10px; +} + +.receptor .correo, +.receptor .telefono{ + float: right; + width: 40%; +} + +.receptor .uso-cfdi{ + color: #000; + font-size: 12px; + font-weight: bold; + float: left; + width: 60%; +} + +header .cfdi{ + box-sizing: border-box; + float: right; + font-size: 10px; + height: 100px; + margin-top: 10px; + position: relative; + padding-bottom: 5px; + padding-left: 45px; + padding-top: 5px; + width: 35%; +} + +.cfdi .folio, .cfdi .tipo{ + color: #000; + float: left; + font-size: 11px; + font-weight: bold; + line-height: 15px; + width: 50%; +} + +.cfdi .folio span, .cfdi .tipo span{ + color: #ed483d; +} + +.cfdi .folio span{ + font-size: 10px; +} + +.cfdi .tipo span{ + font-size: 10px; +} + +.cfdi .folio-fiscal{ + color: #000; + font-size: 9px; + font-weight: bold; + line-height: 15px; + text-align: center; +} + +.cfdi .folio-fiscal span{ + color: #ed483d; +} + +.cfdi .fecha-emision, .cfdi .fecha-certificacion, .cfdi .lugar-expedicion{ + color: #333; + font-size: 10px; + font-weight: bold; + line-height: 15px; +} + +.conceptos{ + margin-bottom: 10px; + margin-top: 20px; + width: 100%; + text-align: center; +} + +.conceptos th{ + background-color: #975759; + color: #fff; + font-size: 11px; + line-height: 15px; +} + +.conceptos .clave{ + width: 10%; +} + +.conceptos .descripcion{ + width: 45%; +} +.conceptos .descripcion div{ + text-align: justify; +} + +.conceptos .unidad{ + width: 8%; +} + +.conceptos .cantidad{ + width: 9%; +} + +.conceptos .valor-unitario{ + width: 13%; +} + +.conceptos .importe{ + width: 15%; +} + +.conceptos td{ + background-color: #975759; + color: #000; + font-size: 11px; + line-height: 15px; +} + +.conceptos td.clave, .conceptos td.unidad, .conceptos td.valor-unitario{ + background-color: #dbc5c6; +} + +.conceptos td.descripcion, .conceptos td.cantidad, .conceptos td.importe{ + background-color: #f0e7e7; +} + +.conceptos td.valor-unitario, .conceptos td.importe{ + text-align: right; +} + +.total-letras{ + float: left; + font-size: 12px; + line-height: 15px; + margin: 5px 0; + width: 63%; +} + +table.subtotal{ + float: right; + font-size: 12px; + line-height: 15px; + text-align: right; + width: 37%; + font-weight: bold; +} + +table.subtotal th{ + background-color: #f0e7e7; + width: 60%; + padding: 3px 3px 3px 3px; +} + +table.subtotal td{ + background-color: #dbc5c6; + width: 40%; + padding: 3px 3px 3px 3px; +} + +.condiciones-pago{ + font-size: 11px; + line-height: 14px; +} + +.notas{ + float: left; + font-size: 11px; + margin: 20px 0; + width: 70%; +} + +.formapago-metodopago, .moneda-tipocambio, .tiporelacion, .relacionados{ + float: left; + font-size: 11px; + line-height: 12px; + width: 70%; +} + +.factura-info{ + float: left; + font-size: 11px; + line-height: 13px; + width: 70%; +} + +.tipocomite, .tipoproceso, .idcontabilidad{ + float: right; + font-size: 10px; + line-height: 12px; + width: 20%; +} + +.sello{ + margin: 10px 0; +} + +.sello .cbb{ + border: 1px solid #000; + height: auto; + margin-top: 10px; + margin-right: 1px; + width: 18%; +} + +.sello .cadenas-sello{ + color: #000; + float: right; + font-size: 8px; + font-weight: bold; + line-height: 15px; + width: 79%; + margin-left: 5px; + margin-right: 5px; + word-break: break-all; +} + +.sello .cadenas-sello .cadena{ + margin: 5px 0; +} + +.sello .cadenas-sello .cadena div{ + font-weight: normal; + background-color: #dbc5c6; + color: #7d1916; +} + +.cadena-original{ + color: #000; + float: right; + font-size: 8px; + font-weight: bold; + line-height: 15px; + width: 99%; + margin: 5px; + word-break: break-all; +} +.cadena-original div{ + font-weight: normal; + background-color: #dbc5c6; + color: #7d1916; +} + +.rfc-pac{ + background-color: #dbc5c6; + color: #7d1916; + font-size: 10px; + line-height: 15px; + text-align: center; + margin: 5px; +} diff --git a/source/static/fonts/avenir_next/.DS_Store b/source/static/fonts/avenir_next/.DS_Store new file mode 100644 index 0000000..7b40fe9 Binary files /dev/null and b/source/static/fonts/avenir_next/.DS_Store differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot new file mode 100755 index 0000000..6ff4d6c Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.eot differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.svg b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.svg new file mode 100755 index 0000000..3d73033 --- /dev/null +++ b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.svg @@ -0,0 +1,2842 @@ + + + + +Created by FontForge 20161013 at Thu Mar 18 18:16:44 2004 + By ,,, +Copyright (c) 2004 Linotype Library GmbH, www.linotype.com. All rights reserved. This software may not be reproduced, used, displayed, modified, disclosed or transferred without the express written approval of Linotype Library GmbH. Avenir is a trademark of Heidelberger Druckmaschinen AG, exclusively licensed through Linotype Library GmbH, and may be registered in certain jurisdictions. This typeface is original artwork of Adrian Frutiger. The design may be protected in certain jurisdictions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.ttf b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.ttf new file mode 100755 index 0000000..d32eacb Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.ttf differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff new file mode 100755 index 0000000..82bac28 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff2 b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff2 new file mode 100755 index 0000000..018ab10 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Bold.woff2 differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-It.eot b/source/static/fonts/avenir_next/AvenirNextLTPro-It.eot new file mode 100755 index 0000000..67eff78 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-It.eot differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-It.svg b/source/static/fonts/avenir_next/AvenirNextLTPro-It.svg new file mode 100755 index 0000000..7187cbc --- /dev/null +++ b/source/static/fonts/avenir_next/AvenirNextLTPro-It.svg @@ -0,0 +1,1956 @@ + + + + +Created by FontForge 20161013 at Thu Jan 29 17:13:50 2004 + By ,,, +Copyright (c) 2004 Linotype Library GmbH, www.linotype.com. All rights reserved. This software may not be reproduced, used, displayed, modified, disclosed or transferred without the express written approval of Linotype Library GmbH. Avenir is a trademark of Heidelberger Druckmaschinen AG, exclusively licensed through Linotype Library GmbH, and may be registered in certain jurisdictions. This typeface is original artwork of Adrian Frutiger and Akira Kobayashi. The design may be protected in certain jurisdictions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-It.ttf b/source/static/fonts/avenir_next/AvenirNextLTPro-It.ttf new file mode 100755 index 0000000..433a0a0 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-It.ttf differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-It.woff b/source/static/fonts/avenir_next/AvenirNextLTPro-It.woff new file mode 100755 index 0000000..4afb82b Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-It.woff differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-It.woff2 b/source/static/fonts/avenir_next/AvenirNextLTPro-It.woff2 new file mode 100755 index 0000000..5c1f3b7 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-It.woff2 differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot new file mode 100755 index 0000000..f91a884 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.eot differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.svg b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.svg new file mode 100755 index 0000000..ea10cd4 --- /dev/null +++ b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.svg @@ -0,0 +1,2825 @@ + + + + +Created by FontForge 20161013 at Thu Jan 29 17:13:34 2004 + By ,,, +Copyright (c) 2004 Linotype Library GmbH, www.linotype.com. All rights reserved. This software may not be reproduced, used, displayed, modified, disclosed or transferred without the express written approval of Linotype Library GmbH. Avenir is a trademark of Heidelberger Druckmaschinen AG, exclusively licensed through Linotype Library GmbH, and may be registered in certain jurisdictions. This typeface is original artwork of Adrian Frutiger and Akira Kobayashi. The design may be protected in certain jurisdictions. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.ttf b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.ttf new file mode 100755 index 0000000..ad46859 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.ttf differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff new file mode 100755 index 0000000..f19e41f Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff differ diff --git a/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff2 b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff2 new file mode 100755 index 0000000..32eccb0 Binary files /dev/null and b/source/static/fonts/avenir_next/AvenirNextLTPro-Regular.woff2 differ diff --git a/source/static/img/correo.png b/source/static/img/correo.png new file mode 100644 index 0000000..3e3233f Binary files /dev/null and b/source/static/img/correo.png differ diff --git a/source/static/img/tcm970625mb1.png b/source/static/img/tcm970625mb1.png new file mode 100644 index 0000000..37dbe13 Binary files /dev/null and b/source/static/img/tcm970625mb1.png differ diff --git a/source/static/img/telefono.png b/source/static/img/telefono.png new file mode 100644 index 0000000..edccef9 Binary files /dev/null and b/source/static/img/telefono.png differ diff --git a/source/static/img/web.png b/source/static/img/web.png new file mode 100644 index 0000000..fe9fde2 Binary files /dev/null and b/source/static/img/web.png differ diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 9569a3c..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) @@ -69,11 +70,15 @@ var controllers = { tb_options.attachEvent('onChange', tab_options_change) $$('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_33j').attachEvent('onItemClick', txt_plantilla_factura_33j_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) @@ -840,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({ @@ -978,13 +1023,98 @@ function txt_plantilla_factura_32_click(e){ } -function txt_plantilla_factura_33j_click(e){ +function txt_plantilla_factura_html_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_33j', - width: 200}, {width: 100}]}, - {view: 'list', id: 'lst_files', type: 'uploader', autoheight:true, + {cols: [ + {width: 100}, + {view: 'uploader', id: 'up_template', autosend: true, + link: 'lst_files', value: 'Seleccionar archivo', + upload: '/files/txt_plantilla_factura_html', 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 HTML', + body: { + view: 'form', + elements: body_elements, + } + }) + + w.show() + + $$('up_template').attachEvent('onUploadComplete', function(response){ + if(response.ok){ + $$('txt_plantilla_factura_html').setValue(response.name) + msg_ok('Plantilla cargada correctamente') + }else{ + msg_error(response.name) + } + }) +} + + +function txt_plantilla_factura_css_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_css', 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 Archivo de Estilos CSS', + body: { + view: 'form', + elements: body_elements, + } + }) + + w.show() + + $$('up_template').attachEvent('onUploadComplete', function(response){ + if(response.ok){ + $$('txt_plantilla_factura_css').setValue(response.name) + msg_ok('Plantilla cargada correctamente') + }else{ + msg_error(response.name) + } + }) +} + + +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, @@ -1007,7 +1137,7 @@ function txt_plantilla_factura_33j_click(e){ $$('up_template').attachEvent('onUploadComplete', function(response){ if(response.ok){ - $$('txt_plantilla_factura_33j').setValue(response.name) + $$('txt_plantilla_factura_json').setValue(response.name) msg_ok('Plantilla cargada correctamente') }else{ msg_error(response.name) @@ -2344,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/controller/invoices.js b/source/static/js/controller/invoices.js index fac40e6..893312e 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -1282,11 +1282,15 @@ function enviar_correo(row){ function get_pdf(id){ - //~ location = '/doc/pdf/' + id window.open('/doc/pdf/' + id, '_blank') } +function get_html(id){ + window.open('/doc/html/' + id, '_blank') +} + + function grid_invoices_click(id, e, node){ var row = this.getItem(id) @@ -1294,6 +1298,8 @@ function grid_invoices_click(id, e, node){ location = '/doc/xml/' + row.id }else if(id.column == 'pdf'){ get_pdf(row.id) + }else if(id.column == 'html'){ + get_html(row.id) }else if(id.column == 'ods'){ location = '/doc/ods/' + row.id }else if(id.column == 'zip'){ diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index bae66bd..e74b00e 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -59,6 +59,7 @@ function get_icon(tipo){ icons = { xml: 'fa-file-code-o', pdf: 'fa-file-pdf-o', + html: 'fa-html5', zip: 'fa-file-zip-o', email: 'fa-envelope-o', print: 'fa-print', diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index 30fd7e7..f04eebc 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -204,7 +204,7 @@ var emisor_otros_datos= [ {template: 'Generales', type: 'section'}, {cols: [ {view: 'search', id: 'emisor_logo', icon: 'file-image-o', - name: 'emisor_logo', label: 'Logotipo: '}, + name: 'emisor_logo', label: 'Logotipo: ', placeholder: 'Solo formato PNG'}, {view: 'text', id: 'emisor_nombre_comercial', name: 'emisor_nombre_comercial', label: 'Nombre comercial: '}, ]}, @@ -244,7 +244,7 @@ var emisor_otros_datos= [ {view: 'text', id: 'token_timbrado', name: 'token_timbrado', label: 'Token de Timbrado: '}, {view: 'text', id: 'token_soporte', - name: 'token_soporte', label: 'Token para Respaldos: '}, + name: 'token_soporte', label: 'Token de Soporte: '}, ] @@ -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,33 +582,49 @@ var form_correo = { } +var type_make_pdf = [ + {id: 1, value: 'ODS'}, + {id: 2, value: 'JSON'}, +] + + var options_templates = [ {maxHeight: 15}, - {cols: [{maxWidth: 15}, + {cols: [{maxWidth: 20}, {view: 'search', id: 'txt_plantilla_factura_32', name: 'plantilla_factura_32', label: 'Plantilla Factura v3.2 (ODS): ', labelPosition: 'top', - icon: 'file'}, {}]}, - {maxHeight: 20}, - {cols: [{maxWidth: 15}, + icon: 'file'}, {maxWidth: 25}, {view: 'search', id: 'txt_plantilla_factura_33', labelPosition: 'top', - label: 'Plantilla Factura v3.3 (ODS): ', icon: 'file'}, {}]}, + label: 'Plantilla Factura v3.3 (ODS): ', icon: 'file'}, + {maxWidth: 20} ]}, {maxHeight: 20}, - {cols: [{maxWidth: 15}, - {view: 'search', id: 'txt_plantilla_factura_33j', name: 'plantilla_factura_33j', + {cols: [{maxWidth: 20}, + {view: 'search', id: 'txt_plantilla_factura_html', name: 'plantilla_factura_html', + label: 'Plantilla Factura v3.3 (HTML): ', labelPosition: 'top', + icon: 'file'}, {maxWidth: 25}, + {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'}, {}]}, + icon: 'file'}, {maxWidth: 25}, + {}, {maxWidth: 20} ]}, + {maxHeight: 20}, - {cols: [{maxWidth: 15}, + {cols: [{maxWidth: 20}, {view: 'search', id: 'txt_plantilla_nomina1233', name: 'plantilla_nomina1233', label: 'Plantilla Nomina v1.2 - Cfdi 3.3 (ODS): ', labelPosition: 'top', - icon: 'file'}, {}]}, + icon: 'file'}, {maxWidth: 40}, {}]}, {maxHeight: 20}, - {cols: [{maxWidth: 15}, + {cols: [{maxWidth: 20}, {view: 'search', id: 'txt_plantilla_pagos10', name: 'plantilla_pagos10', label: 'Plantilla Factura de Pagos v1.0 - Cfdi 3.3 (ODS): ', - labelPosition: 'top', icon: 'file'}, {}]}, + labelPosition: 'top', icon: 'file'}, {maxWidth: 40}, {}]}, {maxHeight: 20}, - {cols: [{maxWidth: 15}, + {cols: [{maxWidth: 20}, {view: 'search', id: 'txt_plantilla_ticket', name: 'plantilla_ticket', label: 'Plantilla para Tickets (ODS): ', labelPosition: 'top', icon: 'file'}, @@ -614,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}, + {}]}, {}] diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js index 49fe2f4..09d7e1d 100644 --- a/source/static/js/ui/invoices.js +++ b/source/static/js/ui/invoices.js @@ -267,6 +267,7 @@ var grid_invoices_cols = [ fillspace: true, sort: 'string', footer: {text: '$ 0.00', colspan: 2}}, {id: 'xml', header: 'XML', adjust: 'data', template: get_icon('xml')}, {id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')}, + {id: 'html', header: 'HTML', adjust: 'data', template: get_icon('html')}, {id: 'ods', header: 'ODS', adjust: 'data', template: get_icon('table')}, {id: 'zip', header: 'ZIP', adjust: 'data', template: get_icon('zip')}, {id: 'email', header: '', adjust: 'data', template: get_icon('email')} diff --git a/source/templates/plantilla_factura.html b/source/templates/plantilla_factura.html new file mode 100644 index 0000000..f3b6d1c --- /dev/null +++ b/source/templates/plantilla_factura.html @@ -0,0 +1,204 @@ + + + + + + Empresa Libre + + +
+
+ Cancelada +
Sin valor fiscal
+
+
+
+ ${emisor_calle} ${emisor_noexterior} ${emisor_nointerior} + ${emisor_colonia} ${emisor_codigopostal} ${emisor_municipio} + ${emisor_estado} ${emisor_pais} +
+
+ +
+
+ ${emisor_nombre} +
+
+ ${emisor_rfc} +
+
+ ${emisor_regimenfiscal} +
+
+ (55) 5810-8088 + +
+
+ administracion@empresalibre.net + +
+
+ https://empresalibre.net + +
+
+
+
+
Receptor
+
+ ${receptor_nombre} +
+
+ ${receptor_rfc} +
+
+ ${receptor_calle} ${receptor_noexterior} ${receptor_nointerior} + ${receptor_colonia} ${receptor_codigopostal} +
+
+ ${receptor_municipio} ${receptor_estado} ${receptor_pais} +
+
+ ${receptor_correo_1} +
+
+
+ ${receptor_usocfdi} +
+
+ ${receptor_telefono_1} +
+
+
+
Datos CFDI
+
Folio: ${cfdi_serie}-${cfdi_folio}
+
Tipo: ${cfdi_tipodecomprobante}
+
+
+ Folio Fiscal
+ ${timbre_uuid} +
+
Fecha Emisión: ${cfdi_fecha}
+
Fecha Certificacion: ${timbre_fechatimbrado}
+
CP de Expedición: ${cfdi_lugarexpedicion}
+
+
+
+ +
+ + + + + + + + + + + + + % for c in conceptos: + + + + + + + + + % endfor + +
ClaveDescripciónUnidadCantidadValor UnitarioImporte
${c['clave']}
${c['descripcion']}
${c['unidad']}${c['cantidad']}${c['valorunitario']}${c['importe']}
+ +
+ ${cfdi_totalenletras} + +
+ ${cfdi_condicionespago}
+ ${cfdi_formapago}
+ ${cfdi_metodopago}
+ ${cfdi_moneda}, Tipo de Cambio: ${cfdi_tipocambio}
+ ${cfdi_tiporelacion}
+ ${cfdi_relacionados}
+ ${cfdi_notas} +
+
+ + % for total in totales: + + + + + % endfor +
${total[0]}${total[1]}
+ + + +
+
+ +
+
+ Sello Digital del CFDI: +
+ ${cfdi_sello} +
+
+
+ Sello del SAT: +
+ ${timbre_sellosat} +
+
+
+
+ +
+
+ Cadena original del complemento de certificación digital del SAT: +
+ ${timbre_cadenaoriginal} +
+
+ +
+
+ RFC del PAC que Certifico: ${timbre_rfcprovcertif} + Serie CSD SAT: ${timbre_nocertificadosat} + Serie CSD Emisor: ${cfdi_nocertificado} +
+
+ + diff --git a/source/templates/plantilla_factura.json b/source/templates/plantilla_factura.json index 2dff441..18dfca1 100644 --- a/source/templates/plantilla_factura.json +++ b/source/templates/plantilla_factura.json @@ -1,4 +1,5 @@ { +"colores": {"piepagina": "darkred"}, "encabezado": { "emisor": { "direccion": { @@ -208,10 +209,9 @@ "estilo": {"name": "tipocambio", "fontName": "Helvetica", "fontSize": 7, "alignment": 0, "textColor": "black"} }, - "leyenda": { - "estilo": {"name": "leyenda", "fontName": "Helvetica-Bold", - "fontSize": 6, "alignment": 1, "textColor": "black", - "spaceBefore": 0.2} + "notas": { + "estilo": {"name": "notas", "fontName": "Helvetica", + "fontSize": 7, "alignment": 0, "textColor": "black"} } } } diff --git a/source/templates/plantilla_factura.ods b/source/templates/plantilla_factura.ods index 8061850..cab2f95 100644 Binary files a/source/templates/plantilla_factura.ods and b/source/templates/plantilla_factura.ods differ