Facturar productos en lote
This commit is contained in:
parent
65a3bc931f
commit
7873a4376c
|
@ -928,7 +928,7 @@ class LIBO(object):
|
|||
options = {'AsTemplate': True, 'Hidden': True}
|
||||
doc = self._doc_open(path, options)
|
||||
if doc is None:
|
||||
return ()
|
||||
return (), 'No se pudo abrir la plantilla'
|
||||
|
||||
data, msg = self._get_data(doc)
|
||||
doc.close(True)
|
||||
|
@ -952,6 +952,22 @@ class LIBO(object):
|
|||
rows = [dict(zip(fields, r)) for r in data[1:]]
|
||||
return rows, ''
|
||||
|
||||
def invoice(self, path):
|
||||
options = {'AsTemplate': True, 'Hidden': True}
|
||||
doc = self._doc_open(path, options)
|
||||
if doc is None:
|
||||
return (), 'No se pudo abrir la plantilla'
|
||||
|
||||
data, msg = self._get_data(doc)
|
||||
doc.close(True)
|
||||
|
||||
if len(data) == 1:
|
||||
msg = 'Sin datos para importar'
|
||||
return (), msg
|
||||
|
||||
rows = tuple(data[1:])
|
||||
return rows, ''
|
||||
|
||||
|
||||
def to_pdf(data, emisor_rfc, ods=False):
|
||||
rfc = data['emisor']['rfc']
|
||||
|
@ -1390,6 +1406,15 @@ def upload_file(rfc, opt, file_obj):
|
|||
|
||||
name = '{}_products.ods'.format(rfc.lower())
|
||||
path = _join(PATH_MEDIA, 'tmp', name)
|
||||
elif opt == 'invoiceods':
|
||||
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 = '{}_invoice.ods'.format(rfc.lower())
|
||||
path = _join(PATH_MEDIA, 'tmp', name)
|
||||
|
||||
if save_file(path, file_obj.file.read()):
|
||||
return {'status': 'server', 'name': file_obj.filename, 'ok': True}
|
||||
|
@ -2601,11 +2626,26 @@ def import_products(rfc):
|
|||
name = '{}_products.ods'.format(rfc.lower())
|
||||
path = _join(PATH_MEDIA, 'tmp', name)
|
||||
if not is_file(path):
|
||||
return ()
|
||||
return (), 'No se encontró la plantilla'
|
||||
|
||||
if APP_LIBO:
|
||||
app = LIBO()
|
||||
if app.is_running:
|
||||
return app.products(path)
|
||||
|
||||
return ()
|
||||
return (), 'No se encontro LibreOffice'
|
||||
|
||||
|
||||
def import_invoice(rfc):
|
||||
name = '{}_invoice.ods'.format(rfc.lower())
|
||||
path = _join(PATH_MEDIA, 'tmp', name)
|
||||
if not is_file(path):
|
||||
return (), 'No se encontró la plantilla'
|
||||
|
||||
if APP_LIBO:
|
||||
app = LIBO()
|
||||
if app.is_running:
|
||||
return app.invoice(path)
|
||||
|
||||
return (), 'No se encontro LibreOffice'
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ class StorageEngine(object):
|
|||
return getattr(self, '_get_{}'.format(table))(values, session)
|
||||
return getattr(self, '_get_{}'.format(table))(values)
|
||||
|
||||
def _get_importinvoice(self, values):
|
||||
return main.import_invoice()
|
||||
|
||||
def _get_main(self, values):
|
||||
return main.config_main()
|
||||
|
||||
|
|
|
@ -104,6 +104,62 @@ def validar_timbrar():
|
|||
return {'ok': True, 'msg': msg}
|
||||
|
||||
|
||||
|
||||
def _get_taxes_product(id):
|
||||
model_pt = Productos.impuestos.get_through_model()
|
||||
impuestos = tuple(model_pt
|
||||
.select(
|
||||
model_pt.productos_id.alias('product'),
|
||||
model_pt.satimpuestos_id.alias('tax'))
|
||||
.where(model_pt.productos_id==id).dicts())
|
||||
return impuestos
|
||||
|
||||
|
||||
def import_invoice():
|
||||
log.info('Importando factura...')
|
||||
emisor = Emisor.select()[0]
|
||||
rows, msg = util.import_invoice(emisor.rfc)
|
||||
if not rows:
|
||||
return {'ok': False, 'msg': msg}
|
||||
|
||||
# ~ clave, descripcion, precio, cantidad
|
||||
products = {}
|
||||
for row in rows:
|
||||
try:
|
||||
obj = Productos.get(Productos.clave==row[0])
|
||||
if obj.id in products:
|
||||
vu = round(row[2], 2)
|
||||
cant = round(row[3], 2)
|
||||
pf = products[obj.id]['valor_unitario'] - float(obj.descuento)
|
||||
products[obj.id]['cantidad'] += cant
|
||||
products[obj.id]['importe'] = round(
|
||||
pf * products[obj.id]['cantidad'], DECIMALES)
|
||||
if vu != products[obj.id]['valor_unitario']:
|
||||
msg = 'Precio diferente en producto: {}'.format(row[0])
|
||||
return {'ok': False, 'msg': msg}
|
||||
else:
|
||||
vu = round(row[2], 2)
|
||||
cant = round(row[3], 2)
|
||||
pf = vu - float(obj.descuento)
|
||||
p = {
|
||||
'id': obj.id,
|
||||
'delete': '-',
|
||||
'clave': obj.clave,
|
||||
'descripcion': obj.descripcion,
|
||||
'unidad': obj.unidad.name,
|
||||
'cantidad': cant,
|
||||
'valor_unitario': vu,
|
||||
'descuento': obj.descuento,
|
||||
'importe': round(pf * cant, DECIMALES),
|
||||
'taxes': _get_taxes_product(obj.id),
|
||||
}
|
||||
products[obj.id] = p
|
||||
except Productos.DoesNotExist:
|
||||
pass
|
||||
log.info('Factura importada...')
|
||||
return {'ok': True, 'rows': tuple(products.values())}
|
||||
|
||||
|
||||
def get_doc(type_doc, id, rfc):
|
||||
types = {
|
||||
'xml': 'application/xml',
|
||||
|
@ -2231,21 +2287,21 @@ class Productos(BaseModel):
|
|||
q.execute()
|
||||
obj = Productos.get(w)
|
||||
obj.impuestos = taxes
|
||||
log.info('Producto actualizado...')
|
||||
log.info('\tProducto actualizado: {}'.format(data['clave']))
|
||||
ap += 1
|
||||
else:
|
||||
obj = Productos.create(**data)
|
||||
obj.impuestos = taxes
|
||||
log.info('Producto agregado...')
|
||||
log.info('\tProducto agregado: {}'.format(data['clave']))
|
||||
np += 1
|
||||
except Exception as e:
|
||||
msg = 'Error al importar producto: {}'.format(data['clave'])
|
||||
log.error(msg)
|
||||
log.error(e)
|
||||
|
||||
msg = 'Productos encontrados: {}\n'.format(len(rows))
|
||||
msg += 'Productos agregados: {}\n'.format(np)
|
||||
msg += 'Productos actualizados: {}'.format(ap)
|
||||
msg = 'Productos encontrados: {}<BR>'.format(len(rows))
|
||||
msg += 'Productos agregados: {}<BR>'.format(np)
|
||||
msg += 'Productos actualizados: {}<BR>'.format(ap)
|
||||
msg += 'Productos con problemas: {}'.format(len(rows) - np - ap)
|
||||
return {'ok': True, 'msg': msg}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ var invoices_controllers = {
|
|||
|
||||
$$('cmd_cfdi_notes').attachEvent('onItemClick', cmd_cfdi_notes_click)
|
||||
$$('cmd_admin_invoice_notes').attachEvent('onItemClick', cmd_admin_invoice_notes_click)
|
||||
$$('cmd_import_invoice').attachEvent('onItemClick', cmd_import_invoice_click)
|
||||
|
||||
webix.extend($$('grid_invoices'), webix.ProgressBar)
|
||||
}
|
||||
|
@ -858,7 +859,6 @@ function set_product(values){
|
|||
table_pt.insert(v)
|
||||
}
|
||||
}
|
||||
//~ calculate_taxes()
|
||||
calcular_impuestos()
|
||||
}
|
||||
|
||||
|
@ -868,25 +868,6 @@ function grid_products_found_click(obj){
|
|||
}
|
||||
|
||||
|
||||
//~ function search_product_by_id(id){
|
||||
//~ webix.ajax().get('/values/product', {'id': id}, {
|
||||
//~ error: function(text, data, xhr) {
|
||||
//~ msg_error('Error al consultar')
|
||||
//~ },
|
||||
//~ success: function(text, data, xhr){
|
||||
//~ var values = data.json()
|
||||
//~ if (values.ok){
|
||||
//~ set_product(values)
|
||||
//~ } else {
|
||||
//~ msg = 'No se encontró un producto con la clave: ' + id
|
||||
//~ msg_error(msg)
|
||||
//~ }
|
||||
//~ }
|
||||
//~ })
|
||||
|
||||
//~ }
|
||||
|
||||
|
||||
function search_product_by_key(key){
|
||||
webix.ajax().get('/values/productokey', {'key': key}, {
|
||||
error: function(text, data, xhr) {
|
||||
|
@ -1864,4 +1845,112 @@ function cmd_admin_invoice_notes_click(){
|
|||
to_end('invoice_notes')
|
||||
})
|
||||
cfg_invoice['notes'] = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cmd_import_invoice_click(){
|
||||
win_import_invoice.init()
|
||||
$$('win_import_invoice').show()
|
||||
}
|
||||
|
||||
|
||||
function cmd_upload_invoice_click(){
|
||||
var form = $$('form_upload_invoice')
|
||||
|
||||
var values = form.getValues()
|
||||
|
||||
if(!$$('lst_upload_invoice').count()){
|
||||
$$('win_import_invoice').close()
|
||||
return
|
||||
}
|
||||
|
||||
if($$('lst_upload_invoice').count() > 1){
|
||||
msg = 'Selecciona solo un archivo'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var template = $$('up_invoice').files.getItem($$('up_invoice').files.getFirstId())
|
||||
|
||||
if(template.type.toLowerCase() != 'ods'){
|
||||
msg = 'Archivo inválido.\n\nSe requiere un archivo ODS'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
|
||||
msg = '¿Estás seguro de importar este archivo?'
|
||||
webix.confirm({
|
||||
title: 'Importar Factura',
|
||||
ok: 'Si',
|
||||
cancel: 'No',
|
||||
type: 'confirm-error',
|
||||
text: msg,
|
||||
callback:function(result){
|
||||
if(result){
|
||||
$$('up_invoice').send()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function add_import_product_taxes(taxes){
|
||||
//~ var taxes = values.taxes
|
||||
//~ var row = grid.getItem(values.id)
|
||||
|
||||
//~ values['delete'] = '-'
|
||||
//~ if (row == undefined){
|
||||
//~ grid.add(values)
|
||||
//~ } else {
|
||||
//~ values['cantidad'] = parseFloat(row.cantidad) + parseFloat(values['cantidad'])
|
||||
//~ values['valor_unitario'] = parseFloat(row.valor_unitario)
|
||||
//~ values['descuento'] = parseFloat(row.descuento)
|
||||
//~ var precio_final = values['valor_unitario'] - values['descuento']
|
||||
//~ values['importe'] = (precio_final * values['cantidad']).round(DECIMALES)
|
||||
//~ grid.updateItem(row.id, values)
|
||||
//~ }
|
||||
|
||||
for(var v of taxes){
|
||||
var pt = table_pt.findOne(v)
|
||||
if(pt === null){
|
||||
table_pt.insert(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function up_invoice_upload_complete(response){
|
||||
if(response.status != 'server'){
|
||||
msg = 'Ocurrio un error al subir el archivo'
|
||||
msg_error(msg)
|
||||
return
|
||||
}
|
||||
msg = 'Archivo subido correctamente.\n\nComenzando importación.'
|
||||
msg_ok(msg)
|
||||
$$('win_import_invoice').close()
|
||||
|
||||
webix.ajax().get('/values/importinvoice', {
|
||||
error: function(text, data, xhr) {
|
||||
msg_error('Error al consultar')
|
||||
},
|
||||
success: function(text, data, xhr){
|
||||
var values = data.json()
|
||||
if (values.ok){
|
||||
for(var p of values.rows){
|
||||
add_import_product_taxes(p.taxes)
|
||||
}
|
||||
grid.clearAll()
|
||||
grid.parse(values.rows, 'json')
|
||||
grid.refresh()
|
||||
calcular_impuestos()
|
||||
}else{
|
||||
webix.alert({
|
||||
title: 'Error al importar',
|
||||
text: values.msg,
|
||||
type: 'alert-error',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -75,23 +75,6 @@ function get_partners(){
|
|||
}
|
||||
|
||||
|
||||
function get_products(){
|
||||
var grid = $$('grid_products')
|
||||
webix.ajax().get('/products', {}, {
|
||||
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 menu_user_click(id, e, node){
|
||||
if (id == 1){
|
||||
window.location = '/logout';
|
||||
|
|
|
@ -50,6 +50,24 @@ function get_categorias(){
|
|||
}
|
||||
|
||||
|
||||
function get_products(){
|
||||
var grid = $$('grid_products')
|
||||
webix.ajax().get('/products', {}, {
|
||||
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');
|
||||
grid.refresh()
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function cmd_new_product_click(id, e, node){
|
||||
get_taxes()
|
||||
configurar_productos(true)
|
||||
|
@ -378,8 +396,11 @@ function up_products_upload_complete(response){
|
|||
success: function(text, data, xhr) {
|
||||
var values = data.json();
|
||||
if (values.ok){
|
||||
msg_ok(values.msg)
|
||||
get_products()
|
||||
webix.alert({
|
||||
title: 'Importación terminada',
|
||||
text: values.msg,
|
||||
})
|
||||
}else{
|
||||
msg_error(values.msg)
|
||||
}
|
||||
|
|
|
@ -201,6 +201,8 @@ var toolbar_invoices_generate = {view: 'toolbar', elements: [{},
|
|||
type: 'iconButton', autowidth: true, icon: 'commenting-o'},
|
||||
{view: 'button', id: 'cmd_cfdi_relacionados', label: 'CFDI Relacionados',
|
||||
type: 'iconButton', autowidth: true, icon: 'file-o'},
|
||||
{view: 'button', id: 'cmd_import_invoice', label: 'Importar',
|
||||
type: 'iconButton', autowidth: true, icon: 'upload'},
|
||||
{view: 'checkbox', id: 'chk_cfdi_anticipo', labelRight: 'Es Anticipo',
|
||||
labelWidth: 0, width: 100, hidden: true},
|
||||
{view: 'checkbox', id: 'chk_cfdi_donativo', labelRight: 'Es Donativo',
|
||||
|
@ -277,7 +279,8 @@ var grid_invoices = {
|
|||
var grid_details_cols = [
|
||||
{id: "id", header:"ID", hidden: true},
|
||||
{id: 'delete', header: '', width: 30, css: 'delete'},
|
||||
{id: "clave", header:{text: 'Clave', css: 'center'}, width: 100},
|
||||
{id: "clave", header:{text: 'Clave', css: 'center'}, width: 100,
|
||||
adjust: 'data'},
|
||||
{id: "clave_sat", hidden: true},
|
||||
{id: "descripcion", header:{text: 'Descripción', css: 'center'},
|
||||
fillspace: true, editor: 'popup'},
|
||||
|
@ -661,3 +664,35 @@ var app_invoices = {
|
|||
multi_invoices
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
var body_upload_invoice = {rows: [
|
||||
{view: 'form', id: 'form_upload_invoice', rows: [
|
||||
{cols: [{},
|
||||
{view: 'uploader', id: 'up_invoice', autosend: false,
|
||||
link: 'lst_upload_invoice', value: 'Seleccionar Archivo',
|
||||
upload: '/files/invoiceods'}, {}]},
|
||||
{cols: [
|
||||
{view: 'list', id: 'lst_upload_invoice', name: 'lst_upload_invoice',
|
||||
type: 'uploader', autoheight: true, borderless: true}]},
|
||||
{cols: [{}, {view: 'button', id: 'cmd_upload_invoice',
|
||||
label: 'Importar Factura'}, {}]},
|
||||
]},
|
||||
]}
|
||||
|
||||
|
||||
var win_import_invoice = {
|
||||
init: function(){
|
||||
webix.ui({
|
||||
view: 'window',
|
||||
id: 'win_import_invoice',
|
||||
width: 400,
|
||||
modal: true,
|
||||
position: 'center',
|
||||
head: 'Importar Factura de Plantilla',
|
||||
body: body_upload_invoice,
|
||||
})
|
||||
$$('cmd_upload_invoice').attachEvent('onItemClick', cmd_upload_invoice_click)
|
||||
$$('up_invoice').attachEvent('onUploadComplete', up_invoice_upload_complete)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue