Merge branch 'develop'

Filtros por fecha y reporte de tickets
This commit is contained in:
Mauricio Baeza 2018-01-01 13:18:25 -06:00
commit 2944ddc6f1
9 changed files with 350 additions and 19 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
#~ import falcon
import os
import re
import smtplib
import ssl
@ -13,8 +13,8 @@ from email.mime.text import MIMEText
from email import encoders
from email.utils import formatdate
import os
import requests
from escpos import printer
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image
from reportlab.lib import colors
@ -949,3 +949,92 @@ class SeaFileAPI(object):
resp = requests.get(url, headers=self._headers)
return resp.json()
class PrintTicket(object):
LINE = '------------------------------------------------\n'
TITLES = 'CANT. U ARTICULO P.U. TOTAL\n'
LEYENDA = 'GRACIAS POR SU COMPRA\n\nGuarde este ticket para cualquier ' \
'aclaración.\nComprobante simplificado de operación con\npúblico en ' \
'general de acuerdo al Art. 37\nFracc II inc. v del Reglamento del\n' \
'Código Fiscal de la Federación.\n\n'
def __init__(self, info):
self.p = self._init_printer(info)
def _init_printer(self, info):
try:
if info['ip']:
p = printer.Network(info['ip'])
else:
p = printer.Usb(*info['usb'])
p.codepage = 'cp850'
return p
except Exception as e:
print (e)
return
def _set(self, *data):
self.p.set(*data)
return
def _t(self, text):
self.p.text(text)
return
def _l(self):
self._t(self.LINE)
return
def printer(self, data):
if self.p is None:
return False
self._emisor(data['emisor'])
self._receptor(data['receptor'])
self._ticket(data['ticket'])
self._products(data['products'])
self._footer(data['ticket'])
self.p.cut()
return True
def _emisor(self, data):
self._set('center', 'B', 'B', 2, 2)
self._t(data['name'])
self._set('center', 'B', 'B', 2)
self._t(data['rfc'])
self._set('center', 'A')
self._t(data['address'])
return
def _receptor(self, data):
self._set('center', 'B', 'B', 2)
self._t(data['name'])
return
def _ticket(self, data):
self._set('left', 'B', 'B', 2)
self._t(data['title'])
self._set('left', 'A')
self._t(data['date'])
return
def _products(self, data):
self._l()
self._t(self.TITLES)
self._l()
for p in data:
self._t(p)
self._l()
self._t('Total artículos: {}\n'.format(len(data)))
self._l()
return
def _footer(self, data):
self._set('right', 'B', 'B', 2)
self._t(data['total'])
self._set('center', 'A')
self._t(data['letters'])
self._t(self.LEYENDA)
self._set('center', 'A', 'B')
self._t('empresalibre.net')
return

View File

@ -36,7 +36,7 @@ import pyqrcode
from dateutil import parser
from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \
SeaFileAPI
SeaFileAPI, PrintTicket
from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS
@ -1307,6 +1307,15 @@ def upload_file(rfc, opt, file_obj):
name = '{}_3.3.json'.format(rfc.lower())
path = _join(PATH_MEDIA, 'templates', name)
elif opt == 'txt_plantilla_ticket':
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}
name = '{}_ticket.ods'.format(rfc.lower())
path = _join(PATH_MEDIA, 'templates', name)
elif opt == 'txt_plantilla_donataria':
tmp = file_obj.filename.split('.')
ext = tmp[-1].lower()
@ -1705,12 +1714,12 @@ class ImportFacturaLibreGambas(object):
('cfdifacturas', 'Facturas'),
('categorias', 'Categorias'),
('productos', 'Productos'),
# ~ ('tickets', 'Tickets'),
('tickets', 'Tickets'),
)
for source, target in tables:
data[target] = self._get_table(source)
# ~ data['Socios'] += self._clientes
data['Socios'] += self._clientes
return data
@ -2505,3 +2514,8 @@ class ImportFacturaLibre(object):
data.append(new)
return data
def print_ticket(data, info):
p = PrintTicket(info)
return p.printer(data)

View File

@ -268,6 +268,8 @@ class StorageEngine(object):
return main.Tickets.cancel(values)
if opt == 'invoice':
return main.Tickets.invoice(values)
if opt == 'print':
return main.Tickets.printer(values)
def get_tickets(self, values):
return main.Tickets.get_by(values)

View File

