Generar factura de ticket

This commit is contained in:
Mauricio Baeza 2017-12-25 23:30:34 -06:00
parent 937329bd9c
commit b777e1774f
5 changed files with 392 additions and 55 deletions

View File

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

View File

@ -1790,7 +1790,7 @@ class Socios(BaseModel):
fields = cls._clean(cls, values)
try:
obj = Socios.create(**fields)
except IntegrityError:
except IntegrityError as e:
msg = 'Ya existe el RFC y Razón Social'
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
return data
@ -2789,17 +2789,9 @@ class Facturas(BaseModel):
totals_tax[tax.id] = tax
for tax in totals_tax.values():
# ~ if tax.tipo == 'E' or tax.tipo == 'R':
if tax.tipo == 'E':
continue
# ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
# ~ if tax.key == '000':
# ~ locales_traslados += import_tax
# ~ else:
# ~ total_trasladados = (total_trasladados or 0) + import_tax
# ~ if tax.name == 'IVA':
# ~ total_iva += import_tax
invoice_tax = {
'factura': invoice.id,
@ -2809,26 +2801,6 @@ class Facturas(BaseModel):
}
FacturasImpuestos.create(**invoice_tax)
# ~ for tax in totals_tax.values():
# ~ if tax.tipo == 'E' or tax.tipo == 'T':
# ~ continue
# ~ if tax.tasa == round(Decimal(2/3), 6):
# ~ import_tax = round(float(tax.tasa) * total_iva, DECIMALES)
# ~ else:
# ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
# ~ if tax.key == '000':
# ~ locales_retenciones += import_tax
# ~ else:
# ~ total_retenciones = (total_retenciones or 0) + import_tax
# ~ invoice_tax = {
# ~ 'factura': invoice.id,
# ~ 'impuesto': tax.id,
# ~ 'base': tax.base,
# ~ 'importe': tax.suma_impuestos,
# ~ }
# ~ FacturasImpuestos.create(**invoice_tax)
total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0) \
+ locales_traslados - locales_retenciones
@ -4006,6 +3978,162 @@ class Tickets(BaseModel):
data = {'ok': True, 'row': row}
return data
def _get_folio_invoice(self, serie):
inicio = (Facturas
.select(fn.Max(Facturas.folio).alias('mf'))
.where(Facturas.serie==serie)
.order_by(SQL('mf'))
.scalar())
if inicio is None:
inicio = 1
else:
inicio += 1
return inicio
def _cancel_tickets(self, invoice, tickets):
query = (Tickets
.update(estatus='Facturado', cancelado=True, factura=invoice)
.where(Tickets.id.in_(tickets))
)
result = query.execute()
print (result)
return
def _calculate_totals_invoice(self, invoice, tickets):
subtotal = 0
descuento_cfdi = 0
totals_tax = {}
total_trasladados = None
total_retenciones = None
details = TicketsDetalle.select().where(TicketsDetalle.ticket.in_(tickets))
for detail in details:
product = {}
p = detail.producto
product['unidad'] = p.unidad.key
product['clave'] = p.clave
product['clave_sat'] = p.clave_sat
product['factura'] = invoice.id
product['producto'] = p.id
product['descripcion'] = detail.descripcion
cantidad = float(detail.cantidad)
valor_unitario = float(detail.valor_unitario)
descuento = float(detail.descuento)
precio_final = valor_unitario - descuento
importe = round(cantidad * precio_final, DECIMALES)
product['cantidad'] = cantidad
product['valor_unitario'] = valor_unitario
product['descuento'] = round(descuento * cantidad, DECIMALES)
product['precio_final'] = precio_final
product['importe'] = round(cantidad * valor_unitario, DECIMALES)
descuento_cfdi += product['descuento']
subtotal += product['importe']
FacturasDetalle.create(**product)
base = product['importe'] - product['descuento']
for tax in p.impuestos:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES)
if tax.tipo == 'T' and tax.key != '000':
total_trasladados = (total_trasladados or 0) + impuesto_producto
elif tax.tipo == 'R' and tax.key != '000':
total_retenciones = (total_retenciones or 0) + impuesto_producto
elif tax.tipo == 'T' and tax.key == '000':
locales_traslados += impuesto_producto
elif tax.tipo == 'R' and tax.key == '000':
locales_retenciones += impuesto_producto
if tax.id in totals_tax:
totals_tax[tax.id].base += base
totals_tax[tax.id].suma_impuestos += impuesto_producto
else:
tax.base = base
tax.suma_impuestos = impuesto_producto
totals_tax[tax.id] = tax
for tax in totals_tax.values():
if tax.tipo == 'E':
continue
invoice_tax = {
'factura': invoice.id,
'impuesto': tax.id,
'base': tax.base,
'importe': tax.suma_impuestos,
}
FacturasImpuestos.create(**invoice_tax)
total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0)
total_mn = round(total * invoice.tipo_cambio, DECIMALES)
data = {
'subtotal': subtotal,
'descuento': descuento_cfdi,
'total': total,
'total_mn': total_mn,
'total_trasladados': total_trasladados,
'total_retenciones': total_retenciones,
}
return data
@classmethod
def invoice(cls, values):
is_invoice_day = util.get_bool(values['is_invoice_day'])
id_client = int(values['client'])
tickets = util.loads(values['tickets'])
if is_invoice_day:
filters = (
Socios.rfc == 'XAXX010101000' and
Socios.slug == 'publico_en_general')
try:
client = Socios.get(filters)
except Socios.DoesNotExist:
msg = 'No existe el cliente Público en General. Agregalo primero.'
data = {'ok': False, 'msg': msg}
return data
else:
client = Socios.get(Socios.id==id_client)
emisor = Emisor.select()[0]
data = {}
data['cliente'] = client
data['serie'] = 'T'
data['folio'] = cls._get_folio_invoice(cls, data['serie'])
data['forma_pago'] = client.forma_pago.key
data['tipo_cambio'] = 1.00
data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
if client.uso_cfdi is None:
data['uso_cfdi'] = 'P01'
else:
data['uso_cfdi'] = client.uso_cfdi.key
data['regimen_fiscal'] = emisor.regimenes[0].key
with database_proxy.atomic() as txn:
obj = Facturas.create(**data)
totals = cls._calculate_totals_invoice(cls, obj, tickets)
obj.subtotal = totals['subtotal']
obj.descuento = totals['descuento']
obj.total_trasladados = totals['total_trasladados']
obj.total_retenciones = totals['total_retenciones']
obj.total = totals['total']
obj.saldo = totals['total']
obj.total_mn = totals['total_mn']
obj.save()
cls._cancel_tickets(cls, obj, tickets)
msg = 'Factura generada correctamente.<BR><BR>Enviando a timbrar'
data = {'ok': True, 'msg': msg, 'id': obj.id}
return data
@classmethod
def cancel(cls, values):
id = int(values['id'])
@ -4021,6 +4149,10 @@ class Tickets(BaseModel):
if not opt:
return
if opt == 'active':
filters = (Tickets.cancelado==False)
return filters
if opt == 'today':
t = util.today()
filters = (

View File

@ -155,7 +155,7 @@ function cmd_save_partner_click(id, e, node){
if (values.ok) {
update_grid_partner(values)
} else {
msg_error(msg)
msg_error(values.msg)
}
}
})

