├── backend ├── __init__.py ├── crm │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_alter_cliente_cnpj.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── admin.py │ ├── forms.py │ ├── crm_api.py │ ├── urls.py │ ├── models.py │ ├── templates │ │ └── crm │ │ │ ├── cliente_detail.html │ │ │ ├── cliente_form.html │ │ │ └── cliente_list.html │ └── views.py ├── core │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── views.py │ ├── apps.py │ ├── urls.py │ ├── templates │ │ ├── index.html │ │ ├── includes │ │ │ ├── menu.html │ │ │ └── pagination.html │ │ └── base.html │ ├── static │ │ ├── css │ │ │ └── autocomplete.css │ │ └── js │ │ │ └── servico │ │ │ └── servico.js │ └── models.py ├── servico │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_alter_ordemservico_options.py │ │ ├── 0002_alter_ordemservicoitem_options_and_more.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── forms.py │ ├── templates │ │ └── servico │ │ │ ├── ordem_servico_detail.html │ │ │ ├── ordem_servico_list.html │ │ │ └── ordem_servico_form.html │ ├── views.py │ ├── models.py │ └── servico_api.py ├── api.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── img ├── modelagem.png └── fluxo_requisicao_alpinejs_django_ninja.png ├── requirements.txt ├── Makefile ├── manage.py ├── contrib └── env_gen.py ├── README.md └── .gitignore /backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/crm/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/servico/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/crm/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/servico/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/modelagem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/ordem-de-servico/HEAD/img/modelagem.png -------------------------------------------------------------------------------- /backend/servico/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /img/fluxo_requisicao_alpinejs_django_ninja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rg3915/ordem-de-servico/HEAD/img/fluxo_requisicao_alpinejs_django_ninja.png -------------------------------------------------------------------------------- /backend/core/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def index(request): 5 | template_name = 'index.html' 6 | return render(request, template_name) 7 | -------------------------------------------------------------------------------- /backend/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'backend.core' 7 | -------------------------------------------------------------------------------- /backend/crm/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CrmConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'backend.crm' 7 | -------------------------------------------------------------------------------- /backend/servico/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ServicoConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'backend.servico' 7 | -------------------------------------------------------------------------------- /backend/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from backend.core import views as v 4 | 5 | app_name = 'core' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.index, name='index'), # noqa E501 10 | ] 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django-extensions==3.2.3 2 | django-localflavor==4.0 3 | django-ninja==0.22.2 4 | django-widget-tweaks==1.4.12 5 | Django==4.2.3 6 | python-decouple==3.8 7 | isort==5.12.0 8 | autopep8==2.0.2 9 | djhtml==3.0.6 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | indenter: 2 | find backend -name "*.html" | xargs djhtml -t 2 3 | 4 | autopep8: 5 | find backend -name "*.py" | xargs autopep8 --max-line-length 120 --in-place 6 | 7 | isort: 8 | isort -m 3 * --skip migrations --skip .venv 9 | 10 | lint: autopep8 isort indenter 11 | -------------------------------------------------------------------------------- /backend/crm/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Cliente 4 | 5 | 6 | @admin.register(Cliente) 7 | class ClienteAdmin(admin.ModelAdmin): 8 | list_display = ('__str__', 'bairro', 'cidade') 9 | search_fields = ('razao_social', 'bairro', 'cidade') 10 | -------------------------------------------------------------------------------- /backend/api.py: -------------------------------------------------------------------------------- 1 | from ninja import NinjaAPI 2 | 3 | from backend.crm.crm_api import router as crm_router 4 | from backend.servico.servico_api import router as servico_router 5 | 6 | api = NinjaAPI(csrf=True) 7 | 8 | api.add_router('/crm/', crm_router) 9 | api.add_router('/servico/', servico_router) 10 | -------------------------------------------------------------------------------- /backend/crm/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import Cliente 4 | 5 | 6 | class ClienteForm(forms.ModelForm): 7 | cnpj = forms.CharField(max_length=18, label='CNPJ') 8 | 9 | class Meta: 10 | model = Cliente 11 | fields = ('razao_social', 'cnpj', 'endereco', 'complemento', 'bairro', 'cidade', 'uf', 'cep') 12 | -------------------------------------------------------------------------------- /backend/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for backend project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend 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/4.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', 'backend.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import include, path 3 | from ninja import NinjaAPI 4 | 5 | from .api import api 6 | 7 | urlpatterns = [ 8 | path('', include('backend.core.urls', namespace='core')), 9 | path('crm/', include('backend.crm.urls', namespace='crm')), 10 | path('servico/', include('backend.servico.urls', namespace='servico')), 11 | path('admin/', admin.site.urls), 12 | path("api/v1/", api.urls), 13 | ] 14 | -------------------------------------------------------------------------------- /backend/core/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

