diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index f1fb86d..5ff3ddc 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -36,7 +36,7 @@ from dateutil import parser from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \ PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ - PATH_XMLSEC, TEMPLATE_CANCEL, IMPUESTOS + PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO #~ def _get_hash(password): @@ -1277,10 +1277,10 @@ class ImportFacturaLibre(object): self._rfc = rfc self._con = None self._cursor = None + self._error = '' self._is_connect = self._connect(path) self._clientes = [] self._clientes_rfc = [] - self._error = '' @property def error(self): @@ -1299,7 +1299,7 @@ class ImportFacturaLibre(object): return False if obj['rfc'] != self._rfc: - self._error = 'Los datos no corresponden al emisor: {}'.format(self._rfc) + self._error = 'Los datos no corresponden al RFC: {}'.format(self._rfc) return False return True @@ -1328,6 +1328,7 @@ class ImportFacturaLibre(object): tables = ( ('receptores', 'Socios'), ('cfdfacturas', 'Facturas'), + ('categorias', 'Categorias'), ) for source, target in tables: data[target] = self._get_table(source) @@ -1339,6 +1340,63 @@ class ImportFacturaLibre(object): def _get_table(self, table): return getattr(self, '_{}'.format(table))() + def import_productos(self): + sql = "SELECT * FROM productos" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('id_categoria', 'categoria'), + ('noIdentificacion', 'clave'), + ('descripcion', 'descripcion'), + ('unidad', 'unidad'), + ('valorUnitario', 'valor_unitario'), + ('existencia', 'existencia'), + ('inventario', 'inventario'), + ('codigobarras', 'codigo_barras'), + ('CuentaPredial', 'cuenta_predial'), + ('precio_compra', 'ultimo_precio'), + ('minimo', 'minimo'), + ) + data = [] + + sql = """ + SELECT nombre, tasa, tipo + FROM impuestos, productos, productosimpuestos + WHERE productos.id=productosimpuestos.id_producto + AND productosimpuestos.id_impuesto=impuestos.id + AND productos.id = ? + """ + for row in rows: + new = {t: row[s] for s, t in fields} + new['descripcion'] = ' '.join(new['descripcion'].split()) + new['clave_sat'] = DEFAULT_SAT_PRODUCTO + self._cursor.execute(sql, (row['id'],)) + impuestos = self._cursor.fetchall() + new['impuestos'] = tuple(impuestos) + data.append(new) + + return data + + def _categorias(self): + sql = "SELECT * FROM categorias" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('categoria', 'categoria'), + ('id_padre', 'padre'), + ) + data = [] + + for row in rows: + new = {t: row[s] for s, t in fields} + if new['padre'] == 0: + new['padre'] = None + data.append(new) + + return data + def _get_cliente(self, invoice): sql = "SELECT rfc, nombre FROM receptores WHERE id=?" self._cursor.execute(sql, [invoice['id_cliente']]) diff --git a/source/app/models/main.py b/source/app/models/main.py index b304403..3c85a35 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -15,7 +15,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 + INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO FORMAT = '{0:.2f}' @@ -529,6 +529,10 @@ class Categorias(BaseModel): (('categoria', 'padre'), True), ) + @classmethod + def exists(cls, filters): + return Categorias.select().where(filters).exists() + @classmethod def get_all(cls): rows = (Categorias.select( @@ -536,6 +540,9 @@ class Categorias(BaseModel): Categorias.categoria.alias('value'), Categorias.padre.alias('parent_id')) ).dicts() + for row in rows: + if row['parent_id'] is None: + row['parent_id'] = 0 return tuple(rows) @@ -3073,6 +3080,112 @@ def _importar_facturas(rows): return +def _importar_categorias(rows): + log.info('\tImportando Categorías...') + for row in rows: + if row['padre'] is None: + filters = ( + (Categorias.categoria==row['categoria']) & + (Categorias.padre.is_null(True)) + ) + else: + filters = ( + (Categorias.categoria==row['categoria']) & + (Categorias.padre==row['padre']) + ) + + if Categorias.exists(filters): + continue + + try: + Categorias.create(**row) + except IntegrityError: + msg = '\tCategoria: ({}) {}'.format(row['padre'], row['categoria']) + log.error(msg) + + log.info('\tCategorías importadas...') + return + + +def _get_id_unidad(unidad): + obj = SATUnidades.select(SATUnidades.id).where(SATUnidades.name==unidad) + + if obj is None: + msg = 'No se encontró: {}'.format(unidad) + log.error('\t', msg) + return unidad + + return str(obj[0].id) + + +def _get_impuestos(impuestos): + lines = '|' + for impuesto in impuestos: + info = ( + IMPUESTOS.get(impuesto['nombre']), + impuesto['nombre'], + impuesto['tipo'][0], + str(round(float(impuesto['tasa']) / 100.0, 6)), + ) + lines += '|'.join(info) + return lines + + +def _generar_archivo_productos(archivo): + rfc = input('Introduce el RFC: ').strip().upper() + if not rfc: + msg = 'El RFC es requerido' + log.error(msg) + return + + args = util.get_con(rfc) + if not args: + return + + conectar(args) + + log.info('Importando datos...') + app = util.ImportFacturaLibre(archivo, rfc) + if not app.is_connect: + log.error('\t{}'.format(app._error)) + return + + rows = app.import_productos() + + p, _, _, _ = util.get_path_info(archivo) + path_txt = util._join(p, 'productos.txt') + log.info('\tGenerando archivo: {}'.format(path_txt)) + + fields = ( + 'clave', + 'clave_sat', + 'unidad', + 'categoria', + 'descripcion', + 'valor_unitario', + 'existencia', + 'inventario', + 'codigo_barras', + 'cuenta_predial', + 'ultimo_precio', + 'minimo', + ) + + data = ['|'.join(fields)] + for row in rows: + impuestos = row.pop('impuestos', ()) + line = [str(row[r]) for r in fields] + line[2] = _get_id_unidad(line[2]) + line = '|'.join(line) + _get_impuestos(impuestos) + data.append(line) + + with open(path_txt, 'w') as fh: + fh.write('\n'.join(data)) + + log.info('\tArchivo generado: {}'.format(path_txt)) + return + + def _importar_factura_libre(archivo): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: @@ -3089,18 +3202,39 @@ def _importar_factura_libre(archivo): log.info('Importando datos...') app = util.ImportFacturaLibre(archivo, rfc) if not app.is_connect: - log.error('\t{}'.format(app.error)) + log.error('\t{}'.format(app._error)) return data = app.import_data() _importar_socios(data['Socios']) _importar_facturas(data['Facturas']) + _importar_categorias(data['Categorias']) + #~ _generar_archivo_productos(data['Productos'], archivo) log.info('Importación terminada...') return +def _test(): + rfc = input('Introduce el RFC: ').strip().upper() + if not rfc: + msg = 'El RFC es requerido' + log.error(msg) + return + + args = util.get_con(rfc) + if not args: + return + + conectar(args) + + rows = Categorias.select().where( + Categorias.categoria=='Productos', Categorias.padre.is_null(True)).exists() + print (rows) + return + + CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) help_create_tables = 'Crea las tablas en la base de datos' help_migrate_db = 'Migra las tablas en la base de datos' @@ -3125,10 +3259,19 @@ help_lr = 'Listar RFCs' @click.option('-i', '--importar-valores', is_flag=True, default=False) @click.option('-a', '--archivo') @click.option('-fl', '--factura-libre', is_flag=True, default=False) +@click.option('-t', '--test', is_flag=True, default=False) +@click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False) +@click.option('-ip', '--importar-productos', is_flag=True, default=False) def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, - borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre): + borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre, test, + generar_archivo_productos, importar_productos): + opt = locals() + if opt['test']: + _test() + sys.exit(0) + if opt['iniciar_bd']: _iniciar_bd() sys.exit(0) @@ -3183,6 +3326,36 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, _importar_factura_libre(opt['archivo']) sys.exit(0) + if opt['generar_archivo_productos']: + if not opt['archivo']: + msg = 'Falta la ruta de la base de datos' + raise click.ClickException(msg) + if not util.is_file(opt['archivo']): + msg = 'No es un archivo' + raise click.ClickException(msg) + _, _, _, ext = util.get_path_info(opt['archivo']) + if ext != '.sqlite': + msg = 'No es una base de datos' + raise click.ClickException(msg) + + _generar_archivo_productos(opt['archivo']) + sys.exit(0) + + if opt['importar_productos']: + if not opt['archivo']: + msg = 'Falta la ruta del archivo' + raise click.ClickException(msg) + if not util.is_file(opt['archivo']): + msg = 'No es un archivo' + raise click.ClickException(msg) + _, _, _, ext = util.get_path_info(opt['archivo']) + if ext != '.txt': + msg = 'No es un archivo de texto' + raise click.ClickException(msg) + + _importar_productos(opt['archivo']) + sys.exit(0) + return diff --git a/source/app/settings.py b/source/app/settings.py index c670fb1..2a4e9df 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -102,3 +102,4 @@ IMPUESTOS = { 'ICIC': '000', 'CEDULAR': '000', } +DEFAULT_SAT_PRODUCTO = '01010101' diff --git a/source/static/js/controller/products.js b/source/static/js/controller/products.js index 87255ff..da87d4f 100644 --- a/source/static/js/controller/products.js +++ b/source/static/js/controller/products.js @@ -1,13 +1,21 @@ +function get_categorias(){ + webix.ajax().sync().get('/values/categorias', function(text, data){ + var values = data.json() + $$('categoria').getList().parse(values, 'plainjs') + }) +} + + function cmd_new_product_click(id, e, node){ $$('form_product').setValues({ - id: 0, es_activo_producto: true}) + id: 0, es_activo_producto: true}) add_config({'key': 'id_product', 'value': ''}) get_new_key() get_taxes() + get_categorias() $$('grid_products').clearSelection() - $$('categoria').getList().load('/values/categorias') $$('unidad').getList().load('/values/unidades') $$("multi_products").setValue("product_new") } diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index dd26684..dd607ee 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -153,3 +153,29 @@ function get_config(value){ return key.value } } + + +webix.DataDriver.plainjs = webix.extend({ + arr2hash:function(data){ + var hash = {}; + for (var i=0; i