├── dfe ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── emitentes ├── views.py ├── __init__.py ├── api │ ├── __init__.py │ ├── viewsets.py │ └── serializers.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── tests.py ├── apps.py ├── validators.py ├── admin.py └── models.py ├── nfe ├── __init__.py ├── api │ ├── __init__.py │ └── viewsets.py ├── migrations │ ├── __init__.py │ ├── 0002_alter_nfe_situacao.py │ └── 0001_initial.py ├── tests.py ├── views.py ├── apps.py ├── admin.py └── models.py ├── requirements ├── dev.txt └── base.txt ├── requirements.txt ├── manage.py ├── contrib └── env_gen.py ├── README.md └── .gitignore /dfe/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /emitentes/views.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nfe/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /emitentes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nfe/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /emitentes/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nfe/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /emitentes/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | ipdb 3 | isort 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/base.txt 2 | PyNFe==0.4 3 | -------------------------------------------------------------------------------- /nfe/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /nfe/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /emitentes/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /nfe/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NfeConfig(AppConfig): 5 | name = 'nfe' 6 | -------------------------------------------------------------------------------- /emitentes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EmitenteConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'emitentes' 7 | -------------------------------------------------------------------------------- /nfe/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Nfe 4 | 5 | 6 | class NfeAdmin(admin.ModelAdmin): 7 | list_display = ('emitente_id', 'serie', 'numero', 'numero_carta_correcao', 'chave', 'data_emissao', 'situacao', 8 | 'status_sefaz') 9 | 10 | 11 | admin.site.register(Nfe, NfeAdmin) 12 | -------------------------------------------------------------------------------- /emitentes/validators.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | 4 | def validate_certificado_a1(value): 5 | if not value: 6 | raise serializers.ValidationError('Certificado A1 não pode estar vazio') # noqa E501 7 | return value 8 | 9 | 10 | def validate_logotipo_dfe(value): 11 | if not value: 12 | raise serializers.ValidationError('Logo Tipo não pode estar vazio') 13 | return value 14 | -------------------------------------------------------------------------------- /dfe/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for dfe project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dfe.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /emitentes/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Emitente 4 | 5 | 6 | class EmitenteAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | 'nome', 9 | 'token', 10 | 'cnpj', 11 | 'cpf', 12 | 'municipio', 13 | 'uf', 14 | 'telefones', 15 | 'email', 16 | 'emissoes_suspensas', 17 | 'is_active' 18 | ) 19 | 20 | 21 | admin.site.register(Emitente, EmitenteAdmin) 22 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.3.4 2 | certifi==2020.12.5 3 | cffi==1.14.5 4 | chardet==4.0.0 5 | cryptography==3.4.7 6 | dj-database-url==0.5.0 7 | Django==3.2 8 | django_extensions 9 | djangorestframework==3.12.4 10 | eight==1.0.1 11 | future==0.18.2 12 | idna==2.10 13 | lxml==4.6.3 14 | Pillow==8.2.0 15 | pycparser==2.20 16 | pyOpenSSL==19.1.0 17 | python-decouple==3.4 18 | pytz==2021.1 19 | requests==2.25.1 20 | signxml==2.8.1 21 | six==1.15.0 22 | sqlparse==0.4.1 23 | typing-extensions==3.7.4.3 24 | urllib3==1.26.4 25 | drf-yasg 26 | -------------------------------------------------------------------------------- /nfe/migrations/0002_alter_nfe_situacao.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-04-23 20:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('nfe', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='nfe', 15 | name='situacao', 16 | field=models.CharField(choices=[('PRO', 'processando_autorizacao'), ('AUT', 'autorizado'), ('CAN', 'cancelado'), ('ERR', 'erro_autorizacao'), ('DEN', 'denegado')], max_length=3, verbose_name='Situação'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dfe.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /contrib/env_gen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python SECRET_KEY generator. 3 | """ 4 | import random 5 | 6 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%^&*()" 7 | size = 50 8 | secret_key = "".join(random.sample(chars, size)) 9 | 10 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%_" 11 | size = 20 12 | password = "".join(random.sample(chars, size)) 13 | 14 | CONFIG_STRING = """ 15 | DEBUG=True 16 | SECRET_KEY=%s 17 | ALLOWED_HOSTS=127.0.0.1,.localhost,0.0.0.0 18 | 19 | #DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME 20 | #POSTGRES_DB= 21 | #POSTGRES_USER= 22 | #POSTGRES_PASSWORD=%s 23 | #DB_HOST=localhost 24 | 25 | #DEFAULT_FROM_EMAIL= 26 | #EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 27 | #EMAIL_HOST=localhost 28 | #EMAIL_PORT= 29 | #EMAIL_HOST_USER= 30 | #EMAIL_HOST_PASSWORD= 31 | #EMAIL_USE_TLS=True 32 | """.strip() % (secret_key, password) 33 | 34 | # Writing our configuration file to '.env' 35 | with open('.env', 'w') as configfile: 36 | configfile.write(CONFIG_STRING) 37 | 38 | print('Success!') 39 | print('Type: cat .env') 40 | -------------------------------------------------------------------------------- /nfe/api/viewsets.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import IsAuthenticated 2 | from rest_framework.response import Response 3 | from rest_framework.views import APIView 4 | 5 | 6 | def enviar_nfe(data, ref): 7 | if type(data) == dict: 8 | # processar json-dict, gerar xml, assinar, validar e enviar a Sefaz via assíncrona 9 | 10 | respond = { 11 | "http_code": 202, 12 | "cnpj_emitente": "07504505000132", 13 | "ref": ref, 14 | "status": "processando_autorizacao", 15 | "mensagem": "Processando autorização" 16 | } 17 | else: 18 | respond = { 19 | "http_code": 400, 20 | "codigo": "json_parse_error", 21 | "mensagem": "Bad Request - O texto não é um formato json válido" 22 | } 23 | 24 | return respond 25 | 26 | 27 | class NfeViewSet(APIView): 28 | permission_classes = (IsAuthenticated,) 29 | 30 | # Recebe o JSON da NFe, gera o XML, Assina, Valida e envia à Sefaz se não houver erro 31 | def post(self, request): 32 | json_file = request.data 33 | if 'ref' in request.headers: 34 | refer = request.headers['ref'] 35 | else: 36 | refer = None 37 | response = enviar_nfe(data=json_file, ref=refer) 38 | # print(response) 39 | http_code = response['http_code'] 40 | del response['http_code'] 41 | return Response(response, status=http_code) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DFe 2 | 3 | ### API REST FULL DFe - Documento Fiscal Eletrônico em DRF 4 | 5 | Webservice (API) para emissão de Documentos Eletrônicos Fiscais (DFe) 6 | - [ ] NFe 7 | - [ ] NFCe 8 | - [ ] CTe 9 | - [ ] MDFe 10 | 11 | ## Dependências 12 | 13 | - [Python](https://www.python.org/downloads/ "Python Download") - Versão 3.7.10 (security) 14 | - [Django](https://www.djangoproject.com/download/ "Django Download") - Versão 3.2 LTS 15 | - [DjangoRestFramework](https://www.django-rest-framework.org/ "Django REST Framework") 16 | - [Python-Decouple](https://pypi.org/project/python-decouple/ "Python-decouple") 17 | - [dj-database-url](https://pypi.org/project/dj-database-url/ "Use Database URLs in your Django Application") 18 | 19 | 20 | ## Como rodar o projeto? 21 | 22 | * Clone esse repositório. 23 | * Crie um virtualenv com Python 3. 24 | * Ative o virtualenv. 25 | * Instale as dependências. 26 | * Rode as migrações. 27 | 28 | ``` 29 | git clone https://github.com/nilton-medeiros/DFe.git 30 | cd DFe 31 | python3 -m venv .venv 32 | source .venv/bin/activate 33 | 34 | # Dev 35 | pip install -r requirements/dev.txt 36 | 37 | # Produção 38 | pip install -r requirements.txt 39 | 40 | python contrib/env_gen.py 41 | python manage.py migrate 42 | python manage.py createsuperuser --username="admin" --email="" 43 | ``` 44 | 45 | ## Doc 46 | 47 | Documentação usando [drf-yasg](https://drf-yasg.readthedocs.io/en/stable). 48 | 49 | 50 | ``` 51 | http://localhost:8000/swagger/ 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /dfe/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import url 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | from django.urls import include, path 6 | from drf_yasg import openapi 7 | from drf_yasg.views import get_schema_view 8 | from rest_framework import permissions, routers 9 | 10 | from emitentes.api.viewsets import EmitenteViewSet 11 | from nfe.api.viewsets import NfeViewSet 12 | 13 | schema_view = get_schema_view( 14 | openapi.Info( 15 | title="Snippets API", 16 | default_version='v1', 17 | description="Test description", 18 | terms_of_service="https://www.google.com/policies/terms/", 19 | contact=openapi.Contact(email="contact@snippets.local"), 20 | license=openapi.License(name="BSD License"), 21 | ), 22 | public=True, 23 | permission_classes=(permissions.AllowAny,), 24 | ) 25 | 26 | router = routers.DefaultRouter() 27 | router.register(r'emitente', EmitenteViewSet) 28 | 29 | swagger_urlpatterns = [ 30 | url( 31 | r'^swagger(?P\.json|\.yaml)$', 32 | schema_view.without_ui(cache_timeout=0), 33 | name='schema-json'), 34 | url( 35 | r'^swagger/$', 36 | schema_view.with_ui('swagger', cache_timeout=0), 37 | name='schema-swagger-ui'), 38 | url( 39 | r'^redoc/$', 40 | schema_view.with_ui('redoc', cache_timeout=0), 41 | name='schema-redoc'), 42 | ] 43 | 44 | urlpatterns = [ 45 | path('admin/', admin.site.urls), 46 | path('', include(router.urls)), 47 | path('nfe/', NfeViewSet.as_view()), 48 | ] + swagger_urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 49 | -------------------------------------------------------------------------------- /nfe/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-04-23 19:09 2 | 3 | import uuid 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | import nfe.models 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('emitentes', '0001_initial'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Nfe', 22 | fields=[ 23 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 24 | ('serie', models.IntegerField(default=1, verbose_name='Série NFe')), 25 | ('numero', models.IntegerField(default=1, verbose_name='Número NFe')), 26 | ('numero_carta_correcao', models.IntegerField(default=0, verbose_name='Número Carta Cor.')), 27 | ('chave', models.CharField(max_length=44, verbose_name='Chave NFe')), 28 | ('data_emissao', models.DateField(verbose_name='Emitido Em')), 29 | ('situacao', models.CharField(max_length=25, verbose_name='Situação')), 30 | ('status_sefaz', models.CharField(max_length=3, verbose_name='Status Sefaz')), 31 | ('mensagem_sefaz', models.CharField(max_length=300, verbose_name='Mensagem Sefaz')), 32 | ('url_xml_nfe', models.FileField(blank=True, null=True, upload_to=nfe.models.nfe_upload_file)), 33 | ('url_pdf_nfe', models.FileField(blank=True, null=True, upload_to=nfe.models.nfe_upload_file)), 34 | ('url_xml_cancelado', models.FileField(blank=True, null=True, upload_to=nfe.models.nfe_upload_file)), 35 | ('url_pdf_cancelado', models.FileField(blank=True, null=True, upload_to=nfe.models.nfe_upload_file)), 36 | ('url_xml_carta_correcao', models.FileField(blank=True, null=True, upload_to=nfe.models.nfe_upload_file)), 37 | ('url_pdf_carta_correcao', models.FileField(blank=True, null=True, upload_to=nfe.models.nfe_upload_file)), 38 | ('emitente_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='emitentes.emitente')), 39 | ], 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /nfe/models.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | from django.db import models 4 | 5 | from emitentes.models import Emitente 6 | 7 | 8 | def nfe_upload_file(instance, filename): 9 | emitente = Emitente.objects.get(pk=instance.emitente_id) 10 | 11 | if emitente.cnpj: 12 | doc = emitente.cnpj 13 | else: 14 | doc = emitente.cpf 15 | 16 | path: str = f"{doc}/nfe/{filename[filename.rfind('.')+1:].upper()}s/" 17 | path += str(instance.data_emissao.year) + '/' + str(instance.data_emissao.month) + '/' + filename 18 | return path 19 | 20 | 21 | # Controle de NFe's do ambiente de Produção 22 | class Nfe(models.Model): 23 | # Este id do tipo token (UUID4) é retornado ao solicitante como ref (referencia) a esta NFe para consultas 24 | id = models.UUIDField(primary_key=True, default=uuid4, editable=False) 25 | emitente_id = models.ForeignKey(Emitente, on_delete=models.CASCADE) 26 | serie = models.IntegerField('Série NFe', default=1) 27 | numero = models.IntegerField('Número NFe', default=1) 28 | numero_carta_correcao = models.IntegerField('Número Carta Cor.', default=0) 29 | chave = models.CharField('Chave NFe', max_length=44) 30 | data_emissao = models.DateField('Emitido Em') 31 | 32 | SITUATION_STATUS = ( 33 | ('PRO', 'processando_autorizacao'), 34 | ('AUT', 'autorizado'), 35 | ('CAN', 'cancelado'), 36 | ('ERR', 'erro_autorizacao'), 37 | ('DEN', 'denegado'), 38 | ) 39 | situacao = models.CharField('Situação', max_length=3, choices=SITUATION_STATUS) 40 | status_sefaz = models.CharField('Status Sefaz', max_length=3) 41 | mensagem_sefaz = models.CharField('Mensagem Sefaz', max_length=300) 42 | url_xml_nfe = models.FileField(upload_to=nfe_upload_file, null=True, blank=True) 43 | url_pdf_nfe = models.FileField(upload_to=nfe_upload_file, null=True, blank=True) 44 | url_xml_cancelado = models.FileField(upload_to=nfe_upload_file, null=True, blank=True) 45 | url_pdf_cancelado = models.FileField(upload_to=nfe_upload_file, null=True, blank=True) 46 | url_xml_carta_correcao = models.FileField(upload_to=nfe_upload_file, null=True, blank=True) 47 | url_pdf_carta_correcao = models.FileField(upload_to=nfe_upload_file, null=True, blank=True) 48 | -------------------------------------------------------------------------------- /emitentes/api/viewsets.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status, viewsets 2 | from rest_framework.authentication import ( 3 | BasicAuthentication, 4 | TokenAuthentication 5 | ) 6 | from rest_framework.decorators import action 7 | from rest_framework.permissions import AllowAny, IsAuthenticated 8 | from rest_framework.response import Response 9 | 10 | from emitentes import models 11 | from emitentes.api import serializers 12 | 13 | 14 | class EmitenteViewSet(viewsets.ModelViewSet): 15 | serializer_class = serializers.EmitenteSerializer 16 | queryset = models.Emitente.objects.all() 17 | permission_classes = (IsAuthenticated,) 18 | authentication_classes = (BasicAuthentication, TokenAuthentication) 19 | 20 | def get_serializer_class(self): 21 | serializers_choices = { 22 | 'nfe_emit': serializers.NFeEmiteSerializer, 23 | 'nfce_emit': serializers.NFCeEmiteSerializer, 24 | 'cte_emit': serializers.CTeEmiteSerializer, 25 | 'mdfe_emit': serializers.MDFeEmiteSerializer 26 | } 27 | return serializers_choices.get(self.action, serializers.EmitenteSerializer) 28 | 29 | def list(self, request, *args, **kwargs): 30 | queryset = self.get_queryset() 31 | user = request.user 32 | if user.is_authenticated: 33 | queryset = queryset.filter(user_id=user) 34 | serializer = self.get_serializer(queryset, many=True) 35 | return Response(serializer.data) 36 | 37 | def perform_create(self, serializer): 38 | serializer.save(user_id=self.resquest.user) 39 | 40 | def partial_update(self, request, *args, **kwargs): 41 | return super(EmitenteViewSet, self).partial_update(request, *args, **kwargs) 42 | 43 | @action(detail=True, url_path='nfe-emit', name="nfe-emit", methods=['get', 'put']) 44 | def nfe_emit(self, request, *args, **kwargs): 45 | return self.update(request, *args, **kwargs) 46 | 47 | @action(detail=True, url_path='nfce-emit', name="nfce-emit", methods=['get', 'put']) 48 | def nfce_emit(self, request, *args, **kwargs): 49 | return self.update(request, *args, **kwargs) 50 | 51 | @action(detail=True, url_path='cte-emit', name="cte-emit", methods=['get', 'put']) 52 | def cte_emit(self, request, *args, **kwargs): 53 | return self.update(request, *args, **kwargs) 54 | 55 | @action(detail=True, url_path='mdfe-emit', name="mdfe-emit", methods=['get', 'put']) 56 | def mdfe_emit(self, request, *args, **kwargs): 57 | return self.update(request, *args, **kwargs) 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | db.dfe 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | .idea 131 | *pyc 132 | 133 | # Temporary files 134 | temp/ 135 | 136 | # Client files 137 | client_files/ -------------------------------------------------------------------------------- /emitentes/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from emitentes import models, validators 4 | 5 | 6 | class NFeEmiteSerializer(serializers.ModelSerializer): 7 | certificado_a1 = serializers.FileField(validators=[validators.validate_certificado_a1]) # noqa E501 8 | logotipo_dfe = serializers.FileField(validators=[validators.validate_logotipo_dfe]) # noqa E501 9 | 10 | class Meta: 11 | model = models.Emitente 12 | fields = ( 13 | 'id', 14 | 'nfe_serie', 15 | 'logotipo_dfe', 16 | 'certificado_a1', 17 | 'nfe_serie_homologacao', 18 | 'nfe_numero_homologacao', 19 | ) 20 | 21 | 22 | class NFCeEmiteSerializer(serializers.ModelSerializer): 23 | certificado_a1 = serializers.FileField(validators=[validators.validate_certificado_a1]) # noqa E501 24 | logotipo_dfe = serializers.FileField(validators=[validators.validate_logotipo_dfe]) # noqa E501 25 | 26 | class Meta: 27 | model = models.Emitente 28 | fields = ( 29 | 'id', 30 | 'nfce_serie', 31 | 'logotipo_dfe', 32 | 'certificado_a1', 33 | 'nfce_serie_homologacao', 34 | 'nfce_numero_homologacao', 35 | ) 36 | 37 | 38 | class CTeEmiteSerializer(serializers.ModelSerializer): 39 | certificado_a1 = serializers.FileField(validators=[validators.validate_certificado_a1]) # noqa E501 40 | logotipo_dfe = serializers.FileField(validators=[validators.validate_logotipo_dfe]) # noqa E501 41 | 42 | class Meta: 43 | model = models.Emitente 44 | fields = ( 45 | 'id', 46 | 'cte_serie', 47 | 'logotipo_dfe', 48 | 'certificado_a1', 49 | 'cte_serie_homologacao', 50 | 'cte_numero_homologacao', 51 | ) 52 | 53 | 54 | class MDFeEmiteSerializer(serializers.ModelSerializer): 55 | certificado_a1 = serializers.FileField(validators=[validators.validate_certificado_a1]) # noqa E501 56 | logotipo_dfe = serializers.FileField(validators=[validators.validate_logotipo_dfe]) # noqa E501 57 | 58 | class Meta: 59 | model = models.Emitente 60 | fields = ( 61 | 'id', 62 | 'mdfe_serie', 63 | 'logotipo_dfe', 64 | 'certificado_a1', 65 | 'mdfe_serie_homologacao', 66 | 'mdfe_numero_homologacao', 67 | ) 68 | 69 | 70 | class EmitenteSerializer(serializers.ModelSerializer): 71 | certificado_a1 = serializers.FileField(validators=[validators.validate_certificado_a1]) # noqa E501 72 | logotipo_dfe = serializers.FileField(validators=[validators.validate_logotipo_dfe]) # noqa E501 73 | 74 | class Meta: 75 | model = models.Emitente 76 | fields = ( 77 | 'id', 78 | 'nome', 79 | 'nfe_numero', 80 | 'nfe_serie_homologacao', 81 | 'certificado_a1', 82 | 'logotipo_dfe' 83 | ) 84 | 85 | def to_representation(self, instance): 86 | data = { 87 | 'id': instance.id, 88 | 'nome': instance.nome, 89 | } 90 | if instance.cnpj: 91 | data['cnpj'] = instance.cnpj 92 | else: 93 | data['cpf'] = instance.cpf 94 | 95 | for emit in 'nfe_emite nfce_emite cte_emite mdfe_emite'.split(): 96 | base_name = emit.split('_')[0] 97 | if getattr(instance, emit): 98 | data[f'{base_name}_serie'] = getattr(instance, f'{base_name}_serie') # noqa E501 99 | data[f'{base_name}_numero'] = getattr(instance, f'{base_name}_numero') # noqa E501 100 | data[f'{base_name}_serie_homologacao'] = getattr(instance, f'{base_name}_serie_homologacao') # noqa E501 101 | data[f'{base_name}_numero_homologacao'] = getattr(instance, f'{base_name}_numero_homologacao') # noqa E501 102 | 103 | return data 104 | -------------------------------------------------------------------------------- /emitentes/models.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | from django.contrib.auth.models import User 4 | from django.db import models 5 | 6 | 7 | def emitente_upload_file(instance, filename): 8 | if instance.cnpj: 9 | doc = instance.cnpj 10 | else: 11 | doc = instance.cpf 12 | 13 | path: str = f"{doc}/media/" 14 | path += filename 15 | return path 16 | 17 | 18 | class Emitente(models.Model): 19 | user_id = models.ForeignKey(User, on_delete=models.RESTRICT) 20 | nome = models.CharField('Nome', max_length=60) 21 | cnpj = models.CharField('CNPJ', max_length=14, null=True, blank=True) 22 | inscricao_estadual = models.CharField('Inscrição Estadual', max_length=14, null=True, blank=True) 23 | cpf = models.CharField('CPF', max_length=11, null=True, blank=True) 24 | logradouro = models.CharField(max_length=60) 25 | numero = models.CharField(max_length=40) 26 | complemento = models.CharField(max_length=60, null=True, blank=True) 27 | bairro = models.CharField(max_length=60) 28 | municipio = models.CharField(max_length=60) 29 | uf = models.CharField('UF', max_length=2) 30 | cep = models.CharField('CEP', max_length=8) 31 | telefones = models.CharField(max_length=60, null=True, blank=True) 32 | email = models.EmailField('E-mail', max_length=100, null=True, blank=True) 33 | certificado_a1 = models.FileField(upload_to=emitente_upload_file, null=True, blank=True) 34 | logotipo_dfe = models.ImageField(upload_to=emitente_upload_file, null=True, blank=True) 35 | 36 | # DFe's - Controle de Emissões, séries e numeração ambiente Produção & Homologação (testes) 37 | 38 | nfe_emite = models.BooleanField('Emite NFe', default=False) 39 | nfe_serie = models.IntegerField('Série NFe', default=0) 40 | nfe_numero = models.IntegerField('Número NFe', default=0) 41 | nfe_serie_homologacao = models.IntegerField('Série NFe em homologação', default=0) 42 | nfe_numero_homologacao = models.IntegerField('Número NFe em homologação', default=0) 43 | 44 | nfce_emite = models.BooleanField('Emite NFCe', default=False) 45 | nfce_serie = models.IntegerField('Série NFCe', default=0) 46 | nfce_numero = models.IntegerField('Número NFCe', default=0) 47 | nfce_serie_homologacao = models.IntegerField('Série NFCe em homologação', default=0) 48 | nfce_numero_homologacao = models.IntegerField('Número NFCe em homologação', default=0) 49 | 50 | cte_emite = models.BooleanField('Emite CTe', default=False) 51 | cte_serie = models.IntegerField('Série CTe', default=0) 52 | cte_numero = models.IntegerField('Número CTe', default=0) 53 | cte_serie_homologacao = models.IntegerField('Série CTe', default=0) 54 | cte_numero_homologacao = models.IntegerField('Número CTe em homologação', default=0) 55 | 56 | mdfe_emite = models.BooleanField('Emite MDFe', default=False) 57 | mdfe_serie = models.IntegerField('Série MDFe', default=0) 58 | mdfe_numero = models.IntegerField('Número MDFe', default=0) 59 | mdfe_serie_homologacao = models.IntegerField('Série MDFe em homologação', default=0) 60 | mdfe_numero_homologacao = models.IntegerField('Número MDFe em homologação', default=0) 61 | 62 | # Token de autenticação enviado ao Emitente/ERP/TMS para acesso a API 63 | 64 | token = models.UUIDField(default=uuid4, editable=False) 65 | 66 | # Limites: Número de créditos restantes de solicitações (requests) para o período atual, decrescente 67 | requests_credit = models.IntegerField('Crédito Requisições', default=100) 68 | # Número de segundos inicial da contagem do cronômetro até 60 segundos para até 100 solicitações 69 | requests_timer = models.IntegerField('Cronômetro de Requisições', default=0) 70 | 71 | """ 72 | emissoes_suspensas: Se ativo, suspende as emissões dos DFes acima selecionados. 73 | Motivos: 74 | - Manutenção 75 | - Suspensão de um ou mais serviços solicitado pelo emitente 76 | - Cobrança: Falta de pagamento 77 | """ 78 | emissoes_suspensas = models.BooleanField('Suspender Emissões', default=False) 79 | is_active = models.BooleanField('Emitente Ativo', default=True, 80 | help_text='Emitentes inativos não aparecem no sistema (api)') 81 | created_at = models.DateField('Cadastrado Em', auto_now_add=True) 82 | 83 | def __str__(self): 84 | return self.nome 85 | -------------------------------------------------------------------------------- /dfe/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for dfe project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | from decouple import Csv, config 16 | from dj_database_url import parse as dburl 17 | 18 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 19 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = config('SECRET_KEY') 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = config('DEBUG', default=False, cast=bool) 30 | 31 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | # Django Apps 38 | 'django.contrib.admin', 39 | 'django.contrib.auth', 40 | 'django.contrib.contenttypes', 41 | 'django.contrib.sessions', 42 | 'django.contrib.messages', 43 | 'django.contrib.staticfiles', 44 | 45 | # Third-Party Apps 46 | 'rest_framework', 47 | 'rest_framework.authtoken', 48 | 'drf_yasg', 49 | 50 | # DFe Project 51 | 'emitentes', 52 | 'nfe', 53 | ] 54 | 55 | DEV_INSTALLED_APPS = [ 56 | 'django_extensions', 57 | ] 58 | 59 | if DEBUG: 60 | INSTALLED_APPS += DEV_INSTALLED_APPS 61 | 62 | MIDDLEWARE = [ 63 | 'django.middleware.security.SecurityMiddleware', 64 | 'django.contrib.sessions.middleware.SessionMiddleware', 65 | 'django.middleware.common.CommonMiddleware', 66 | 'django.middleware.csrf.CsrfViewMiddleware', 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 68 | 'django.contrib.messages.middleware.MessageMiddleware', 69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 70 | ] 71 | 72 | REST_FRAMEWORK = { 73 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 74 | # 'rest_framework.authentication.BasicAuthentication', 75 | # 'rest_framework.authentication.SessionAuthentication', 76 | 'rest_framework.authentication.TokenAuthentication', 77 | ], 78 | } 79 | 80 | ROOT_URLCONF = 'dfe.urls' 81 | 82 | TEMPLATES = [ 83 | { 84 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 85 | 'DIRS': [], 86 | 'APP_DIRS': True, 87 | 'OPTIONS': { 88 | 'context_processors': [ 89 | 'django.template.context_processors.debug', 90 | 'django.template.context_processors.request', 91 | 'django.contrib.auth.context_processors.auth', 92 | 'django.contrib.messages.context_processors.messages', 93 | ], 94 | }, 95 | }, 96 | ] 97 | 98 | WSGI_APPLICATION = 'dfe.wsgi.application' 99 | 100 | 101 | # Database 102 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 103 | 104 | default_dburl = 'sqlite:///' + os.path.join(BASE_DIR, 'db.dfe') 105 | DATABASES = {'default': config( 106 | 'DATABASE_URL', default=default_dburl, cast=dburl), } 107 | # DATABASES = { 108 | # 'default': { 109 | # 'ENGINE': 'django.db.backends.sqlite3', 110 | # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 111 | # } 112 | # } 113 | 114 | 115 | # Password validation 116 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 117 | 118 | AUTH_PASSWORD_VALIDATORS = [ 119 | { 120 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 121 | }, 122 | { 123 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 124 | }, 125 | { 126 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 127 | }, 128 | { 129 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 130 | }, 131 | ] 132 | 133 | 134 | # Internationalization 135 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 136 | 137 | LANGUAGE_CODE = 'pt-br' 138 | 139 | TIME_ZONE = 'UTC' 140 | 141 | USE_I18N = True 142 | 143 | USE_L10N = True 144 | 145 | USE_TZ = True 146 | 147 | 148 | # Static files (CSS, JavaScript, Images) 149 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 150 | 151 | STATIC_URL = '/static/' 152 | 153 | MEDIA_ROOT = 'client_files' 154 | 155 | MEDIA_URL = '/media/' 156 | -------------------------------------------------------------------------------- /emitentes/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-04-23 19:09 2 | 3 | import uuid 4 | 5 | import django.db.models.deletion 6 | from django.conf import settings 7 | from django.db import migrations, models 8 | 9 | import emitentes.models 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | initial = True 15 | 16 | dependencies = [ 17 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='Emitente', 23 | fields=[ 24 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('nome', models.CharField(max_length=60, verbose_name='Nome')), 26 | ('cnpj', models.CharField(blank=True, max_length=14, null=True, verbose_name='CNPJ')), 27 | ('inscricao_estadual', models.CharField(blank=True, max_length=14, null=True, verbose_name='Inscrição Estadual')), 28 | ('cpf', models.CharField(blank=True, max_length=11, null=True, verbose_name='CPF')), 29 | ('logradouro', models.CharField(max_length=60)), 30 | ('numero', models.CharField(max_length=40)), 31 | ('complemento', models.CharField(blank=True, max_length=60, null=True)), 32 | ('bairro', models.CharField(max_length=60)), 33 | ('municipio', models.CharField(max_length=60)), 34 | ('uf', models.CharField(max_length=2, verbose_name='UF')), 35 | ('cep', models.CharField(max_length=8, verbose_name='CEP')), 36 | ('telefones', models.CharField(blank=True, max_length=60, null=True)), 37 | ('email', models.EmailField(blank=True, max_length=100, null=True, verbose_name='E-mail')), 38 | ('certificado_a1', models.FileField(blank=True, null=True, upload_to=emitentes.models.emitente_upload_file)), 39 | ('logotipo_dfe', models.ImageField(blank=True, null=True, upload_to=emitentes.models.emitente_upload_file)), 40 | ('nfe_emite', models.BooleanField(default=False, verbose_name='Emite NFe')), 41 | ('nfe_serie', models.IntegerField(default=0, verbose_name='Série NFe')), 42 | ('nfe_numero', models.IntegerField(default=0, verbose_name='Número NFe')), 43 | ('nfe_serie_homologacao', models.IntegerField(default=0, verbose_name='Série NFe em homologação')), 44 | ('nfe_numero_homologacao', models.IntegerField(default=0, verbose_name='Número NFe em homologação')), 45 | ('nfce_emite', models.BooleanField(default=False, verbose_name='Emite NFCe')), 46 | ('nfce_serie', models.IntegerField(default=0, verbose_name='Série NFCe')), 47 | ('nfce_numero', models.IntegerField(default=0, verbose_name='Número NFCe')), 48 | ('nfce_serie_homologacao', models.IntegerField(default=0, verbose_name='Série NFCe em homologação')), 49 | ('nfce_numero_homologacao', models.IntegerField(default=0, verbose_name='Número NFCe em homologação')), 50 | ('cte_emite', models.BooleanField(default=False, verbose_name='Emite CTe')), 51 | ('cte_serie', models.IntegerField(default=0, verbose_name='Série CTe')), 52 | ('cte_numero', models.IntegerField(default=0, verbose_name='Número CTe')), 53 | ('cte_serie_homologacao', models.IntegerField(default=0, verbose_name='Série CTe')), 54 | ('cte_numero_homologacao', models.IntegerField(default=0, verbose_name='Número CTe em homologação')), 55 | ('mdfe_emite', models.BooleanField(default=False, verbose_name='Emite MDFe')), 56 | ('mdfe_serie', models.IntegerField(default=0, verbose_name='Série MDFe')), 57 | ('mdfe_numero', models.IntegerField(default=0, verbose_name='Número MDFe')), 58 | ('mdfe_serie_homologacao', models.IntegerField(default=0, verbose_name='Série MDFe em homologação')), 59 | ('mdfe_numero_homologacao', models.IntegerField(default=0, verbose_name='Número MDFe em homologação')), 60 | ('token', models.UUIDField(default=uuid.uuid4, editable=False)), 61 | ('requests_credit', models.IntegerField(default=100, verbose_name='Crédito Requisições')), 62 | ('requests_timer', models.IntegerField(default=0, verbose_name='Cronômetro de Requisições')), 63 | ('emissoes_suspensas', models.BooleanField(default=False, verbose_name='Suspender Emissões')), 64 | ('is_active', models.BooleanField(default=True, help_text='Emitentes inativos não aparecem no sistema (api)', verbose_name='Emitente Ativo')), 65 | ('created_at', models.DateField(auto_now_add=True, verbose_name='Cadastrado Em')), 66 | ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to=settings.AUTH_USER_MODEL)), 67 | ], 68 | ), 69 | ] 70 | --------------------------------------------------------------------------------