6 | Sistema de Ordens de Serviço 7 |

8 |

9 | Gerencie suas Ordens de Serviço com facilidade. 10 |

11 |

12 | Criar Ordem de Serviço 16 |

17 |
18 | {% endblock content %} -------------------------------------------------------------------------------- /backend/core/static/css/autocomplete.css: -------------------------------------------------------------------------------- 1 | .autocomplete { 2 | position: relative; 3 | } 4 | 5 | #id_ul { 6 | list-style: none; 7 | width: 100%; 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | border: 1px solid #ccc; 12 | border-radius: 0 0 5px 5px; 13 | background: white; 14 | position: absolute; 15 | z-index: 1; 16 | } 17 | 18 | #id_ul li { 19 | width: 100%; 20 | margin: 0; 21 | padding: 5px 10px; 22 | cursor: pointer; 23 | } 24 | #id_ul li:hover { 25 | background: #f5f5f5; 26 | } -------------------------------------------------------------------------------- /backend/crm/migrations/0002_alter_cliente_cnpj.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-06 18:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('crm', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='cliente', 15 | name='cnpj', 16 | field=models.CharField(blank=True, max_length=18, null=True, verbose_name='CNPJ'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/crm/crm_api.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from ninja import Router 4 | from ninja.orm import create_schema 5 | 6 | from backend.crm.models import Cliente 7 | 8 | router = Router() 9 | 10 | ClienteSchema = create_schema(Cliente, fields=( 11 | 'id', 12 | 'razao_social', 13 | 'endereco', 14 | 'bairro', 15 | )) 16 | 17 | 18 | @router.get('cliente/', response=List[ClienteSchema]) 19 | def list_cliente(request, search=None): 20 | if search: 21 | return Cliente.objects.filter(razao_social__istartswith=search) 22 | return Cliente.objects.all() 23 | -------------------------------------------------------------------------------- /backend/servico/migrations/0003_alter_ordemservico_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-06 06:05 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('servico', '0002_alter_ordemservicoitem_options_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='ordemservico', 15 | options={'ordering': ('-pk',), 'verbose_name': 'ordem de serviço', 16 | 'verbose_name_plural': 'ordens de serviço'}, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/servico/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import OrdemServico, OrdemServicoItem, Servico 4 | 5 | 6 | class OrdemServicoItemInline(admin.TabularInline): 7 | model = OrdemServicoItem 8 | extra = 0 9 | 10 | 11 | @admin.register(OrdemServico) 12 | class OrdemServicoAdmin(admin.ModelAdmin): 13 | inlines = (OrdemServicoItemInline,) 14 | list_display = ('__str__', 'cliente', 'situacao') 15 | list_display_links = ('cliente',) 16 | search_fields = ('cliente__razao_social',) 17 | list_filter = ('situacao',) 18 | 19 | 20 | admin.site.register(Servico) 21 | -------------------------------------------------------------------------------- /backend/crm/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from backend.crm import views as v 4 | 5 | app_name = 'crm' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.ClienteListView.as_view(), name='cliente_list'), # noqa E501 10 | path('create/', v.ClienteCreateView.as_view(), name='cliente_create'), # noqa E501 11 | path('/', v.ClienteDetailView.as_view(), name='cliente_detail'), # noqa E501 12 | path('/update/', v.ClienteUpdateView.as_view(), name='cliente_update'), # noqa E501 13 | # path('/delete/', v.ClienteDeleteView.as_view(), name='cliente_delete'), # noqa E501 14 | ] 15 | -------------------------------------------------------------------------------- /backend/servico/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from backend.servico import views as v 4 | 5 | app_name = 'servico' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.ordem_servico_list, name='ordem_servico_list'), # noqa E501 10 | path('create/', v.ordem_servico_create, name='ordem_servico_create'), # noqa E501 11 | path('/', v.ordem_servico_detail, name='ordem_servico_detail'), # noqa E501 12 | path('/update/', v.ordem_servico_update, name='ordem_servico_update'), # noqa E501 13 | # path('/delete/', v.ordem_servico_delete, name='ordem_servico_delete'), # noqa E501 14 | ] 15 | -------------------------------------------------------------------------------- /backend/servico/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import OrdemServico 4 | 5 | 6 | class OrdemServicoForm(forms.ModelForm): 7 | servico = forms.CharField() 8 | valor = forms.DecimalField() 9 | proxima_visita = forms.DateField( 10 | label='Próxima Visita', 11 | required=False, 12 | widget=forms.DateInput( 13 | format='%Y-%m-%d', 14 | attrs={ 15 | 'type': 'date', 16 | 'class': 'form-control' 17 | }), 18 | input_formats=('%Y-%m-%d',), 19 | ) 20 | 21 | class Meta: 22 | model = OrdemServico 23 | fields = ('situacao',) 24 | -------------------------------------------------------------------------------- /backend/crm/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.urls import reverse_lazy 3 | 4 | from backend.core.models import Endereco 5 | 6 | 7 | class Cliente(Endereco): 8 | razao_social = models.CharField('Razão Social', max_length=120, unique=True) 9 | cnpj = models.CharField('CNPJ', max_length=14, null=True, blank=True) 10 | 11 | class Meta: 12 | ordering = ('razao_social',) 13 | verbose_name = 'cliente' 14 | verbose_name_plural = 'clientes' 15 | 16 | def __str__(self): 17 | return f'{self.razao_social}' 18 | 19 | def get_absolute_url(self): 20 | return reverse_lazy('crm:cliente_detail', kwargs={'pk': self.pk}) 21 | -------------------------------------------------------------------------------- /backend/servico/templates/servico/ordem_servico_detail.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