@ -17,7 +17,7 @@ if __name__ == '__main__':
from controllers import util
from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
CANCEL_SIGNATURE
CANCEL_SIGNATURE, PUBLIC
FORMAT = '{0:.2f}'
@ -211,6 +211,24 @@ class Configuracion(BaseModel):
values['default_unidad'] = SATUnidades.get_default()
return values
if keys['fields'] == 'configtemplates':
try:
emisor = Emisor.select()[0]
is_ong = emisor.es_ong
except IndexError:
is_ong = False
values = {'txt_plantilla_donataria': is_ong}
fields = (
('chk_usar_punto_de_venta', 'txt_plantilla_ticket'),
)
for s, key in fields:
value = util.get_bool(Configuracion.get_(s))
values[key] = value
return values
if keys['fields'] == 'correo':
fields = ('correo_servidor', 'correo_puerto', 'correo_ssl',
'correo_usuario', 'correo_contra', 'correo_copia',
@ -231,6 +249,7 @@ class Configuracion(BaseModel):
'txt_plantilla_factura_32',
'txt_plantilla_factura_33',
'txt_plantilla_factura_33j',
'txt_plantilla_ticket',
'txt_plantilla_donataria',
)
data = (Configuracion
@ -4242,6 +4261,14 @@ class Tickets(BaseModel):
filters = (fy & fm)
return filters
if opt == 'dates':
dates = util.loads(values['range'])
filters = Tickets.fecha.between(
util.get_date(dates['start']),
util.get_date(dates['end'], True)
)
return filters
return
@classmethod
@ -4328,6 +4355,63 @@ class Tickets(BaseModel):
doc = util.to_pdf(data, data['emisor']['rfc'])
return doc, name
def _format_ticket(self, id):
emisor = util.get_dict(Emisor.select().dicts()[0])
ticket = Tickets.select().where(Tickets.id==id).dicts()[0]
products = TicketsDetalle.get_by_print(id)
emisor['name'] = '{}\n'.format(emisor['nombre'])
emisor['rfc'] = 'RFC: {}\n'.format(emisor['rfc'])
interior = ''
if emisor['no_interior']:
interior = ', {}'.format(emisor['no_interior'])
colonia = ''
if emisor['colonia']:
colonia = ', Col. {}'.format(emisor['colonia'])
municipio = ''
if emisor['municipio']:
municipio = ', {}'.format(emisor['municipio'])
estado = ''
if emisor['estado']:
estado = ', {}'.format(emisor['estado'])
cp = ''
if emisor['codigo_postal']:
cp = ', C.P. {}'.format(emisor['codigo_postal'])
pais = ''
if emisor['pais']:
pais = ', {}'.format(emisor['pais'])
direccion = '{} {}{}{}{}{}{}{}\n\n'.format(emisor['calle'],
emisor['no_exterior'], interior, colonia, municipio, estado, cp,
pais)
emisor['address'] = direccion
ticket['title'] = 'Ticket: {}{}\n\n'.format(ticket['serie'],
ticket['folio'])
ticket['date'] = 'Fecha y hora: {}\n'.format(ticket['fecha'])
ticket['letters'] = '{}\n\n'.format(
util.to_letters(ticket['total'], 'peso'))
ticket['total'] = 'TOTAL: $ {:>12,.2f}\n\n'.format(ticket['total'])
data = {
'emisor': emisor,
'receptor': {'name': '{}\n\n'.format(PUBLIC)},
'ticket': ticket,
'products': products,
}
return data
@classmethod
def printer(cls, values):
id = int(values['id'])
info = {'ip': '', 'usb': (int('1ba0', 16), int('2204', 16))}
data = cls._format_ticket(cls, id)
result = util.print_ticket(data, info)
msg = 'Ticket impreso correctamente'
if not result:
msg = 'Asegurate de que la impresora este conectada y funcionando.'
result = {'ok': result, 'msg': msg}
return result
class TicketsDetalle(BaseModel):
ticket = ForeignKeyField(Tickets)
@ -4356,6 +4440,21 @@ class TicketsDetalle(BaseModel):
return precio_final
@classmethod
def get_by_print(cls, id):
products = TicketsDetalle.select().where(TicketsDetalle.ticket==id)
lines = []
for p in products:
price_with_tax = cls._with_tax(cls, p)
importe = round(price_with_tax * float(p.cantidad), DECIMALES)
l = '{:>6,.2f} {:<4} {:<14} {:>9,.2f} {:>10,.2f}\n'.format(
p.cantidad, p.producto.unidad.name, p.descripcion,
price_with_tax, importe
)
lines.append(l)
return lines
@classmethod
def get_by_ticket(cls, id):
data = []

View File

@ -117,4 +117,5 @@ IMPUESTOS = {
DEFAULT_SAT_PRODUCTO = '01010101'
DIR_FACTURAS = 'facturas'
USAR_TOKEN = False
CANCEL_SIGNATURE = False
CANCEL_SIGNATURE = False
PUBLIC = 'Público en general'

View File

@ -49,6 +49,7 @@ 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_33j').attachEvent('onItemClick', txt_plantilla_factura_33j_click)
$$('txt_plantilla_ticket').attachEvent('onItemClick', txt_plantilla_ticket_click)
$$('txt_plantilla_donataria').attachEvent('onItemClick', txt_plantilla_donataria_click)
$$('chk_config_ocultar_metodo_pago').attachEvent('onItemClick', chk_config_item_click)
$$('chk_config_ocultar_condiciones_pago').attachEvent('onItemClick', chk_config_item_click)
@ -330,13 +331,29 @@ function get_admin_usuarios(){
}
function set_config_templates(){
webix.ajax().get('/config', {'fields': 'configtemplates'}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
},
success: function(text, data, xhr) {
var values = data.json()
Object.keys(values).forEach(function(key){
show(key, values[key])
})
}
})
}
function get_config_values(opt){
if(opt == undefined){
return
}
if(opt == 'templates'){
show('txt_plantilla_donataria', $$('chk_ong').getValue())
set_config_templates()
}
webix.ajax().get('/config', {'fields': opt}, {
@ -822,6 +839,44 @@ function txt_plantilla_factura_33_click(e){
}
function txt_plantilla_ticket_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_ticket',
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 Ticket ODT',
body: {
view: 'form',
elements: body_elements,
}
})
w.show()
$$('up_template').attachEvent('onUploadComplete', function(response){
if(response.ok){
$$('txt_plantilla_ticket').setValue(response.name)
msg_ok('Plantilla cargada correctamente')
}else{
msg_error(response.name)
}
})
}
function txt_plantilla_factura_32_click(e){
var body_elements = [

View File

@ -7,6 +7,7 @@ var tickets_controllers = {
init: function(){
$$('cmd_nuevo_ticket').attachEvent('onItemClick', cmd_nuevo_ticket_click)
$$('cmd_ticket_to_invoice').attachEvent('onItemClick', cmd_ticket_to_invoice_click)
$$('cmd_ticket_report').attachEvent('onItemClick', cmd_ticket_report_click)
$$('cmd_generar_ticket').attachEvent('onItemClick', cmd_generar_ticket_click)
$$('cmd_cerrar_ticket').attachEvent('onItemClick', cmd_cerrar_ticket_click)
$$('cmd_new_invoice_from_ticket').attachEvent('onItemClick', cmd_new_invoice_from_ticket_click)
@ -20,8 +21,10 @@ var tickets_controllers = {
$$('grid_tdetails').attachEvent('onItemClick', grid_ticket_details_click)
$$('grid_tdetails').attachEvent('onBeforeEditStop', grid_tickets_details_before_edit_stop)
$$('gt_productos_found').attachEvent('onValueSuggest', gt_productos_found_click)
$$('cmd_ticket_filter_today').attachEvent('onItemClick', cmd_ticket_filter_today_click)
$$('filter_year_ticket').attachEvent('onChange', filter_year_ticket_change)
$$('filter_month_ticket').attachEvent('onChange', filter_month_ticket_change)
$$('filter_dates_ticket').attachEvent('onChange', filter_dates_ticket_change)
$$('chk_is_invoice_day').attachEvent('onChange', chk_is_invoice_day_change)
$$('grid_tickets_active').attachEvent('onItemDblClick', grid_tickets_active_double_click)
$$('grid_tickets_invoice').attachEvent('onItemDblClick', grid_tickets_invoice_double_click)
@ -78,20 +81,33 @@ function get_tickets(filters){
}
function cmd_ticket_filter_today_click(){
get_tickets()
}
function filter_year_ticket_change(nv, ov){
var fm = $$('filter_month_ticket')
filters = {'opt': 'yearmonth','year': nv, 'month': fm.getValue()}
filters = {'opt': 'yearmonth', 'year': nv, 'month': fm.getValue()}
get_tickets(filters)
}
function filter_month_ticket_change(nv, ov){
var fy = $$('filter_year_ticket')
filters = {'opt': 'yearmonth','year': fy.getValue(), 'month': nv}
filters = {'opt': 'yearmonth', 'year': fy.getValue(), 'month': nv}
get_tickets(filters)
}
function filter_dates_ticket_change(range){
if(range.start != null && range.end != null){
filters = {'opt': 'dates', 'range': range}
get_tickets(filters)
}
}
function configuracion_inicial_ticket(){
current_dates_tickets()
get_tickets()
@ -651,11 +667,54 @@ function ticket_notes_key_up(){
}
function print_ticket(id){
var data = new Object()
data['opt'] = 'print'
data['id'] = id
webix.ajax().post('tickets', data, {
error:function(text, data, XmlHttpRequest){
msg = 'Ocurrio un error, consulta a soporte técnico'
msg_error(msg)
},
success:function(text, data, XmlHttpRequest){
values = data.json();
if(values.ok){
msg_ok(values.msg)
}else{
msg_error(values.msg)
}
}
})
}
function grid_tickets_click(id, e, node){
//~ var row = this.getItem(id)
if(id.column == 'pdf'){
//~ window.open('/doc/tpdf/' + id, '_blank')
get_ticket_pdf(id)
get_ticket_pdf(id.row)
return
}
if(id.column == 'print'){
print_ticket(id.row)
return
}
}
function cmd_ticket_report_click(){
webix.toPDF($$('grid_tickets'), {
ignore: {'pdf': true, 'print': true},
filename: 'Reporte_Tickets',
columns:{
index: true,
serie: {width: 50},
folio: {width: 50},
fecha: {width: 125},
estatus: true,
total: {css: 'right'},
}
})
}

View File

@ -27,6 +27,7 @@ var form_editar_usuario_elementos = [
]}
]
var admin_ui_windows = {
init: function(){
webix.ui({
@ -470,12 +471,12 @@ var options_templates = [
{maxHeight: 15},
{cols: [{maxWidth: 15},
{view: 'search', id: 'txt_plantilla_factura_32', name: 'plantilla_factura_32',
label: 'Plantilla Factura v3.2 (ODT): ', labelPosition: 'top',
label: 'Plantilla Factura v3.2 (ODS): ', labelPosition: 'top',
icon: 'file'}, {}]},
{maxHeight: 20},
{cols: [{maxWidth: 15},
{view: 'search', id: 'txt_plantilla_factura_33', labelPosition: 'top',
label: 'Plantilla Factura v3.3 (ODT): ', icon: 'file'}, {}]},
label: 'Plantilla Factura v3.3 (ODS): ', icon: 'file'}, {}]},
{maxHeight: 20},
{cols: [{maxWidth: 15},
{view: 'search', id: 'txt_plantilla_factura_33j', name: 'plantilla_factura_33j',
@ -483,9 +484,14 @@ var options_templates = [
icon: 'file'}, {}]},
{maxHeight: 20},
{cols: [{maxWidth: 15},
{view: 'search', id: 'txt_plantilla_ticket', name: 'plantilla_ticket',
label: 'Plantilla para Tickets (ODS): ', labelPosition: 'top',
icon: 'file'},
{view: 'search', id: 'txt_plantilla_donataria', name: 'plantilla_donataria',
label: 'Plantilla Donataria (solo ONGs): ', labelPosition: 'top',
icon: 'file'}, {}]},
icon: 'file'},
{}]},
{maxHeight: 20},
{}]

View File

@ -6,12 +6,17 @@ var toolbar_tickets = [
{view: 'button', id: 'cmd_ticket_to_invoice', label: 'Facturar',
type: 'iconButton', autowidth: true, icon: 'file-code-o'},
{},
{view: 'button', id: 'cmd_ticket_report', label: 'Reporte',
type: 'iconButton', autowidth: true, icon: 'bars'},
{},
{view: 'button', id: 'cmd_cancelar_ticket', label: 'Cancelar',
type: 'iconButton', autowidth: true, icon: 'ban'},
]
var toolbar_tickets_filter = [
{view: 'button', id: 'cmd_ticket_filter_today', label: 'Hoy', type: 'iconButton',
autowidth: true, icon: 'filter'},
{view: 'richselect', id: 'filter_year_ticket', label: 'Año',
labelAlign: 'right', labelWidth: 50, width: 150, options: []},
{view: 'richselect', id: 'filter_month_ticket', label: 'Mes',
@ -25,8 +30,8 @@ var grid_tickets_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data",
sort:"string", hidden: true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "header",
sort: "string"},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {text: 'Tickets', colspan: 3}},
{id: "fecha", header: ["Fecha y Hora"],
@ -34,11 +39,12 @@ var grid_tickets_cols = [
{id: "estatus", header: ["Estatus", {content: "selectFilter"}],
adjust: "data", sort:"string"},
{id: 'total', header: ['Total', {content: 'numberFilter'}], width: 150,
sort: 'int', format: webix.i18n.priceFormat, css: 'right'},
sort: 'int', format: webix.i18n.priceFormat, css: 'right',
footer: {content: 'summColumn', css: 'right'}},
{id: "cliente", header: ["Razón Social", {content: "selectFilter"}],
fillspace:true, sort:"string", hidden: true},
{id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')},
{id: 'print', header: 'I', adjust: 'data', template: get_icon('print')},
{id: 'print', header: '', adjust: 'data', template: get_icon('print')},
]