#!/usr/bin/env python import sqlite3 import click from peewee import * from playhouse.fields import PasswordField, ManyToManyField from playhouse.shortcuts import case if __name__ == '__main__': import os, sys parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parent_dir) from controllers import util from settings import log, VERSION, PATH_CP, COMPANIES, PRE FORMAT = '{0:.2f}' database_proxy = Proxy() class BaseModel(Model): class Meta: database = database_proxy def conectar(opt): db = { 'sqlite': SqliteDatabase, 'postgres': PostgresqlDatabase, 'mysql': MySQLDatabase, } db_type = opt.pop('type') db_name = opt.pop('name') if not db_type in db: log.error('Tipo de base de datos no soportado') return False #~ print ('DB NAME', db_name) database = db[db_type](db_name, **opt) try: database_proxy.initialize(database) database_proxy.connect() log.info('Conectado a la BD...') return True except OperationalError as e: log.error('Error al intentar conectar a la base de datos') log.error(e) return False def desconectar(): if database_proxy.obj is None: return if not database_proxy.is_closed(): database_proxy.close() log.info('Desconectado a la BD...') return class Configuracion(BaseModel): clave = TextField() valor = TextField(default='') class Meta: order_by = ('clave',) indexes = ( (('clave', 'valor'), True), ) class Tags(BaseModel): tag = TextField(index=True, unique=True) class Meta: order_by = ('tag',) class Usuarios(BaseModel): usuario = TextField(unique=True) nombre = TextField(default='') apellidos = TextField(default='') correo = TextField(default='') contraseña = PasswordField() es_superusuario = BooleanField(default=False) es_admin = BooleanField(default=False) es_activo = BooleanField(default=True) fecha_ingreso = DateTimeField(default=util.now) ultimo_ingreso = DateTimeField(null=True) def __str__(self): t = '{} {} ({})' return t.format(self.nombre, self.apellidos, self.usuario) class Meta: order_by = ('nombre', 'apellidos') class SATRegimenes(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) activo = BooleanField(default=False) default = BooleanField(default=False) fisica = BooleanField(default=False) moral = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) @classmethod def get_(cls, ids): if isinstance(ids, int): ids = [ids] return SATRegimenes.select().where(SATRegimenes.id.in_(ids)) @classmethod def get_activos(cls, rfc): where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True)) if (len(rfc) == 12): where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True)) rows = (SATRegimenes .select( SATRegimenes.id, SATRegimenes.name.alias('value')) .where(where) .dicts() ) return tuple(rows) class Emisor(BaseModel): rfc = TextField(unique=True) nombre = TextField(default='') nombre_comercial = TextField(default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='México') codigo_postal = TextField(default='') cp_expedicion = TextField(default='') es_moral = BooleanField(default=False) es_ong = BooleanField(default=False) es_escuela = BooleanField(default=False) autorizacion = TextField(default='') fecha_autorizacion = DateField(null=True) fecha_dof = DateField(null=True) telefono = TextField(default='') correo = TextField(default='') web = TextField(default='') curp = TextField(default='') regimenes = ManyToManyField(SATRegimenes, related_name='emisores') def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) class Meta: order_by = ('nombre',) @classmethod def get_(cls, rfc): regimenes = SATRegimenes.get_activos(rfc) row = {'regimenes': regimenes} obj = Emisor.select().where(Emisor.rfc==rfc) if bool(obj): obj = obj[0] row['emisor'] = { 'emisor_rfc': obj.rfc, 'emisor_nombre': obj.nombre, 'emisor_cp': obj.codigo_postal, 'emisor_cp2': obj.cp_expedicion, 'emisor_calle': obj.calle, 'emisor_no_exterior': obj.no_exterior, 'emisor_no_interior': obj.no_interior, 'emisor_colonia': obj.colonia, 'emisor_municipio': obj.municipio, 'emisor_estado': obj.estado, 'emisor_pais': obj.pais, 'emisor_nombre_comercial': obj.nombre_comercial, 'emisor_telefono': obj.telefono, 'emisor_correo': obj.correo, 'emisor_web': obj.web, 'es_escuela': obj.es_escuela, 'es_ong': obj.es_ong, 'ong_autorizacion': obj.autorizacion, 'ong_fecha': obj.fecha_autorizacion, 'ong_fecha_dof': obj.fecha_dof, 'regimenes': [row.id for row in obj.regimenes] } else: row['emisor'] = {'emisor_rfc': rfc} return {'ok': True, 'row': row} @classmethod def get_regimenes(cls): obj = Emisor.select()[0] rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes] return tuple(rows) def _clean(self, values): fields = util.clean(values) fields['rfc'] = fields.pop('emisor_rfc') fields['nombre'] = fields.pop('emisor_nombre') fields['codigo_postal'] = fields.pop('emisor_cp') fields['cp_expedicion'] = fields.pop('emisor_cp2', '') or fields['codigo_postal'] fields['colonia'] = fields.pop('emisor_colonia', '') fields['municipio'] = fields.pop('emisor_municipio', '') fields['estado'] = fields.pop('emisor_estado', '') fields['pais'] = fields.pop('emisor_pais', 'México') fields['nombre_comercial'] = fields.pop('emisor_nombre_comercial', '') fields['telefono'] = fields.pop('emisor_telefono', '') fields['correo'] = fields.pop('emisor_correo', '') fields['web'] = fields.pop('emisor_web', '') fields['es_escuela'] = bool(fields['es_escuela'].replace('0', '')) fields['es_ong'] = bool(fields['es_ong'].replace('0', '')) fields['autorizacion'] = fields.pop('ong_autorizacion', '') fields['fecha_autorizacion'] = fields.pop('ong_fecha', None) fields['fecha_dof'] = fields.pop('ong_fecha_dof', None) if len(fields['rfc']) == 12: fields['es_moral'] = True fields['regimenes'] = SATRegimenes.get_( util.loads(fields['regimenes'])) return fields @classmethod def add(cls, values): fields = cls._clean(cls, values) obj, created = Emisor.get_or_create(rfc=fields['rfc']) obj.regimenes = fields.pop('regimenes') q = Emisor.update(**fields).where(Emisor.id==obj.id) return {'ok': bool(q.execute())} class Certificado(BaseModel): key = BlobField(null=True) key_tmp = BlobField(null=True) key_enc = TextField(default='') cer = BlobField(null=True) cer_tmp = BlobField(null=True) cer_pem = TextField(default='') cer_txt = TextField(default='') p12 = BlobField(null=True) serie = TextField(default='') rfc = TextField(default='') desde = DateTimeField(null=True) hasta = DateTimeField(null=True) def __str__(self): return self.serie @classmethod def get_data(cls): obj = cls.get_(cls) row = { 'cert_rfc': obj.rfc, 'cert_serie': obj.serie, 'cert_desde': obj.desde, 'cert_hasta': obj.hasta, } return row def get_(cls): if Certificado.select().count(): obj = Certificado.select()[0] else: obj = Certificado() return obj @classmethod def add(cls, file_object): obj = cls.get_(cls) if file_object.filename.endswith('key'): obj.key_tmp = file_object.file.read() elif file_object.filename.endswith('cer'): obj.cer_tmp = file_object.file.read() obj.save() return {'status': 'server'} @classmethod def validate(cls, values, session): row = {} result = False obj = cls.get_(cls) cert = util.Certificado(obj.key_tmp, obj.cer_tmp) data = cert.validate(values['contra'], session['rfc']) if data: msg = 'Certificado guardado correctamente' q = Certificado.update(**data).where(Certificado.id==obj.id) if q.execute(): result = True row = { 'cert_rfc': data['rfc'], 'cert_serie': data['serie'], 'cert_desde': data['desde'], 'cert_hasta': data['hasta'], } else: msg = cert.error obj.key_tmp = None obj.cer_tmp = None obj.save() return {'ok': result, 'msg': msg, 'data': row} class Folios(BaseModel): serie = TextField(unique=True) inicio = IntegerField(default=1) default = BooleanField(default=False) usarcon = TextField(default='') class Meta: order_by = ('-default', 'serie', 'inicio') indexes = ( (('serie', 'inicio'), True), ) @classmethod def get_all(cls): rows = (Folios .select( Folios.id, Folios.serie.alias('value'), Folios.usarcon, ) .dicts() ) return tuple(rows) @classmethod def get_(cls): rows = (Folios .select( Folios.id, SQL(" '-' AS 'delete' "), Folios.serie, Folios.inicio, case(Folios.usarcon, ( ('I', 'Ingreso'), ('E', 'Egreso'), ('T', 'Traslado'), ), 'Todos').alias('usarcon'), case(Folios.default, ( (True, 'Si'), (False, 'No'), )).alias('pre') ) .dicts() ) return tuple(rows) @classmethod def add(cls, values): uc = { '': 'Todos', 'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado', } pre = { True: 'Si', False: 'No', } if 'default' in values: values['default'] = True try: obj = Folios.create(**values) except IntegrityError: msg = 'Serie ya existe' return {'ok': False, 'msg': msg} row = { 'id': obj.id, 'delete' : '-', 'serie' : obj.serie, 'inicio' : obj.inicio, 'usarcon' : uc[obj.usarcon], 'pre' : pre[obj.default], } return {'ok': True, 'row': row} @classmethod def remove(cls, id): q = Folios.delete().where(Folios.id==id) return bool(q.execute()) class Categorias(BaseModel): categoria = TextField() padre = ForeignKeyField('self', null=True, related_name='hijos') class Meta: order_by = ('categoria',) indexes = ( (('categoria', 'padre'), True), ) @classmethod def get_all(cls): rows = (Categorias.select( Categorias.id, Categorias.categoria.alias('value'), Categorias.padre.alias('parent_id')) ).dicts() return tuple(rows) class CondicionesPago(BaseModel): condicion = TextField(unique=True) class Meta: order_by = ('condicion',) class SATUnidades(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name') indexes = ( (('key', 'name'), True), ) @classmethod def get_activos(cls): rows = (SATUnidades .select( SATUnidades.id, SATUnidades.name.alias('value')) .where(SATUnidades.activo==True) .dicts() ) return tuple(rows) class SATFormaPago(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) @classmethod def get_activos(cls, values): field = SATFormaPago.id if values: field = SATFormaPago.key.alias('id') rows = (SATFormaPago .select(field, SATFormaPago.name.alias('value')) .where(SATFormaPago.activo==True) .dicts() ) return tuple(rows) class SATAduanas(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) class SATMonedas(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) @classmethod def get_activos(cls): rows = (SATMonedas .select( SATMonedas.key.alias('id'), SATMonedas.name.alias('value')) .where(SATMonedas.activo==True) .dicts() ) return tuple(rows) class SATImpuestos(BaseModel): key = TextField(index=True) name = TextField(default='', index=True) factor = TextField(default='T') tipo = TextField(default='T') tasa = DecimalField(default=0.0, decimal_places=6, auto_round=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'factor', 'tipo', 'tasa'), True), ) @classmethod def get_activos(self): rows = SATImpuestos.select().where(SATImpuestos.activo==True).dicts() return tuple(rows) class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) fisica = BooleanField(default=True) moral = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) @classmethod def get_activos(cls, values): field = SATUsoCfdi.id if values: field = SATUsoCfdi.key.alias('id') rows = (SATUsoCfdi .select( field, SATUsoCfdi.name.alias('value'), SATUsoCfdi.fisica, SATUsoCfdi.moral, ) .where(SATUsoCfdi.activo==True) .dicts() ) return tuple(rows) class Addendas(BaseModel): nombre = TextField(unique=True) addenda = TextField() class Meta: order_by = ('nombre',) class Socios(BaseModel): tipo_persona = IntegerField(default=1) rfc = TextField(index=True) nombre = TextField(index=True) slug = TextField(default='') nombre_comercial = TextField(index=True, default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='') codigo_postal = TextField(default='') notas = TextField(default='') telefonos = TextField(default='') es_activo = BooleanField(default=True) es_ong = BooleanField(default=False) fecha_alta = DateField(default=util.now) dias_pago = IntegerField(default=0) dias_habiles = BooleanField(default=False) es_cliente = BooleanField(default=False) es_proveedor = BooleanField(default=False) cuenta_cliente = TextField(default='') cuenta_proveedor = TextField(default='') web = TextField(default='') correo_facturas = TextField(default='') forma_pago = ForeignKeyField(SATFormaPago, null=True) condicion_pago = ForeignKeyField(CondicionesPago, null=True) addenda = ForeignKeyField(Addendas, null=True) uso_cfdi = ForeignKeyField(SATUsoCfdi, null=True) tags = ManyToManyField(Tags, related_name='socios_tags') def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) class Meta: order_by = ('nombre',) indexes = ( (('rfc', 'slug'), True), ) def _clean(self, values): fields = util.clean(values) fields['rfc'] = fields['rfc'].upper() fields['nombre'] = util.spaces(fields['nombre']) fields['slug'] = util.to_slug(fields['nombre']) fields['uso_cfdi'] = fields.pop('uso_cfdi_socio', None) fb = ('dias_habiles', 'es_activo', 'es_cliente', 'es_proveedor', 'es_ong') for name in fb: fields[name] = bool(fields[name].replace('0', '')) return fields @classmethod def get_(cls, values): if values: id = int(values['id']) row = Socios.select().where(Socios.id==id).dicts()[0] row['uso_cfdi_socio'] = row.pop('uso_cfdi') return row rows = Socios.select(Socios.id, Socios.rfc, Socios.nombre).dicts() return {'ok': True, 'rows': tuple(rows)} @classmethod def get_by_client(cls, values): id = int(values.get('id', 0)) if id: row = (Socios .select( Socios.id, Socios.nombre, Socios.rfc, SATFormaPago.key.alias('forma_pago'), SATUsoCfdi.key.alias('uso_cfdi')) .join(SATFormaPago).switch(Socios) .join(SATUsoCfdi).switch(Socios) .where( (Socios.id==id) & (Socios.es_cliente==True)) .dicts() ) if len(row): return {'ok': True, 'row': row[0]} return {'ok': False} name = values.get('name', '') if name: rows = (Socios .select(Socios.id, Socios.nombre, Socios.rfc, SATFormaPago.key.alias('forma_pago'), SATUsoCfdi.key.alias('uso_cfdi')) .join(SATFormaPago).switch(Socios) .join(SATUsoCfdi).switch(Socios) .where((Socios.es_cliente==True) & (Socios.rfc.contains(name) | Socios.nombre.contains(name))) .dicts()) return tuple(rows) return {'ok': False} @classmethod def add(cls, values): fields = cls._clean(cls, values) try: obj = Socios.create(**fields) except IntegrityError: msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data #~ ToDo Agregar Condicion de pago y tags row = { 'id': obj.id, 'rfc': obj.rfc, 'nombre': obj.nombre, } data = {'ok': True, 'row': row, 'new': True} return data @classmethod def actualizar(cls, values, id): fields = cls._clean(cls, values) try: q = Socios.update(**fields).where(Socios.id==id) q.execute() except IntegrityError: msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data row = { 'id': id, 'rfc': fields['rfc'], 'nombre': fields['nombre'], } data = {'ok': True, 'row': row, 'new': False} return data @classmethod def remove(cls, id): count = (Facturas .select(fn.COUNT(Facturas.id)).join(Socios) .where(Socios.id==id) .count()) if count: return False q = Socios.delete().where(Socios.id==id) return bool(q.execute()) class Productos(BaseModel): categoria = ForeignKeyField(Categorias, null=True) clave = TextField(unique=True, index=True) clave_sat = TextField() descripcion = TextField(index=True) unidad = ForeignKeyField(SATUnidades) valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True) ultimo_costo = DecimalField(default=0.0, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True) inventario = BooleanField(default=False) existencia = DoubleField(default=0.0) minimo = DoubleField(default=0.0) codigo_barras = TextField(default='') cuenta_predial = TextField(default='') es_activo = BooleanField(default=True) impuestos = ManyToManyField(SATImpuestos, related_name='productos') tags = ManyToManyField(Tags, related_name='productos_tags') class Meta: order_by = ('descripcion',) @classmethod def next_key(cls): value = Productos.select(fn.Max(Productos.id)).scalar() if value is None: value = 1 else: value += 1 return {'value': value} @classmethod def get_by(cls, values): id = int(values.get('id', 0)) if id: row = (Productos .select(Productos.id, Productos.clave, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario) .join(SATUnidades).switch(Productos) .where(Productos.id==id).dicts()) if len(row): model_pt = Productos.impuestos.get_through_model() taxes = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return {'ok': True, 'row': row[0], 'taxes': taxes} return {'ok': False} name = values.get('name', '') if name: rows = (Productos .select( Productos.id, Productos.clave, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario) .join(SATUnidades) .switch(Productos) .where(Productos.descripcion.contains(name)) .dicts() ) return tuple(rows) return {'ok': False} @classmethod def get_(cls, values): if values: id = int(values['id']) row = (Productos .select( Productos.id, Productos.es_activo.alias('es_activo_producto'), Productos.categoria, Productos.clave, Productos.clave_sat, Productos.descripcion, Productos.unidad, Productos.valor_unitario, ) .where(Productos.id==id).dicts()[0] ) obj = Productos.get(Productos.id==id) taxes = [row.id for row in obj.impuestos] return {'row': row, 'taxes': taxes} rows = (Productos .select( Productos.id, Productos.clave, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario) .join(SATUnidades) .dicts() ) return {'ok': True, 'rows': tuple(rows)} def _clean(self, values): taxes = util.loads(values.pop('taxes')) fields = util.clean(values) fields['es_activo'] = fields.pop('es_activo_producto') fields['descripcion'] = util.spaces(fields['descripcion']) fields['unidad'] = int(fields['unidad']) fields['valor_unitario'] = fields['valor_unitario'].replace( '$', '').replace(',', '') fb = ('es_activo', 'inventario') for name in fb: fields[name] = bool(fields[name].replace('0', '')) return fields, taxes @classmethod def add(cls, values): fields, taxes = cls._clean(cls, values) if Productos.select().where(Productos.clave==fields['clave']).exists(): msg = 'Clave ya existe' return {'ok': False, 'msg': msg} obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes)) with database_proxy.transaction(): obj = Productos.create(**fields) obj.impuestos = obj_taxes row = { 'id': obj.id, 'clave': obj.clave, 'descripcion': obj.descripcion, 'unidad': obj.unidad.name, 'valor_unitario': obj.valor_unitario, } data = {'ok': True, 'row': row, 'new': True} return data @classmethod def actualizar(cls, values, id): fields, taxes = cls._clean(cls, values) obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes)) with database_proxy.transaction(): q = Productos.update(**fields).where(Productos.id==id) try: q.execute() except IntegrityError: msg = 'Ya existe un producto con esta clave' data = {'ok': False, 'row': {}, 'new': False, 'msg': msg} return data obj = Productos.get(Productos.id==id) obj.impuestos = obj_taxes row = { 'id': obj.id, 'clave': obj.clave, 'descripcion': obj.descripcion, 'unidad': obj.unidad.name, 'valor_unitario': obj.valor_unitario, } data = {'ok': True, 'row': row, 'new': False} return data @classmethod def remove(cls, id): count = (FacturasDetalle .select(fn.COUNT(FacturasDetalle.id)).join(Productos) .where(Productos.id==id) .count() ) if count: return False with database_proxy.transaction(): obj = Productos.get(Productos.id==id) obj.impuestos.clear() obj.tags.clear() q = Productos.delete().where(Productos.id==id) return bool(q.execute()) class Facturas(BaseModel): cliente = ForeignKeyField(Socios) serie = TextField(default='') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) fecha_timbrado = DateTimeField(null=True) forma_pago = TextField(default='') condiciones_pago = TextField(default='') subtotal = DecimalField(default=0.0, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, decimal_places=6, auto_round=True) tipo_comprobante = TextField(default='I') metodo_pago = TextField(default='PUE') lugar_expedicion = TextField(default='') confirmacion = TextField(default='') uso_cfdi = TextField(default='') total_retenciones = DecimalField( decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( decimal_places=6, auto_round=True, null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardada') regimen_fiscal = TextField(default='') notas = TextField(default='') pagada = BooleanField(default=False) cancelada = BooleanField(default=False) error = TextField(default='') class Meta: order_by = ('fecha',) @classmethod def get_xml(cls, id): obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) return obj.xml, name def _get_data_cfdi_to_pdf(self, xml, cancel, version): pre_nomina = PRE['NOMINA'][version] data['comprobante']['letters'] = NumerosLetras().letters( float(data['comprobante']['total'])).upper() data['year'] = data['comprobante']['fecha'][0:4] data['month'] = data['comprobante']['fecha'][5:7] node = doc.find('{}Emisor'.format(pre)) data['emisor'] = node.attrib.copy() rfc_emisor = data['emisor']['rfc'] node = node.find('{}DomicilioFiscal'.format(pre)) if not node is None: data['emisor'].update(node.attrib.copy()) node = doc.find('{}Receptor'.format(pre)) data['receptor'] = node.attrib.copy() rfc_receptor = data['receptor']['rfc'] node = node.find('{}Domicilio'.format(pre)) if not node is None: data['receptor'].update(node.attrib.copy()) data['conceptos'] = [] conceptos = doc.find('{}Conceptos'.format(pre)) for c in conceptos.getchildren(): data['conceptos'].append(c.attrib.copy()) node = doc.find('{}Complemento/{}TimbreFiscalDigital'.format(pre, PRE['TIMBRE'])) data['timbre'] = node.attrib.copy() total_s = '%017.06f' % float(doc.attrib['total']) qr_data = '?re=%s&rr=%s&tt=%s&id=%s' % ( rfc_emisor, rfc_receptor, total_s, node.attrib['UUID']) data['timbre']['path_cbb'] = get_qr(node.attrib['UUID'], qr_data) data['timbre']['cadenaoriginal'] = CADENA.format(**node.attrib) data['nomina'] = {} node = doc.find('{}Complemento/{}Nomina'.format(pre, pre_nomina)) if not node is None: data['nomina']['nomina'] = node.attrib.copy() subnode = node.find('{}Emisor'.format(pre_nomina)) if not subnode is None: data['emisor'].update(subnode.attrib.copy()) subnode = node.find('{}Receptor'.format(pre_nomina)) data['receptor'].update(subnode.attrib.copy()) subnode = node.find('{}Percepciones'.format(pre_nomina)) data['nomina']['percepciones'] = subnode.attrib.copy() detalle = [] for n in subnode.getchildren(): if 'SeparacionIndemnizacion' in n.tag: continue detalle.append(n.attrib.copy()) data['nomina']['percepciones']['detalle'] = detalle data['nomina']['deducciones'] = None subnode = node.find('{}Deducciones'.format(pre_nomina)) if not subnode is None: data['nomina']['deducciones'] = subnode.attrib.copy() detalle = [] for n in subnode.getchildren(): detalle.append(n.attrib.copy()) data['nomina']['deducciones']['detalle'] = detalle data['nomina']['incapacidades'] = None subnode = node.find('{}Incapacidades'.format(pre_nomina)) if not subnode is None: detalle = [] for n in subnode.getchildren(): detalle.append(n.attrib.copy()) data['nomina']['incapacidades'] = detalle data['nomina']['otrospagos'] = None subnode = node.find('{}OtrosPagos'.format(pre_nomina)) if not subnode is None: data['nomina']['otrospagos'] = subnode.attrib.copy() detalle = [] for n in subnode.getchildren(): detalle.append(n.attrib.copy()) ns = n.find('{}SubsidioAlEmpleo'.format(pre_nomina)) if not ns is None: data['nomina']['otrospagos']['SubsidioCausado'] = ns.attrib['SubsidioCausado'] data['nomina']['otrospagos']['detalle'] = detalle return data @classmethod def get_pdf(cls, id, rfc): obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return b'', name path, data = util.get_data(obj, rfc) doc = util.to_pdf(path, data) return doc, name @classmethod def get_(cls, values): rows = tuple(Facturas .select(Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Facturas.total_mn, Socios.nombre.alias('cliente')) .join(Socios) .switch(Facturas).dicts() ) return {'ok': True, 'rows': rows} @classmethod def remove(cls, id): obj = Facturas.get(Facturas.id==id) if obj.uuid: return False q = FacturasDetalle.delete().where(FacturasDetalle.factura==obj) q.execute() q = FacturasImpuestos.delete().where(FacturasImpuestos.factura==obj) q.execute() return bool(obj.delete_instance()) def _get_folio(self, serie): inicio_serie = Folios.select( Folios.inicio).where(Folios.serie==serie).scalar() inicio = (Facturas .select(fn.Max(Facturas.folio)) .where(Facturas.serie==serie) .scalar()) if inicio is None: inicio = inicio_serie else: inicio += 1 return inicio def _calculate_totals(self, invoice, products): subtotal = 0 totals_tax = {} total_trasladados = None total_retenciones = None total_iva = 0 for product in products: id_product = product.pop('id') p = Productos.get(Productos.id==id_product) product['descripcion'] = p.descripcion product['unidad'] = p.unidad.key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['factura'] = invoice.id product['producto'] = id_product product['importe'] = round( float(product['cantidad']) * float(product['valor_unitario']), 2) subtotal += product['importe'] FacturasDetalle.create(**product) for tax in p.impuestos: if tax.id in totals_tax: totals_tax[tax.id]['importe'] += product['importe'] else: tax.importe = product['importe'] totals_tax[tax.id] = tax for tax in totals_tax.values(): if tax.tipo == 'E' or tax.tipo == 'R': continue import_tax = round(float(tax.tasa) * tax.importe, 2) total_trasladados = (total_trasladados or 0) + import_tax if tax.name == 'IVA': total_iva += import_tax invoice_tax = { 'factura': invoice.id, 'impuesto': tax.id, 'base': tax.importe, 'importe': import_tax, } 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, 2) else: import_tax = round(float(tax.tasa) * tax.importe, 2) total_retenciones = (total_retenciones or 0) + import_tax invoice_tax = { 'factura': invoice.id, 'impuesto': tax['id'], 'base': tax['importe'], 'importe': import_tax, } FacturasImpuestos.create(**invoice_tax) total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) total_mn = round(total * invoice.tipo_cambio, 2) data = { 'subtotal': subtotal, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, } return data @classmethod def add(cls, values): #~ print ('VALUES', values) productos = util.loads(values.pop('productos')) emisor = Emisor.select()[0] values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.codigo_postal with database_proxy.atomic() as txn: obj = Facturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) obj.subtotal = totals['subtotal'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] obj.total_mn = totals['total_mn'] obj.save() msg = 'Factura guardada correctamente. Enviando a timbrar' row = { 'id': obj.id, 'serie': obj.serie, 'folio': obj.folio, 'uuid': obj.uuid, 'fecha': obj.fecha, 'tipo_comprobante': obj.tipo_comprobante, 'estatus': obj.estatus, 'total_mn': obj.total_mn, 'cliente': obj.cliente.nombre, } data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data def _make_xml(self, invoice): emisor = Emisor.select()[0] certificado = Certificado.select()[0] comprobante = {} if invoice.serie: comprobante['Serie'] = invoice.serie if invoice.condiciones_pago: comprobante['CondicionesDePago'] = invoice.condiciones_pago if invoice.descuento: comprobante['Descuento'] = invoice.descuento comprobante['Folio'] = str(invoice.folio) comprobante['Fecha'] = invoice.fecha.isoformat()[:19] comprobante['FormaPago'] = invoice.forma_pago comprobante['NoCertificado'] = certificado.serie comprobante['Certificado'] = certificado.cer_txt comprobante['SubTotal'] = FORMAT.format(invoice.subtotal) comprobante['Moneda'] = invoice.moneda comprobante['TipoCambio'] = '1' if comprobante['Moneda'] != 'MXN': comprobante['TipoCambio'] = FORMAT.format(invoice.tipo_cambio) comprobante['Total'] = FORMAT.format(invoice.total) comprobante['TipoDeComprobante'] = invoice.tipo_comprobante comprobante['MetodoPago'] = invoice.metodo_pago comprobante['LugarExpedicion'] = invoice.lugar_expedicion emisor = { 'Rfc': emisor.rfc, 'Nombre': emisor.nombre, 'RegimenFiscal': invoice.regimen_fiscal, } receptor = { 'Rfc': invoice.cliente.rfc, 'Nombre': invoice.cliente.nombre, 'UsoCFDI': invoice.uso_cfdi, } conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: concepto = { 'ClaveProdServ': row.producto.clave_sat, 'NoIdentificacion': row.producto.clave, 'Cantidad': FORMAT.format(row.cantidad), 'ClaveUnidad': row.producto.unidad.key, 'Unidad': row.producto.unidad.name, 'Descripcion': row.producto.descripcion, 'ValorUnitario': FORMAT.format(row.valor_unitario), 'Importe': FORMAT.format(row.importe), } taxes = {} traslados = [] retenciones = [] for impuesto in row.producto.impuestos: if impuesto.tipo == 'E': continue import_tax = round(impuesto.tasa * row.importe, 2) tipo_factor = 'Tasa' if impuesto.factor != 'T': tipo_factor = 'Cuota' tax = { "Base": FORMAT.format(row.importe), "Impuesto": impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(impuesto.tasa), "Importe": FORMAT.format(import_tax), } if impuesto.tipo == 'T': traslados.append(tax) else: retenciones.append(tax) taxes['traslados'] = traslados taxes['retenciones'] = retenciones concepto['impuestos'] = taxes conceptos.append(concepto) impuestos = {} traslados = [] retenciones = [] if not invoice.total_trasladados is None: impuestos['TotalImpuestosTrasladados'] = \ FORMAT.format(invoice.total_trasladados) if not invoice.total_retenciones is None: impuestos['TotalImpuestosRetenidos'] = \ FORMAT.format(invoice.total_retenciones) taxes = (FacturasImpuestos .select() .where(FacturasImpuestos.factura==invoice)) for tax in taxes: tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' if tax.impuesto.tipo == 'T': traslado = { "Impuesto": tax.impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(tax.impuesto.tasa), "Importe": FORMAT.format(tax.importe), } traslados.append(traslado) else: retencion = { "Impuesto": tax.impuesto.key, "Importe": FORMAT.format(tax.importe), } retenciones.append(retencion) impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones data = { 'comprobante': comprobante, 'emisor': emisor, 'receptor': receptor, 'conceptos': conceptos, 'impuestos': impuestos, } return util.make_xml(data, certificado) @classmethod def timbrar(cls, id): obj = Facturas.get(Facturas.id == id) obj.xml = cls._make_xml(cls, obj) obj.estatus = 'Generada' obj.save() error = False msg = 'Factura timbrada correctamente' result = util.timbra_xml(obj.xml) if result['ok']: obj.xml = result['xml'] obj.uuid = result['uuid'] obj.fecha_timbrado = result['fecha'] obj.estatus = 'Timbrada' obj.save() row = {'uuid': obj.uuid, 'estatus': 'Timbrada'} else: error = True msg = result['error'] obj.estatus = 'Error' obj.error = msg row = {'estatus': 'Error'} obj.save() return {'ok': result['ok'], 'msg': msg, 'row': row} class FacturasDetalle(BaseModel): factura = ForeignKeyField(Facturas) producto = ForeignKeyField(Productos, null=True) cantidad = DecimalField(default=0.0, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, decimal_places=6, auto_round=True) descripcion = TextField(default='') unidad = TextField(default='') clave = TextField(default='') clave_sat = TextField(default='') categoria = TextField(default='') aduana = TextField(default='') pedimento = TextField(default='') fecha_pedimento = DateField(null=True) alumno = TextField(default='') curp = TextField(default='') nivel = TextField(default='') autorizacion = TextField(default='') cuenta_predial = TextField(default='') class Meta: order_by = ('factura',) class FacturasImpuestos(BaseModel): factura = ForeignKeyField(Facturas) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('factura', 'impuesto'), True), ) def authenticate(args): respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''} values = util.get_con(args['rfc']) if not values: return respuesta conectar(values) try: obj = Usuarios.get(usuario=args['usuario'], es_activo=True) except Usuarios.DoesNotExist: return respuesta if not obj.contraseña.check_password(args['contra']): return respuesta obj.ultimo_ingreso = util.now() obj.save() respuesta['msg'] = '' respuesta['login'] = True respuesta['user'] = str(obj) respuesta['super'] = obj.es_superusuario #~ desconectar() return respuesta def get_cp(cp): con = sqlite3.connect(PATH_CP) cursor = con.cursor() sql = """ SELECT colonia, municipio, estado FROM colonias, municipios, estados WHERE colonias.id_municipio=municipios.id AND municipios.id_estado=estados.id AND cp=? ORDER BY colonia""" cursor.execute(sql, (cp,)) rows = cursor.fetchall() cursor.close() con.close() data = {} if rows: data = { 'estado': rows[0][2], 'municipio': rows[0][1], } if len(rows) == 1: data['colonia'] = rows[0][0] else: data['colonia'] = [r[0] for r in rows] return data def get_sat_key(key): return util.get_sat_key('products', key) def _init_values(): data = ( {'key': 'version', 'value': VERSION}, {'key': 'rfc_publico', 'value': 'XAXX010101000'}, {'key': 'rfc_extranjero', 'value': 'XEXX010101000'}, ) for row in data: try: Configuration.create(**row) except IntegrityError: pass log.info('Valores iniciales insertados...') return def _crear_tablas(): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, Emisor, Facturas, FacturasDetalle, FacturasImpuestos, Folios, Productos, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATUnidades, SATUsoCfdi, Socios, Tags, Usuarios, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), Productos.tags.get_through_model(), ] database_proxy.create_tables(tablas, True) log.info('Tablas creadas correctamente...') return True def migrate_tables(): connect() log.info('Tablas migradas correctamente...') return def _agregar_superusuario(): args = util.get_con() if not args: return conectar(args) usuario = input('Introduce el nuevo nombre para el superusuario: ').strip() if not usuario: msg = 'El nombre de usuario es requerido' log.erro(msg) return ok, contraseña = util.get_pass() if not ok: log.error(contraseña) return try: obj = Usuarios.create( usuario=usuario, contraseña=contraseña, es_superusuario=True) except IntegrityError: msg = 'El usuario ya existe' log.error(msg) return log.info('SuperUsuario creado correctamente...') return def _cambiar_contraseña(): args = util.get_con() if not args: return conectar(args) usuario = input('Introduce el nombre de usuario: ').strip() if not usuario: msg = 'El nombre de usuario es requerido' log.error(msg) return try: obj = Usuarios.get(usuario=usuario) except Usuarios.DoesNotExist: msg = 'El usuario no existe' log.error(msg) return ok, contraseña = util.get_pass() if not ok: log.error(contraseña) return obj.contraseña = contraseña obj.save() log.info('Contraseña cambiada correctamente...') return def _add_emisor(rfc, args): con = sqlite3.connect(COMPANIES) cursor = con.cursor() sql = """ INSERT INTO names VALUES (?, ?)""" try: cursor.execute(sql, (rfc, args)) except sqlite3.IntegrityError as e: log.error(e) return False con.commit() cursor.close() con.close() return True def _iniciar_bd(): 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) if _crear_tablas(): return log.error('No se pudieron crear las tablas') return def _agregar_rfc(): rfc = input('Introduce el nuevo RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return datos = input('Introduce los datos de conexión: ').strip() if not datos: msg = 'Los datos de conexión son requeridos' log.error(msg) return opt = util.parse_con(datos) if not opt: log.error('Datos de conexión incompletos') return args = opt.copy() if conectar(args): if _add_emisor(rfc, util.dumps(opt)) and _crear_tablas(): log.info('RFC agregado correctamente...') return log.error('No se pudo agregar el RFC') return def _listar_rfc(): data = util.get_rfcs() for row in data: msg = 'RFC: {}\n\t{}'.format(row[0], row[1]) log.info(msg) return def _importar_valores(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...') regimen = '' rows = util.loads(open(archivo, 'r').read()) for row in rows: log.info('\tImportando tabla: {}'.format(row['tabla'])) if row['tabla'] == 'Emisor' and 'regimen' in row: regimen = row['regimen'] table = globals()[row['tabla']] for r in row['datos']: try: table.create(**r) except IntegrityError: pass if regimen: emisor = Emisor.select()[0] regimen = SATRegimenes.get(SATRegimenes.key == regimen) emisor.regimenes.clear() emisor.regimenes.add(regimen) log.info('Importación terminada...') 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' help_superuser = 'Crea un nuevo super usuario' help_change_pass = 'Cambia la contraseña a un usuario' help_rfc = 'Agrega un nuevo RFC' help_br = 'Elimina un RFC' help_lr = 'Listar RFCs' @click.command(context_settings=CONTEXT_SETTINGS) @click.option('-bd', '--iniciar-bd',help=help_create_tables, is_flag=True, default=False) @click.option('-m', '--migrar-bd', help=help_migrate_db, is_flag=True, default=False) @click.option('-ns', '--nuevo-superusuario', help=help_superuser, is_flag=True, default=False) @click.option('-cc', '--cambiar-contraseña', help=help_change_pass, is_flag=True, default=False) @click.option('-rfc', '--rfc', help=help_rfc, is_flag=True, default=False) @click.option('-br', '--borrar-rfc', help=help_br, is_flag=True, default=False) @click.option('-lr', '--listar-rfc', help=help_lr, is_flag=True, default=False) @click.option('-i', '--importar_valores', is_flag=True, default=False) @click.option('-a', '--archivo') def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, borrar_rfc, listar_rfc, importar_valores, archivo): opt = locals() if opt['iniciar_bd']: _iniciar_bd() sys.exit(0) if opt['migrar_bd']: migrate_tables() sys.exit(0) if opt['nuevo_superusuario']: _agregar_superusuario() sys.exit(0) if opt['cambiar_contraseña']: _cambiar_contraseña() sys.exit(0) if opt['rfc']: _agregar_rfc() sys.exit(0) if opt['borrar_rfc']: _borrar_rfc() sys.exit(0) if opt['listar_rfc']: _listar_rfc() sys.exit(0) if opt['importar_valores']: if not opt['archivo']: msg = 'Falta la ruta del archivo importar' raise click.ClickException(msg) if not util.is_file(opt['archivo']): msg = 'No es un archivo' raise click.ClickException(msg) _importar_valores(opt['archivo']) sys.exit(0) return if __name__ == '__main__': main()