View File

@ -11,6 +11,8 @@ var tickets_controllers = {
$$('cmd_new_invoice_from_ticket').attachEvent('onItemClick', cmd_new_invoice_from_ticket_click)
$$('cmd_close_ticket_invoice').attachEvent('onItemClick', cmd_cerrar_ticket_click)
$$('cmd_cancelar_ticket').attachEvent('onItemClick', cmd_cancelar_ticket_click)
$$('cmd_move_tickets_right').attachEvent('onItemClick', cmd_move_tickets_right_click)
$$('cmd_move_tickets_left').attachEvent('onItemClick', cmd_move_tickets_left_click)
$$('tsearch_product_key').attachEvent('onKeyPress', tsearch_product_key_press)
$$('grid_tdetails').attachEvent('onItemClick', grid_ticket_details_click)
$$('grid_tdetails').attachEvent('onBeforeEditStop', grid_tickets_details_before_edit_stop)
@ -18,8 +20,13 @@ var tickets_controllers = {
$$('filter_year_ticket').attachEvent('onChange', filter_year_ticket_change)
$$('filter_month_ticket').attachEvent('onChange', filter_month_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)
$$('tsearch_client_key').attachEvent('onKeyPress', tsearch_client_key_press)
$$('grid_ticket_clients_found').attachEvent('onValueSuggest', grid_ticket_clients_found_click)
webix.extend($$('grid_tickets'), webix.ProgressBar)
webix.extend($$('grid_tickets_active'), webix.ProgressBar)
}
}
@ -87,8 +94,38 @@ function configuracion_inicial_ticket(){
}
function get_active_tickets(grid){
filters = {'opt': 'active'}
grid.showProgress({type: 'icon'})
webix.ajax().get('/tickets', filters, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr) {
var values = data.json();
grid.clearAll();
if (values.ok){
grid.parse(values.rows, 'json')
}
}
})
}
function configuracion_inicial_ticket_to_invoice(){
//~ get_active_tickets()
var grid = $$('grid_tickets_active')
var gridt = $$('grid_tickets_invoice')
var form = $$('form_ticket_invoice')
get_active_tickets(grid)
form.setValues({id_partner: 0, lbl_tclient: 'Ninguno'})
gridt.attachEvent('onAfterAdd', function(id, index){
gridt.adjustColumn('index')
gridt.adjustColumn('folio', 'all')
gridt.adjustColumn('fecha', 'all')
});
gridt.clearAll()
}
@ -416,6 +453,165 @@ function chk_is_invoice_day_change(new_value, old_value){
}
function send_timbrar_invoice(id){
webix.ajax().get('/values/timbrar', {id: id}, function(text, data){
var values = data.json()
if(values.ok){
msg_ok(values.msg)
}else{
webix.alert({
title: 'Error al Timbrar',
text: values.msg,
type: 'alert-error'
})
}
})
}
function save_ticket_to_invoice(data){
webix.ajax().sync().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)
send_timbrar_invoice(values.id)
$$('multi_tickets').setValue('tickets_home')
}else{
msg_error(values.msg)
}
}
})
}
function cmd_new_invoice_from_ticket_click(){
showvar('ok')
var form = this.getFormView();
var chk = $$('chk_is_invoice_day')
var grid = $$('grid_tickets_invoice')
var values = form.getValues()
var tickets = []
if(!chk.getValue()){
if(values.id_partner == 0){
webix.UIManager.setFocus('tsearch_client_name')
msg = 'Selecciona un cliente'
msg_error(msg)
return false
}
}
if(!grid.count()){
msg = 'Agrega al menos un ticket a facturar'
msg_error(msg)
return false
}
grid.eachRow(function(row){
tickets.push(row)
})
var data = new Object()
data['client'] = values.id_partner
data['tickets'] = tickets
data['is_invoice_day'] = chk.getValue()
data['opt'] = 'invoice'
msg = 'Todos los datos son correctos.<BR><BR>¿Estás seguro de generar esta factura?'
webix.confirm({
title: 'Generar Factura',
ok: 'Si',
cancel: 'No',
type: 'confirm-error',
text: msg,
callback:function(result){
if(result){
save_ticket_to_invoice(data)
}
}
})
}
function grid_tickets_active_double_click(id, e, node){
this.move(id.row, -1, $$('grid_tickets_invoice'))
}
function grid_tickets_invoice_double_click(id, e, node){
this.move(id.row, -1, $$('grid_tickets_active'))
}
function cmd_move_tickets_right_click(){
$$('grid_tickets_active').eachRow(
function(row){
this.copy(row, -1, $$('grid_tickets_invoice'))
}
)
$$('grid_tickets_active').clearAll()
}
function cmd_move_tickets_left_click(){
$$('grid_tickets_invoice').eachRow(
function(row){
this.copy(row, -1, $$('grid_tickets_active'))
}
)
$$('grid_tickets_invoice').clearAll()
}
function ticket_set_client(row){
var form = $$('form_ticket_invoice')
var html = '<span class="webix_icon fa-user"></span><span class="lbl_partner">'
form.setValues({
id_partner: row.id,
tsearch_client_key: '',
tsearch_client_name: ''}, true)
html += row.nombre + ' (' + row.rfc + ')</span>'
$$('lbl_tclient').setValue(html)
}
function ticket_search_client_by_id(id){
webix.ajax().get('/values/client', {'id': id}, {
error: function(text, data, xhr) {
msg_error('Error al consultar')
},
success: function(text, data, xhr){
var values = data.json()
if (values.ok){
ticket_set_client(values.row)
}else{
msg = 'No se encontró un cliente con la clave: ' + id
msg_error(msg)
}
}
})
}
function tsearch_client_key_press(code, e){
var value = this.getValue()
if(code == 13 && value.length > 0){
var id = parseInt(value, 10)
if (isNaN(id)){
msg_error('Captura una clave válida')
}else{
ticket_search_client_by_id(id)
}
}
}
function grid_ticket_clients_found_click(obj){
ticket_set_client(obj)
}