Detalhes da OS {{ object }} - {{ object.cliente }}

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for item in object.ordem_servico_itens.all %} 17 | 18 | 19 | 20 | 21 | 22 | {% endfor %} 23 | 24 |
ServiçoValorPróxima Visita
{{ item.servico }}{{ item.valor }}{{ item.proxima_visita|date:"d/m/Y" }}
25 | {% endblock content %} -------------------------------------------------------------------------------- /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 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /backend/servico/migrations/0002_alter_ordemservicoitem_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-06 05:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('servico', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='ordemservicoitem', 15 | options={'ordering': ('-pk',), 'verbose_name': 'item da ordem de serviço', 16 | 'verbose_name_plural': 'itens da ordens de serviço'}, 17 | ), 18 | migrations.AlterField( 19 | model_name='ordemservicoitem', 20 | name='proxima_visita', 21 | field=models.DateField(blank=True, null=True, verbose_name='Próxima Visita'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from localflavor.br.br_states import STATE_CHOICES 3 | 4 | 5 | class Endereco(models.Model): 6 | endereco = models.CharField( 7 | 'endereço', 8 | max_length=100, 9 | null=True, 10 | blank=True 11 | ) 12 | complemento = models.CharField( 13 | 'complemento', 14 | max_length=100, 15 | null=True, 16 | blank=True 17 | ) 18 | bairro = models.CharField( 19 | 'bairro', 20 | max_length=100, 21 | null=True, 22 | blank=True 23 | ) 24 | cidade = models.CharField('cidade', max_length=100, null=True, blank=True) 25 | uf = models.CharField( 26 | 'UF', 27 | max_length=2, 28 | choices=STATE_CHOICES, 29 | null=True, 30 | blank=True 31 | ) 32 | cep = models.CharField('CEP', max_length=9, null=True, blank=True) 33 | 34 | class Meta: 35 | abstract = True 36 | -------------------------------------------------------------------------------- /backend/crm/templates/crm/cliente_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Detalhes do Cliente: {{ object.razao_social }}

5 | 6 | Voltar 10 | Adicionar 14 | 15 | 32 | {% endblock content %} -------------------------------------------------------------------------------- /backend/crm/templates/crm/cliente_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load widget_tweaks %} 3 | 4 | {% block content %} 5 |
6 |

Adicionar Cliente

7 | 8 |
9 |
10 |
14 | {% csrf_token %} 15 | 16 | {% for field in form.visible_fields %} 17 |
18 | 19 | {% render_field field class="form-control" %} 20 | 21 | {% if field.errors %} 22 | {% for error in field.errors %} 23 |

{{ error }}

24 | {% endfor %} 25 | {% endif %} 26 |
27 | {% endfor %} 28 | 29 | 33 | 34 |
35 |
36 |
37 |
38 | {% endblock content %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /backend/core/templates/includes/menu.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/servico/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | from .forms import OrdemServicoForm 4 | from .models import OrdemServico 5 | 6 | 7 | def ordem_servico_list(request): 8 | template_name = 'servico/ordem_servico_list.html' 9 | object_list = OrdemServico.objects.all() 10 | 11 | search = request.GET.get('search') 12 | if search: 13 | object_list = object_list.filter(situacao=search) 14 | 15 | context = {'object_list': object_list} 16 | return render(request, template_name, context) 17 | 18 | 19 | def ordem_servico_create(request): 20 | template_name = 'servico/ordem_servico_form.html' 21 | context = {'form': OrdemServicoForm} 22 | return render(request, template_name, context) 23 | 24 | 25 | def ordem_servico_detail(request, pk): 26 | template_name = 'servico/ordem_servico_detail.html' 27 | instance = OrdemServico.objects.get(pk=pk) 28 | context = {'object': instance} 29 | return render(request, template_name, context) 30 | 31 | 32 | def ordem_servico_update(request, pk): 33 | ... 34 | 35 | 36 | def ordem_servico_delete(request, pk): 37 | ... 38 | -------------------------------------------------------------------------------- /backend/crm/views.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Q 2 | from django.views.generic import ( 3 | CreateView, 4 | DeleteView, 5 | DetailView, 6 | ListView, 7 | UpdateView 8 | ) 9 | 10 | from .forms import ClienteForm 11 | from .models import Cliente 12 | 13 | 14 | class ClienteListView(ListView): 15 | model = Cliente 16 | paginate_by = 20 17 | 18 | def get_queryset(self): 19 | qs = self.model.objects.all() 20 | search = self.request.GET.get('search') 21 | if search: 22 | qs = qs.filter( 23 | Q(razao_social__icontains=search) 24 | | Q(bairro__icontains=search) 25 | | Q(cidade__icontains=search) 26 | ) 27 | return qs 28 | 29 | 30 | class ClienteDetailView(DetailView): 31 | model = Cliente 32 | 33 | 34 | class ClienteCreateView(CreateView): 35 | model = Cliente 36 | form_class = ClienteForm 37 | 38 | 39 | class ClienteUpdateView(UpdateView): 40 | model = Cliente 41 | form_class = ClienteForm 42 | 43 | 44 | # class ClienteDeleteView(DeleteView): 45 | # model = Cliente 46 | # success_url = reverse_lazy('crm:cliente_list') 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ordem de Serviço 2 | 3 | ## Este projeto foi feito com: 4 | 5 | * [Django 4.2.3](https://www.djangoproject.com/) 6 | * [Django-ninja](https://django-ninja.rest-framework.com/) 7 | * [AlpineJS](https://alpinejs.dev/) 8 | 9 | ## Como rodar o projeto? 10 | 11 | * Clone esse repositório. 12 | * Crie um virtualenv com Python 3. 13 | * Ative o virtualenv. 14 | * Instale as dependências. 15 | * Rode as migrações. 16 | 17 | ``` 18 | git clone https://github.com/rg3915/ordem-de-servico.git 19 | # git clone https://gitlab.com/rg3915/ordem-de-servico.git 20 | cd ordem-de-servico 21 | 22 | python -m venv .venv 23 | source .venv/bin/activate 24 | 25 | pip install -r requirements.txt 26 | 27 | python contrib/env_gen.py 28 | 29 | python manage.py migrate 30 | python manage.py createsuperuser 31 | ``` 32 | 33 | ## Links 34 | 35 | https://alpinejs.dev 36 | 37 | https://django-ninja.rest-framework.com 38 | 39 | ## Videos 40 | 41 | [A Essência do Django parte 1](https://youtu.be/mlaCLGItR7Q) 42 | 43 | [A Essência do Django parte 2](https://youtu.be/Qu2QTxdYfZ4) 44 | 45 | [FBV vs CBV](https://www.youtube.com/live/2qZcPb8ZWQA?feature=share) 46 | 47 | [Class Based Views](https://www.youtube.com/live/C7Ecugxa7ic?feature=share) tem Login simples também. 48 | 49 | [Django-ninja](https://youtu.be/4RdTltDCfl0) 50 | 51 | [AlpineJS](https://www.youtube.com/watch?v=rqH70WZhKcc&list=PLsGCdfxkV9urAmKLmU5j_gshYXeEwzFpu) 52 | 53 | -------------------------------------------------------------------------------- /backend/core/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Serviço 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% block css %}{% endblock css %} 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | {% include "includes/menu.html" %} 37 | 38 | {% block content %}{% endblock content %} 39 |
40 |
41 |
42 | 43 | {% block js %}{% endblock js %} 44 | 45 | 46 | -------------------------------------------------------------------------------- /backend/crm/templates/crm/cliente_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Lista de Clientes

5 | 6 | Adicionar 10 | 11 |
12 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for object in object_list %} 33 | 34 | 37 | 38 | 39 | {% endfor %} 40 | 41 |
Razão SocialCNPJ
35 | {{ object.razao_social }} 36 | {{ object.cnpj }}
42 | {% include "includes/pagination.html" %} 43 |
44 |
45 | {% endblock content %} 46 | 47 | {% block js %} 48 | 61 | {% endblock js %} -------------------------------------------------------------------------------- /backend/servico/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from backend.crm.models import Cliente 4 | 5 | 6 | class Servico(models.Model): 7 | titulo = models.CharField('Título', max_length=100) 8 | 9 | class Meta: 10 | ordering = ('titulo',) 11 | verbose_name = 'serviço' 12 | verbose_name_plural = 'serviços' 13 | 14 | def __str__(self): 15 | return f'{self.titulo}' 16 | 17 | 18 | SITUACAO = ( 19 | ('pe', 'Pendente'), 20 | ('ca', 'Cancelado'), 21 | ('an', 'Andamento'), 22 | ('ap', 'Aprovado'), 23 | ) 24 | 25 | 26 | class OrdemServico(models.Model): 27 | situacao = models.CharField('Situação', max_length=2, choices=SITUACAO) 28 | cliente = models.ForeignKey( 29 | Cliente, 30 | on_delete=models.SET_NULL, 31 | verbose_name='cliente', 32 | related_name='ordem_servicos', 33 | null=True, 34 | blank=True 35 | ) 36 | 37 | class Meta: 38 | ordering = ('-pk',) 39 | verbose_name = 'ordem de serviço' 40 | verbose_name_plural = 'ordens de serviço' 41 | 42 | def __str__(self): 43 | return f'{self.pk}' 44 | 45 | 46 | class OrdemServicoItem(models.Model): 47 | ordem_servico = models.ForeignKey( 48 | OrdemServico, 49 | on_delete=models.CASCADE, 50 | verbose_name='ordem de serviço', 51 | related_name='ordem_servico_itens', 52 | ) 53 | servico = models.ForeignKey( 54 | Servico, 55 | on_delete=models.CASCADE, 56 | verbose_name='serviço', 57 | related_name='ordem_servico_item_servicos', 58 | ) 59 | valor = models.DecimalField('valor', max_digits=8, decimal_places=2, null=True, blank=True) 60 | proxima_visita = models.DateField('Próxima Visita', null=True, blank=True) 61 | 62 | class Meta: 63 | ordering = ('-pk',) # ordem decrescente, se quiser crescente só tirar o sinal de menos. 64 | verbose_name = 'item da ordem de serviço' 65 | verbose_name_plural = 'itens da ordens de serviço' 66 | 67 | def __str__(self): 68 | return f'{self.pk}' 69 | -------------------------------------------------------------------------------- /backend/servico/templates/servico/ordem_servico_list.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |

Ordens de Serviço

6 | 7 | Adicionar 11 | 12 |
13 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {% for object in object_list %} 60 | 61 | 62 | 65 | 66 | 67 | {% endfor %} 68 | 69 |
NumClienteSituação
{{ object.pk }} 63 | {{ object.cliente }} 64 | {{ object.get_situacao_display }}
70 |
71 |
72 | {% endblock content %} -------------------------------------------------------------------------------- /backend/crm/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-05 23:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Cliente', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('endereco', models.CharField(blank=True, max_length=100, null=True, verbose_name='endereço')), 19 | ('complemento', models.CharField(blank=True, max_length=100, null=True, verbose_name='complemento')), 20 | ('bairro', models.CharField(blank=True, max_length=100, null=True, verbose_name='bairro')), 21 | ('cidade', models.CharField(blank=True, max_length=100, null=True, verbose_name='cidade')), 22 | ('uf', models.CharField(blank=True, choices=[('AC', 'Acre'), ('AL', 'Alagoas'), ('AP', 'Amapá'), ('AM', 'Amazonas'), ('BA', 'Bahia'), ('CE', 'Ceará'), ('DF', 'Distrito Federal'), ('ES', 'Espírito Santo'), ('GO', 'Goiás'), ('MA', 'Maranhão'), ('MT', 'Mato Grosso'), ('MS', 'Mato Grosso do Sul'), ('MG', 'Minas Gerais'), ('PA', 'Pará'), ( 23 | 'PB', 'Paraíba'), ('PR', 'Paraná'), ('PE', 'Pernambuco'), ('PI', 'Piauí'), ('RJ', 'Rio de Janeiro'), ('RN', 'Rio Grande do Norte'), ('RS', 'Rio Grande do Sul'), ('RO', 'Rondônia'), ('RR', 'Roraima'), ('SC', 'Santa Catarina'), ('SP', 'São Paulo'), ('SE', 'Sergipe'), ('TO', 'Tocantins')], max_length=2, null=True, verbose_name='UF')), 24 | ('cep', models.CharField(blank=True, max_length=9, null=True, verbose_name='CEP')), 25 | ('razao_social', models.CharField(max_length=120, unique=True, verbose_name='Razão Social')), 26 | ('cnpj', models.CharField(blank=True, max_length=14, null=True, verbose_name='CNPJ')), 27 | ], 28 | options={ 29 | 'verbose_name': 'cliente', 30 | 'verbose_name_plural': 'clientes', 31 | 'ordering': ('razao_social',), 32 | }, 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/servico/servico_api.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from decimal import Decimal 3 | from http import HTTPStatus 4 | from typing import List 5 | 6 | from django.shortcuts import get_object_or_404 7 | from ninja import Router, Schema 8 | from ninja.orm import create_schema 9 | 10 | from backend.crm.models import Cliente 11 | from backend.servico.models import OrdemServico, OrdemServicoItem, Servico 12 | 13 | router = Router() 14 | 15 | 16 | ServicoSchema = create_schema(Servico, fields=( 17 | 'id', 18 | 'titulo', 19 | )) 20 | 21 | 22 | class OrdemServicoItemSchemaIn(Schema): 23 | ordem_servico_id: int = None 24 | servico_id: int 25 | valor: Decimal = None 26 | proxima_visita: date = None 27 | 28 | 29 | class OrdemServicoSchemaIn(Schema): 30 | situacao: str 31 | cliente_id: int = None 32 | ordem_servico_itens: List[OrdemServicoItemSchemaIn] 33 | 34 | 35 | @router.get('servico/', response=List[ServicoSchema]) 36 | def list_servico(request, search=None): 37 | if search: 38 | return Servico.objects.filter(titulo__istartswith=search) 39 | return Servico.objects.all() 40 | 41 | 42 | @router.post('ordem-servico/') 43 | def create_ordem_servico(request, payload: OrdemServicoSchemaIn): 44 | data = payload.dict() 45 | 46 | situacao = data.get('situacao') 47 | cliente_id = data.get('cliente_id') 48 | 49 | # Todos são equivalentes 50 | # cliente = Cliente.objects.get(pk=cliente_id) 51 | # cliente = Cliente.objects.filter(pk=cliente_id).first() 52 | cliente = get_object_or_404(Cliente, pk=cliente_id) 53 | 54 | # Cria a OrdemServico 55 | ordem_servico = OrdemServico.objects.create(situacao=situacao, cliente=cliente) 56 | 57 | # Cria os itens em OrdemServicoItem (lista) 58 | items = data.get('ordem_servico_itens') 59 | for item in items: 60 | servico_id = item.get('servico_id') 61 | servico = get_object_or_404(Servico, pk=servico_id) 62 | 63 | valor = item.get('valor') 64 | 65 | proxima_visita = item.get('proxima_visita') 66 | 67 | OrdemServicoItem.objects.create( 68 | ordem_servico=ordem_servico, 69 | servico=servico, 70 | valor=valor, 71 | proxima_visita=proxima_visita, 72 | ) 73 | 74 | return { 75 | 'ordem_servico_id': ordem_servico.pk, 76 | 'status': HTTPStatus.CREATED 77 | } 78 | -------------------------------------------------------------------------------- /.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 | 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 | 131 | .DS_Store 132 | 133 | media/ 134 | staticfiles/ 135 | .idea 136 | .ipynb_checkpoints/ 137 | .vscode 138 | *.cast 139 | -------------------------------------------------------------------------------- /backend/servico/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-06 00:09 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('crm', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='OrdemServico', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('situacao', models.CharField(choices=[('pe', 'Pendente'), ('ca', 'Cancelado'), 21 | ('an', 'Andamento'), ('ap', 'Aprovado')], max_length=2, verbose_name='Situação')), 22 | ('cliente', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, 23 | related_name='ordem_servicos', to='crm.cliente', verbose_name='cliente')), 24 | ], 25 | options={ 26 | 'verbose_name': 'ordem de serviço', 27 | 'verbose_name_plural': 'ordens de serviço', 28 | 'ordering': ('situacao',), 29 | }, 30 | ), 31 | migrations.CreateModel( 32 | name='Servico', 33 | fields=[ 34 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('titulo', models.CharField(max_length=100, verbose_name='Título')), 36 | ], 37 | options={ 38 | 'verbose_name': 'serviço', 39 | 'verbose_name_plural': 'serviços', 40 | 'ordering': ('titulo',), 41 | }, 42 | ), 43 | migrations.CreateModel( 44 | name='OrdemServicoItem', 45 | fields=[ 46 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 47 | ('valor', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True, verbose_name='valor')), 48 | ('proxima_visita', models.DateTimeField(blank=True, null=True, verbose_name='Próxima Visita')), 49 | ('ordem_servico', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 50 | related_name='ordem_servico_itens', to='servico.ordemservico', verbose_name='ordem de serviço')), 51 | ('servico', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 52 | related_name='ordem_servico_item_servicos', to='servico.servico', verbose_name='serviço')), 53 | ], 54 | options={ 55 | 'verbose_name': 'ordem de serviço', 56 | 'verbose_name_plural': 'ordens de serviço', 57 | 'ordering': ('-pk',), 58 | }, 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /backend/core/static/js/servico/servico.js: -------------------------------------------------------------------------------- 1 | const data = document.currentScript.dataset 2 | const csrf = data.csrf 3 | 4 | const getData = () => ({ 5 | clientes: [], 6 | cliente: {}, 7 | clienteSelecionado: {}, 8 | searchCliente: '', 9 | servicos: [], 10 | servico: {}, 11 | servicoSelecionado: {}, 12 | searchServico: '', 13 | ordemServico: {}, 14 | ordemServicoItem: {}, 15 | currentId: 1, 16 | ordemServicoItems: [], 17 | clienteShow: false, 18 | servicoShow: false, 19 | 20 | init() { 21 | // watch - monitora as ações 22 | this.$watch('searchCliente', (newValue, oldValue) => { 23 | if (!newValue) this.clientes = [] 24 | if (newValue.length >= 3) { 25 | this.getClientes(newValue) 26 | } 27 | }) 28 | this.$watch('searchServico', (newValue, oldValue) => { 29 | if (!newValue) this.servicos = [] 30 | if (newValue.length >= 3) { 31 | this.getServicos(newValue) 32 | } 33 | }) 34 | }, 35 | 36 | addItem() { 37 | // Envia os dados para inserir um novo item na Ordem de Serviço Item, 38 | // que por sua vez será retornado na tabela de itens. 39 | const servico_id = this.servicoSelecionado.id 40 | const servico_titulo = this.servicoSelecionado.titulo 41 | const valor = this.ordemServicoItem.valor 42 | const proxima_visita = this.ordemServicoItem.proximaVisita 43 | 44 | let ordem_servico_item_id = this.currentId++ 45 | this.ordemServicoItems.push({ id: ordem_servico_item_id, servico_id, servico_titulo, valor, proxima_visita }) 46 | }, 47 | 48 | deleteOrdemServicoItem(id) { 49 | const indexToRemove = this.ordemServicoItems.findIndex(i => i.id == id) 50 | this.ordemServicoItems.splice(indexToRemove, 1) 51 | }, 52 | 53 | getClientes(newValue) { 54 | const search = newValue 55 | fetch(`/api/v1/crm/cliente/?search=${search}`) 56 | .then(response => response.json()) 57 | .then(data => { 58 | this.clientes = data 59 | this.clienteShow = true 60 | }) 61 | }, 62 | 63 | getCliente(cliente) { 64 | this.clienteSelecionado = cliente 65 | this.clienteShow = false 66 | }, 67 | 68 | getServicos(newValue) { 69 | const search = newValue 70 | fetch(`/api/v1/servico/servico/?search=${search}`) 71 | .then(response => response.json()) 72 | .then(data => { 73 | this.servicos = data 74 | this.servicoShow = true 75 | }) 76 | }, 77 | 78 | getServico(servico) { 79 | this.servicoSelecionado = servico 80 | this.servicoShow = false 81 | }, 82 | 83 | saveData() { 84 | const cliente_id = this.clienteSelecionado.id 85 | const situacao = this.ordemServico.situacao 86 | const ordem_servico_itens = this.ordemServicoItems 87 | const bodyData = { cliente_id, situacao, ordem_servico_itens } 88 | fetch('/api/v1/servico/ordem-servico/', { 89 | method: "POST", 90 | headers: { "Content-Type": "application/json", "X-CSRFToken": csrf }, 91 | body: JSON.stringify(bodyData), 92 | }) 93 | .then(response => response.json()) 94 | .then(data => { 95 | // redirect para os detalhes da OrdemServico 96 | const ordem_servico_id = data.ordem_servico_id 97 | window.location.href = `/servico/${ordem_servico_id}/` 98 | }) 99 | }, 100 | 101 | }) -------------------------------------------------------------------------------- /backend/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from decouple import Csv, config 4 | 5 | BASE_DIR = Path(__file__).resolve().parent.parent 6 | 7 | # SECURITY WARNING: keep the secret key used in production secret! 8 | SECRET_KEY = config('SECRET_KEY') 9 | 10 | # SECURITY WARNING: don't run with debug turned on in production! 11 | DEBUG = config('DEBUG', default=False, cast=bool) 12 | 13 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 14 | 15 | # Application definition 16 | 17 | INSTALLED_APPS = [ 18 | 'django.contrib.admin', 19 | 'django.contrib.auth', 20 | 'django.contrib.contenttypes', 21 | 'django.contrib.sessions', 22 | 'django.contrib.messages', 23 | 'django.contrib.staticfiles', 24 | # others apps 25 | 'django_extensions', 26 | 'widget_tweaks', 27 | # my apps 28 | 'backend.core', 29 | 'backend.crm', 30 | 'backend.servico', 31 | ] 32 | 33 | MIDDLEWARE = [ 34 | 'django.middleware.security.SecurityMiddleware', 35 | 'django.contrib.sessions.middleware.SessionMiddleware', 36 | 'django.middleware.common.CommonMiddleware', 37 | 'django.middleware.csrf.CsrfViewMiddleware', 38 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 39 | 'django.contrib.messages.middleware.MessageMiddleware', 40 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 41 | ] 42 | 43 | ROOT_URLCONF = 'backend.urls' 44 | 45 | TEMPLATES = [ 46 | { 47 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 48 | 'DIRS': [], 49 | 'APP_DIRS': True, 50 | 'OPTIONS': { 51 | 'context_processors': [ 52 | 'django.template.context_processors.debug', 53 | 'django.template.context_processors.request', 54 | 'django.contrib.auth.context_processors.auth', 55 | 'django.contrib.messages.context_processors.messages', 56 | ], 57 | }, 58 | }, 59 | ] 60 | 61 | WSGI_APPLICATION = 'backend.wsgi.application' 62 | 63 | 64 | # Database 65 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 66 | 67 | DATABASES = { 68 | 'default': { 69 | 'ENGINE': 'django.db.backends.sqlite3', 70 | 'NAME': BASE_DIR / 'db.sqlite3', 71 | } 72 | } 73 | 74 | 75 | # Password validation 76 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 77 | 78 | AUTH_PASSWORD_VALIDATORS = [ 79 | { 80 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 81 | }, 82 | { 83 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 84 | }, 85 | { 86 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 87 | }, 88 | { 89 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 90 | }, 91 | ] 92 | 93 | 94 | # Internationalization 95 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 96 | 97 | LANGUAGE_CODE = 'en-us' 98 | 99 | TIME_ZONE = 'UTC' 100 | 101 | USE_I18N = True 102 | 103 | USE_TZ = True 104 | 105 | 106 | # Static files (CSS, JavaScript, Images) 107 | # https://docs.djangoproject.com/en/4.2/howto/static-files/ 108 | 109 | STATIC_URL = 'static/' 110 | STATIC_ROOT = BASE_DIR.joinpath('staticfiles') 111 | 112 | # Default primary key field type 113 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 114 | 115 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 116 | -------------------------------------------------------------------------------- /backend/core/templates/includes/pagination.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 70 |
-------------------------------------------------------------------------------- /backend/servico/templates/servico/ordem_servico_form.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% load static %} 4 | {% load widget_tweaks %} 5 | 6 | {% block css %} 7 | 11 | {% endblock css %} 12 | 13 | {% block content %} 14 |
15 |