View File

@ -200,10 +200,10 @@ var toolbar_ticket_invoice = {view: 'toolbar', elements: [{},
{}]}
var tsuggest_partners = {
var ticket_suggest_partners = {
view: 'gridsuggest',
id: 'grid_tclients_found',
name: 'grid_tclients_found',
id: 'grid_ticket_clients_found',
name: 'grid_ticket_clients_found',
body: {
autoConfig: false,
header: false,
@ -233,7 +233,7 @@ var ticket_search_client = {cols: [{rows: [
placeholder:'Presiona ENTER para buscar'},
{view: 'search', id: 'tsearch_client_name',
name: 'tsearch_client_name', label: 'por Nombre o RFC',
labelPosition: 'top', suggest: tsuggest_partners,
labelPosition: 'top', suggest: ticket_suggest_partners,
placeholder: 'Captura al menos tres letras'},
]},
{cols: [
@ -247,17 +247,17 @@ var ticket_search_client = {cols: [{rows: [
var grid_tickets_active_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data",
sort:"string", hidden: true},
sort: "string", hidden: true},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {text: 'Tickets', colspan: 3}},
{id: "fecha", header: ["Fecha y Hora"],
adjust: "data", sort: "string"},
{id: 'total', header: 'Total', width: 150,sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
sort: 'int', css: 'right', footer: {content: 'countRows', css: 'right'}},
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string",
footer: 'Tickets'},
{id: 'total', header: 'Total', width: 150,sort: 'int', css: 'right',
format: webix.i18n.priceFormat, footer: {content: 'summColumn',
css: 'right'}},
]
@ -267,6 +267,7 @@ var grid_tickets_active = {
select: 'row',
adjust: true,
footer: true,
drag: true,
resizeColumn: true,
headermenu: true,
columns: grid_tickets_active_cols,
@ -281,17 +282,17 @@ var grid_tickets_active = {
var grid_tickets_invoice_cols = [
{id: 'index', header: '#', adjust: 'data', css: 'right',
footer: {content: 'countRows', colspan: 3, css: 'right'}},
{id: 'index', header: '#', adjust: 'data', css: 'right'},
{id: "id", header:"ID", hidden:true},
{id: "serie", header: ["Serie", {content: "selectFilter"}], adjust: "data",
sort:"string", hidden: true},
{id: 'folio', header: 'Folio', adjust: 'header', sort: 'int',
css: 'right', footer: {text: 'Tickets', colspan: 3}},
{id: "fecha", header: ["Fecha y Hora"],
adjust: "data", sort: "string"},
{id: 'total', header: 'Total', width: 150,sort: 'int',
format: webix.i18n.priceFormat, css: 'right'},
sort: "string", hidden: true},
{id: 'folio', header: ['Folio', {content: 'numberFilter'}], adjust: 'header',
sort: 'int', css: 'right', footer: {content: 'countRows', css: 'right'}},
{id: "fecha", header: ["Fecha y Hora"], adjust: "data", sort: "string",
footer: 'Tickets'},
{id: 'total', header: 'Total', width: 150,sort: 'int', css: 'right',
format: webix.i18n.priceFormat, footer: {content: 'summColumn',
css: 'right'}},
]
@ -301,6 +302,7 @@ var grid_tickets_invoice = {
select: 'row',
adjust: true,
footer: true,
drag: true,
resizeColumn: true,
headermenu: true,
columns: grid_tickets_invoice_cols,
@ -321,8 +323,13 @@ var controls_ticket_to_invoice = [
ticket_search_client,
{minHeight: 5, maxHeight: 5},
{cols:[
{rows: [{view: 'label', label: 'Tickets sin facturar', height: 30, align: 'left'}, grid_tickets_active]},
{minWidth: 10, maxWidth: 10},
{rows: [{view: 'label', label: 'Tickets sin facturar', height: 30,
align: 'left'},
grid_tickets_active]},
{rows:[{},
{view: 'button', id: 'cmd_move_tickets_right', label: '->', autowidth: true},
{view: 'button', id: 'cmd_move_tickets_left', label: '<-', autowidth: true},
{}]},
{rows: [{view: 'label', label: 'Tickets a facturar', height: 30, align: 'left'}, grid_tickets_invoice]},
]},
{minHeight: 20, maxHeight: 20},