Adicionar Ordem de Serviço

16 |

17 | 18 |
19 | 20 | 21 |
22 | 26 |
27 | 28 | 34 |
    38 | 47 |
48 |
49 | 50 |
51 | 52 | 53 | 65 | 66 | 67 | 68 |
69 | 73 |
74 | 75 | {% render_field form.situacao class="form-control" x-model="ordemServico.situacao" %} 76 |
77 |
78 | 79 |

Endereço:

80 |

Bairro:

81 | 82 | 83 |
84 | 88 |
89 | 90 | 96 |
    100 | 109 |
110 |
111 |

112 |
113 | 114 | 115 | 116 |
117 | 121 |
122 | {% render_field form.valor class="form-control" min="0" step="0.01" x-model="ordemServicoItem.valor" %} 123 |
124 |
125 | 126 |
127 | 131 |
132 | {% render_field form.proxima_visita class="form-control" x-model="ordemServicoItem.proximaVisita" %} 133 |
134 |
135 | 136 |
137 |
138 | 143 | 147 |
148 |
149 | 150 |
151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 176 | 177 |
Descrição (Serviço)ValorPróx. VisitaAções
178 |
179 | {% endblock content %} 180 | 181 | {% block js %} 182 | 186 | {% endblock js %} --------------------------------------------------------------------------------