├── .dockerignore
├── .flake8
├── .gitignore
├── Dockerfile
├── README.md
├── ai
├── __init__.py
├── admin.py
├── agent.py
├── apps.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── sge_agent_invoke.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
└── prompts.py
├── app
├── __init__.py
├── asgi.py
├── metrics.py
├── settings.py
├── templates
│ ├── base.html
│ ├── components
│ │ ├── _ai_result.html
│ │ ├── _footer.html
│ │ ├── _header.html
│ │ ├── _pagination.html
│ │ ├── _product_metrics.html
│ │ ├── _sales_metrics.html
│ │ └── _sidebar.html
│ ├── home.html
│ └── registration
│ │ └── login.html
├── urls.py
├── views.py
└── wsgi.py
├── authentication
├── __init__.py
├── apps.py
├── migrations
│ └── __init__.py
└── urls.py
├── brands
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ ├── brand_create.html
│ ├── brand_delete.html
│ ├── brand_detail.html
│ ├── brand_list.html
│ └── brand_update.html
├── urls.py
└── views.py
├── categories
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ ├── category_create.html
│ ├── category_delete.html
│ ├── category_detail.html
│ ├── category_list.html
│ └── category_update.html
├── urls.py
└── views.py
├── cron
├── docker-compose.yml
├── inflows
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── serializers.py
├── signals.py
├── templates
│ ├── inflow_create.html
│ ├── inflow_detail.html
│ └── inflow_list.html
├── urls.py
└── views.py
├── manage.py
├── outflows
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── serializers.py
├── signals.py
├── templates
│ ├── outflow_create.html
│ ├── outflow_detail.html
│ └── outflow_list.html
├── urls.py
└── views.py
├── products
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── fazer_coisas.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── serializers.py
├── templates
│ ├── product_create.html
│ ├── product_delete.html
│ ├── product_detail.html
│ ├── product_list.html
│ └── product_update.html
├── urls.py
└── views.py
├── requirements.txt
├── requirements_dev.txt
└── suppliers
├── __init__.py
├── admin.py
├── apps.py
├── forms.py
├── migrations
├── 0001_initial.py
└── __init__.py
├── models.py
├── serializers.py
├── templates
├── supplier_create.html
├── supplier_delete.html
├── supplier_detail.html
├── supplier_list.html
└── supplier_update.html
├── urls.py
└── views.py
/.dockerignore:
--------------------------------------------------------------------------------
1 | .venv
2 | .git
3 | .gitignore
4 | venv
5 | __pycache__
6 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude = venv
3 | ignore = E501
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | venv
2 | db.sqlite3
3 | media
4 | __pycache__/
5 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.11-slim
2 |
3 | WORKDIR /sge
4 |
5 | ENV PYTHONDONTWRITEBYTECODE 1
6 | ENV PYTHONUNBUFFERED 1
7 |
8 | RUN apt update && apt -y install cron && apt -y install nano
9 |
10 | COPY . .
11 |
12 | RUN pip install --upgrade pip
13 | RUN pip install -r requirements.txt
14 |
15 | COPY ./cron /etc/cron.d/cron
16 | RUN chmod 0644 /etc/cron.d/cron
17 | RUN crontab /etc/cron.d/cron
18 |
19 | EXPOSE 8000
20 |
21 | CMD cron ; python manage.py migrate && python manage.py runserver 0.0.0.0:8000
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sistema de Gestão de Estoque (SGE)
2 |
3 | Bem-vindo ao Sistema de Gestão de Estoque (SGE), um projeto desenvolvido em Django e Bootstrap 5 para facilitar o gerenciamento de estoque. Este README fornece informações essenciais sobre como configurar e executar o projeto em seu ambiente local.
4 |
5 | ## Requisitos
6 |
7 | Certifique-se de que você tenha os seguintes requisitos instalados em seu sistema:
8 |
9 | - Python (versão recomendada: 3.7 ou superior)
10 | - Django (instalado automaticamente ao seguir as instruções abaixo)
11 | - Outras dependências listadas no arquivo `requirements.txt`
12 |
13 |
14 | ## Instalação das Dependências
15 |
16 | Com o ambiente virtual ativado, instale as dependências do projeto usando o comando:
17 | ```bash
18 | pip install -r requirements.txt
19 | ```
20 |
21 |
22 | ## Rodar o projeto
23 |
24 | Após instalar as dependências, aplique as migrations no banco de dados com o comando:
25 | ```bash
26 | python manage.py migrate
27 | ```
28 |
29 | Agora o projeto jã pode ser inicializado com o comando:
30 | ```bash
31 | python manage.py runserver
32 | ```
33 |
34 | Após isso, o sistema estará pronto para ser acessado em:
35 | [http://localhost:8000](http://localhost:8000)
36 |
--------------------------------------------------------------------------------
/ai/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/ai/__init__.py
--------------------------------------------------------------------------------
/ai/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class AIResultAdmin(admin.ModelAdmin):
6 | list_display = ('created_at', 'result',)
7 |
8 |
9 | admin.site.register(models.AIResult, AIResultAdmin)
10 |
--------------------------------------------------------------------------------
/ai/agent.py:
--------------------------------------------------------------------------------
1 | import json
2 | from django.conf import settings
3 | from django.core import serializers
4 | from openai import OpenAI
5 | from ai import prompts, models
6 | from products.models import Product
7 | from outflows.models import Outflow
8 |
9 |
10 | class SGEAgent:
11 |
12 | def __init__(self):
13 | self.__client = OpenAI(
14 | api_key=settings.OPENAI_API_KEY,
15 | )
16 |
17 | def __get_data(self):
18 | products = Product.objects.all()
19 | outflows = Outflow.objects.all()
20 | return json.dumps({
21 | 'products': serializers.serialize('json', products),
22 | 'outflows': serializers.serialize('json', outflows),
23 | })
24 |
25 | def invoke(self):
26 | response = self.__client.chat.completions.create(
27 | model=settings.OPENAI_MODEL,
28 | messages=[
29 | {
30 | 'role': 'system',
31 | 'content': prompts.SYSTEM_PROMPT,
32 | },
33 | {
34 | 'role': 'user',
35 | 'content': prompts.USER_PROMPT.replace('{{data}}', self.__get_data()),
36 | },
37 | ],
38 | )
39 | result = response.choices[0].message.content
40 | models.AIResult.objects.create(result=result)
41 |
--------------------------------------------------------------------------------
/ai/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AiConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'ai'
7 |
--------------------------------------------------------------------------------
/ai/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/ai/management/__init__.py
--------------------------------------------------------------------------------
/ai/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/ai/management/commands/__init__.py
--------------------------------------------------------------------------------
/ai/management/commands/sge_agent_invoke.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from ai.agent import SGEAgent
3 |
4 |
5 | class Command(BaseCommand):
6 |
7 | def handle(self, *args, **options):
8 | agent = SGEAgent()
9 | agent.invoke()
10 |
11 | self.stdout.write(
12 | self.style.SUCCESS('SGE AGENT INVOCADO COM SUCESSO!')
13 | )
14 |
--------------------------------------------------------------------------------
/ai/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-08-04 14:21
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='AIResult',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('created_at', models.DateTimeField(auto_now_add=True)),
19 | ('result', models.TextField(blank=True, null=True)),
20 | ],
21 | options={
22 | 'ordering': ['-created_at'],
23 | },
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/ai/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/ai/migrations/__init__.py
--------------------------------------------------------------------------------
/ai/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class AIResult(models.Model):
5 | created_at = models.DateTimeField(auto_now_add=True)
6 | result = models.TextField(null=True, blank=True)
7 |
8 | class Meta:
9 | ordering = ['-created_at']
10 |
--------------------------------------------------------------------------------
/ai/prompts.py:
--------------------------------------------------------------------------------
1 | SYSTEM_PROMPT = '''
2 | Você é um agente virtual especialista em gestão de estoque e vendas.
3 | Você deve gerar relatórios de insights sobre estoque de produtos baseado
4 | nos dados de um sistema de gestão de estoque feito em django que serão passados.
5 | Faça análises de reposição de produtos e também relatórios de saídas do estoque e valores.
6 | Dê respostas curtas, resumidas e diretas. Você irá gerar análises e sugestões diárias para
7 | os usuários do sistema.
8 | '''
9 |
10 | USER_PROMPT = '''
11 | Faça uma análise e dê sugestões com base nos dados atuais:
12 | {{data}}
13 | '''
14 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/app/__init__.py
--------------------------------------------------------------------------------
/app/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for app 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/5.0/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', 'app.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/app/metrics.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Sum, F
2 | from django.utils.formats import number_format
3 | from django.utils import timezone
4 | from brands.models import Brand
5 | from categories.models import Category
6 | from products.models import Product
7 | from outflows.models import Outflow
8 |
9 |
10 | def get_product_metrics():
11 | products = Product.objects.all()
12 | total_cost_price = sum(product.cost_price * product.quantity for product in products)
13 | total_selling_price = sum(product.selling_price * product.quantity for product in products)
14 | total_quantity = sum(product.quantity for product in products)
15 | total_profit = total_selling_price - total_cost_price
16 |
17 | return dict(
18 | total_cost_price=number_format(total_cost_price, decimal_pos=2, force_grouping=True),
19 | total_selling_price=number_format(total_selling_price, decimal_pos=2, force_grouping=True),
20 | total_quantity=total_quantity,
21 | total_profit=number_format(total_profit, decimal_pos=2, force_grouping=True),
22 | )
23 |
24 |
25 | def get_sales_metrics():
26 | total_sales = Outflow.objects.count()
27 | total_products_sold = Outflow.objects.aggregate(total_products_sold=Sum('quantity'))['total_products_sold'] or 0
28 | total_sales_value = sum(outflow.quantity * outflow.product.selling_price for outflow in Outflow.objects.all())
29 | total_sales_cost = sum(outflow.quantity * outflow.product.cost_price for outflow in Outflow.objects.all())
30 | total_sales_profit = total_sales_value - total_sales_cost
31 |
32 | return dict(
33 | total_sales=total_sales,
34 | total_products_sold=total_products_sold,
35 | total_sales_value=number_format(total_sales_value, decimal_pos=2, force_grouping=True),
36 | total_sales_profit=number_format(total_sales_profit, decimal_pos=2, force_grouping=True),
37 | )
38 |
39 |
40 | def get_daily_sales_data():
41 | today = timezone.now().date()
42 | dates = [str(today - timezone.timedelta(days=i)) for i in range(6, -1, -1)]
43 | values = list()
44 |
45 | for date in dates:
46 | sales_total = Outflow.objects.filter(
47 | created_at__date=date
48 | ).aggregate(
49 | total_sales=Sum(F('product__selling_price') * F('quantity'))
50 | )['total_sales'] or 0
51 | values.append(float(sales_total))
52 |
53 | return dict(
54 | dates=dates,
55 | values=values,
56 | )
57 |
58 |
59 | def get_daily_sales_quantity_data():
60 | today = timezone.now().date()
61 | dates = [str(today - timezone.timedelta(days=i)) for i in range(6, -1, -1)]
62 | quantities = list()
63 |
64 | for date in dates:
65 | sales_quantity = Outflow.objects.filter(created_at__date=date).count()
66 | quantities.append(sales_quantity)
67 |
68 | return dict(
69 | dates=dates,
70 | values=quantities,
71 | )
72 |
73 |
74 | def get_graphic_product_category_metric():
75 | categories = Category.objects.all()
76 | return {category.name: Product.objects.filter(category=category).count() for category in categories}
77 |
78 |
79 | def get_graphic_product_brand_metric():
80 | brands = Brand.objects.all()
81 | return {brand.name: Product.objects.filter(brand=brand).count() for brand in brands}
82 |
--------------------------------------------------------------------------------
/app/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for app project.
3 |
4 | Generated by 'django-admin startproject' using Django 5.0.1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/5.0/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 | from datetime import timedelta
15 |
16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
17 | BASE_DIR = Path(__file__).resolve().parent.parent
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = 'django-insecure-p%a7njmpp-*sj*-g$k!-(olb3%0(y&bk=y34g!!)e993whp6w*'
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = []
30 |
31 |
32 | # Application definition
33 |
34 | INSTALLED_APPS = [
35 | 'django.contrib.admin',
36 | 'django.contrib.auth',
37 | 'django.contrib.contenttypes',
38 | 'django.contrib.sessions',
39 | 'django.contrib.messages',
40 | 'django.contrib.staticfiles',
41 |
42 | 'rest_framework',
43 | 'rest_framework_simplejwt',
44 |
45 | 'authentication',
46 | 'brands',
47 | 'suppliers',
48 | 'categories',
49 | 'products',
50 | 'inflows',
51 | 'outflows',
52 | 'ai',
53 | ]
54 |
55 | LOGIN_URL = 'login'
56 |
57 | LOGIN_REDIRECT_URL = '/'
58 | LOGOUT_REDIRECT_URL = '/login/'
59 |
60 | MIDDLEWARE = [
61 | 'django.middleware.security.SecurityMiddleware',
62 | 'django.contrib.sessions.middleware.SessionMiddleware',
63 | 'django.middleware.common.CommonMiddleware',
64 | 'django.middleware.csrf.CsrfViewMiddleware',
65 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
66 | 'django.contrib.messages.middleware.MessageMiddleware',
67 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
68 | ]
69 |
70 | ROOT_URLCONF = 'app.urls'
71 |
72 | TEMPLATES = [
73 | {
74 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
75 | 'DIRS': ['app/templates'],
76 | 'APP_DIRS': True,
77 | 'OPTIONS': {
78 | 'context_processors': [
79 | 'django.template.context_processors.debug',
80 | 'django.template.context_processors.request',
81 | 'django.contrib.auth.context_processors.auth',
82 | 'django.contrib.messages.context_processors.messages',
83 | ],
84 | },
85 | },
86 | ]
87 |
88 | WSGI_APPLICATION = 'app.wsgi.application'
89 |
90 |
91 | # Database
92 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
93 |
94 | DATABASES = {
95 | 'prod': {
96 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
97 | 'NAME': 'sge',
98 | 'USER': 'postgres',
99 | 'PASSWORD': 'postgres',
100 | 'HOST': 'sge_db',
101 | 'PORT': '5432',
102 | },
103 | 'default': {
104 | 'ENGINE': 'django.db.backends.sqlite3',
105 | 'NAME': BASE_DIR / 'db.sqlite3',
106 | }
107 | }
108 |
109 |
110 | # Password validation
111 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
112 |
113 | AUTH_PASSWORD_VALIDATORS = [
114 | {
115 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
116 | },
117 | {
118 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
119 | },
120 | {
121 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
122 | },
123 | {
124 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
125 | },
126 | ]
127 |
128 |
129 | # Internationalization
130 | # https://docs.djangoproject.com/en/5.0/topics/i18n/
131 |
132 | LANGUAGE_CODE = 'pt-BR'
133 |
134 | TIME_ZONE = 'UTC'
135 |
136 | USE_I18N = True
137 |
138 | USE_TZ = False
139 |
140 |
141 | # Static files (CSS, JavaScript, Images)
142 | # https://docs.djangoproject.com/en/5.0/howto/static-files/
143 |
144 | STATIC_URL = 'static/'
145 |
146 | # Default primary key field type
147 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
148 |
149 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
150 |
151 |
152 | REST_FRAMEWORK = {
153 | 'DEFAULT_AUTHENTICATION_CLASSES': (
154 | 'rest_framework_simplejwt.authentication.JWTAuthentication',
155 | ),
156 | 'DEFAULT_PERMISSION_CLASSES': (
157 | 'rest_framework.permissions.IsAuthenticated',
158 | 'rest_framework.permissions.DjangoModelPermissions',
159 | ),
160 | }
161 |
162 | SIMPLE_JWT = {
163 | "ACCESS_TOKEN_LIFETIME": timedelta(days=1),
164 | "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
165 | }
166 |
167 | OPENAI_MODEL = 'gpt-3.5-turbo'
168 | OPENAI_API_KEY = ''
169 |
--------------------------------------------------------------------------------
/app/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {% block title %}{% endblock %}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% include 'components/_header.html' %}
16 |
17 |
18 | {% if user.is_authenticated %}
19 | {% include 'components/_sidebar.html' %}
20 | {% endif %}
21 |
22 |
23 |
24 |
25 |
26 | {% block content %}
27 | {% endblock %}
28 |
29 |
30 |
31 |
32 |
33 |
34 | {% include 'components/_footer.html' %}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/templates/components/_ai_result.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 | {{ ai_result|linebreaks }}
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/templates/components/_footer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/components/_header.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/templates/components/_pagination.html:
--------------------------------------------------------------------------------
1 | {% if page_obj.has_other_pages %}
2 |
49 | {% endif %}
50 |
--------------------------------------------------------------------------------
/app/templates/components/_product_metrics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Quantidade de produtos
8 |
{{ product_metrics.total_quantity }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Custo do estoque
16 |
R$ {{ product_metrics.total_cost_price }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Valor do estoque
24 |
R$ {{ product_metrics.total_selling_price }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Lucro do estoque
32 |
R$ {{ product_metrics.total_profit }}
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/templates/components/_sales_metrics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Quantidade de vendas
8 |
{{ sales_metrics.total_sales }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Produtos vendidos
16 |
{{ sales_metrics.total_products_sold }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Valor das vendas
24 |
R$ {{ sales_metrics.total_sales_value }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Lucro das vendas
32 |
R$ {{ sales_metrics.total_sales_profit }}
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/templates/components/_sidebar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/app/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Home
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% include 'components/_ai_result.html' %}
10 |
11 | {% if perms.products.view_product and perms.inflows.view_inflow %}
12 | {% include 'components/_product_metrics.html' %}
13 | {% endif %}
14 |
15 | {% if perms.outflows.view_outflow %}
16 | {% include 'components/_sales_metrics.html' %}
17 | {% endif %}
18 |
19 |
20 |
21 | {% if perms.outflows.view_outflow %}
22 |
23 |
24 |
Valor de vendas (Últimos 7 Dias)
25 |
26 |
27 |
28 |
Quantidade de Vendas Diárias
29 |
30 |
31 |
82 |
83 | {% endif %}
84 |
85 | {% if perms.products.view_product %}
86 |
87 |
88 | {% if product_count_by_category != '{}' %}
89 |
Produtos por Categoria
90 |
91 |
92 |
93 |
94 | {% endif %}
95 |
96 |
97 | {% if product_count_by_category != '{}' %}
98 |
Produtos por Marca
99 |
100 |
101 |
102 |
103 | {% endif %}
104 |
105 |
149 |
150 | {% endif %}
151 | {% endblock %}
152 |
--------------------------------------------------------------------------------
/app/templates/registration/login.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block title %}Login{% endblock %}
3 |
4 | {% block content %}
5 |
6 |
7 |
8 |
9 |
10 |
Login
11 |
25 | {% if form.errors %}
26 |
27 |
28 | {% for field in form %}
29 | {% for error in field.errors %}
30 | - {{ error }}
31 | {% endfor %}
32 | {% endfor %}
33 | {% for error in form.non_field_errors %}
34 | - {{ error }}
35 | {% endfor %}
36 |
37 |
38 | {% endif %}
39 |
40 |
41 |
42 |
43 |
44 | {% endblock %}
45 |
--------------------------------------------------------------------------------
/app/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path, include
3 | from django.contrib.auth import views as auth_views
4 | from . import views
5 |
6 |
7 | urlpatterns = [
8 | path('admin/', admin.site.urls),
9 |
10 | path('login/', auth_views.LoginView.as_view(), name='login'),
11 | path('logout/', auth_views.LogoutView.as_view(), name='logout'),
12 |
13 | path('api/v1/', include('authentication.urls')),
14 |
15 | path('', views.home, name='home'),
16 | path('', include('suppliers.urls')),
17 | path('', include('brands.urls')),
18 | path('', include('categories.urls')),
19 | path('', include('products.urls')),
20 | path('', include('inflows.urls')),
21 | path('', include('outflows.urls')),
22 | ]
23 |
--------------------------------------------------------------------------------
/app/views.py:
--------------------------------------------------------------------------------
1 | import json
2 | from django.contrib.auth.decorators import login_required
3 | from django.shortcuts import render
4 | from ai.models import AIResult
5 | from . import metrics
6 |
7 |
8 | @login_required(login_url='login')
9 | def home(request):
10 | product_metrics = metrics.get_product_metrics()
11 | sales_metrics = metrics.get_sales_metrics()
12 | graphic_product_category_metric = metrics.get_graphic_product_category_metric()
13 | graphic_product_brand_metric = metrics.get_graphic_product_brand_metric()
14 | daily_sales_data = metrics.get_daily_sales_data()
15 | daily_sales_quantity_data = metrics.get_daily_sales_quantity_data()
16 | ai_result = AIResult.objects.first().result
17 |
18 | context = {
19 | 'product_metrics': product_metrics,
20 | 'sales_metrics': sales_metrics,
21 | 'product_count_by_category': json.dumps(graphic_product_category_metric),
22 | 'product_count_by_brand': json.dumps(graphic_product_brand_metric),
23 | 'daily_sales_data': json.dumps(daily_sales_data),
24 | 'daily_sales_quantity_data': json.dumps(daily_sales_quantity_data),
25 | 'ai_result': ai_result,
26 | }
27 |
28 | return render(request, 'home.html', context)
29 |
--------------------------------------------------------------------------------
/app/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for app 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/5.0/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', 'app.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/authentication/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/authentication/__init__.py
--------------------------------------------------------------------------------
/authentication/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AuthenticationConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'authentication'
7 |
--------------------------------------------------------------------------------
/authentication/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/authentication/migrations/__init__.py
--------------------------------------------------------------------------------
/authentication/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
3 |
4 |
5 | urlpatterns = [
6 | path('authentication/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
7 | path('authentication/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
8 | path('authentication/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
9 | ]
10 |
--------------------------------------------------------------------------------
/brands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/brands/__init__.py
--------------------------------------------------------------------------------
/brands/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class BrandAdmin(admin.ModelAdmin):
6 | list_display = ('name', 'description',)
7 | search_fields = ('name',)
8 |
9 |
10 | admin.site.register(models.Brand, BrandAdmin)
11 |
--------------------------------------------------------------------------------
/brands/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class BrandsConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'brands'
7 |
--------------------------------------------------------------------------------
/brands/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from . import models
3 |
4 |
5 | class BrandForm(forms.ModelForm):
6 |
7 | class Meta:
8 | model = models.Brand
9 | fields = ['name', 'description']
10 | widgets = {
11 | 'name': forms.TextInput(attrs={'class': 'form-control'}),
12 | 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
13 | }
14 | labels = {
15 | 'name': 'Nome',
16 | 'description': 'Descrição',
17 | }
18 |
--------------------------------------------------------------------------------
/brands/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-02-09 21:42
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='Brand',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=500)),
19 | ('description', models.TextField(blank=True, null=True)),
20 | ('created_at', models.DateTimeField(auto_now_add=True)),
21 | ('updated_at', models.DateTimeField(auto_now=True)),
22 | ],
23 | options={
24 | 'ordering': ['name'],
25 | },
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/brands/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/brands/migrations/__init__.py
--------------------------------------------------------------------------------
/brands/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Brand(models.Model):
5 | name = models.CharField(max_length=500)
6 | description = models.TextField(null=True, blank=True)
7 | created_at = models.DateTimeField(auto_now_add=True)
8 | updated_at = models.DateTimeField(auto_now=True)
9 |
10 | class Meta:
11 | ordering = ['name']
12 |
13 | def __str__(self):
14 | return self.name
15 |
--------------------------------------------------------------------------------
/brands/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from brands.models import Brand
3 |
4 |
5 | class BrandSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | model = Brand
9 | fields = '__all__'
10 |
--------------------------------------------------------------------------------
/brands/templates/brand_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/brands/templates/brand_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Deletar Marca
6 |
7 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/brands/templates/brand_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/brands/templates/brand_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Marcas
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
20 | {% if perms.brands.add_brand %}
21 |
26 | {% endif %}
27 |
28 |
29 |
30 |
31 |
32 |
33 | ID |
34 | Nome |
35 | Descrição |
36 | Ações |
37 |
38 |
39 |
40 | {% for brand in brands %}
41 |
42 |
43 |
44 | {{ brand.id }}
45 |
46 | |
47 | {{ brand.name }} |
48 | {{ brand.description }} |
49 |
50 |
51 |
52 |
53 |
54 | {% if perms.brands.change_brand %}
55 |
56 |
57 |
58 | {% endif %}
59 |
60 | {% if perms.brands.delete_brand %}
61 |
62 |
63 |
64 | {% endif %}
65 | |
66 |
67 | {% endfor %}
68 |
69 |
70 |
71 |
72 | {% include 'components/_pagination.html' %}
73 |
74 | {% endblock %}
--------------------------------------------------------------------------------
/brands/templates/brand_update.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/brands/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 |
5 | urlpatterns = [
6 | path('brands/list/', views.BrandListView.as_view(), name='brand_list'),
7 | path('brands/create/', views.BrandCreateView.as_view(), name='brand_create'),
8 | path('brands/
/detail/', views.BrandDetailView.as_view(), name='brand_detail'),
9 | path('brands//update/', views.BrandUpdateView.as_view(), name='brand_update'),
10 | path('brands//delete/', views.BrandDeleteView.as_view(), name='brand_delete'),
11 |
12 | path('api/v1/brands/', views.BrandCreateListAPIView.as_view(), name='brand-create-list-api-view'),
13 | path('api/v1/brands//', views.BrandRetrieveUpdateDestroyAPIView.as_view(), name='brand-detail-api-view'),
14 | ]
15 |
--------------------------------------------------------------------------------
/brands/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.urls import reverse_lazy
4 | from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
5 | from . import models, forms, serializers
6 |
7 |
8 | class BrandListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
9 | model = models.Brand
10 | template_name = 'brand_list.html'
11 | context_object_name = 'brands'
12 | paginate_by = 10
13 | permission_required = 'brands.view_brand'
14 |
15 | def get_queryset(self):
16 | queryset = super().get_queryset()
17 | name = self.request.GET.get('name')
18 |
19 | if name:
20 | queryset = queryset.filter(name__icontains=name)
21 |
22 | return queryset
23 |
24 |
25 | class BrandCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
26 | model = models.Brand
27 | template_name = 'brand_create.html'
28 | form_class = forms.BrandForm
29 | success_url = reverse_lazy('brand_list')
30 | permission_required = 'brands.add_brand'
31 |
32 |
33 | class BrandDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
34 | model = models.Brand
35 | template_name = 'brand_detail.html'
36 | permission_required = 'brands.view_brand'
37 |
38 |
39 | class BrandUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
40 | model = models.Brand
41 | template_name = 'brand_update.html'
42 | form_class = forms.BrandForm
43 | success_url = reverse_lazy('brand_list')
44 | permission_required = 'brands.change_brand'
45 |
46 |
47 | class BrandDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
48 | model = models.Brand
49 | template_name = 'brand_delete.html'
50 | success_url = reverse_lazy('brand_list')
51 | permission_required = 'brands.delete_brand'
52 |
53 |
54 | class BrandCreateListAPIView(generics.ListCreateAPIView):
55 | queryset = models.Brand.objects.all()
56 | serializer_class = serializers.BrandSerializer
57 |
58 |
59 | class BrandRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
60 | queryset = models.Brand.objects.all()
61 | serializer_class = serializers.BrandSerializer
62 |
--------------------------------------------------------------------------------
/categories/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/categories/__init__.py
--------------------------------------------------------------------------------
/categories/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class CategoryAdmin(admin.ModelAdmin):
6 | list_display = ('name', 'description',)
7 | search_fields = ('name',)
8 |
9 |
10 | admin.site.register(models.Category, CategoryAdmin)
11 |
--------------------------------------------------------------------------------
/categories/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class CategoriesConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'categories'
7 |
--------------------------------------------------------------------------------
/categories/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from . import models
3 |
4 |
5 | class CategoryForm(forms.ModelForm):
6 |
7 | class Meta:
8 | model = models.Category
9 | fields = ['name', 'description']
10 | widgets = {
11 | 'name': forms.TextInput(attrs={'class': 'form-control'}),
12 | 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
13 | }
14 | labels = {
15 | 'name': 'Nome',
16 | 'description': 'Descrição',
17 | }
18 |
--------------------------------------------------------------------------------
/categories/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-02-09 21:42
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='Category',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=500)),
19 | ('description', models.TextField(blank=True, null=True)),
20 | ('created_at', models.DateTimeField(auto_now_add=True)),
21 | ('updated_at', models.DateTimeField(auto_now=True)),
22 | ],
23 | options={
24 | 'ordering': ['name'],
25 | },
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/categories/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/categories/migrations/__init__.py
--------------------------------------------------------------------------------
/categories/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Category(models.Model):
5 | name = models.CharField(max_length=500)
6 | description = models.TextField(null=True, blank=True)
7 | created_at = models.DateTimeField(auto_now_add=True)
8 | updated_at = models.DateTimeField(auto_now=True)
9 |
10 | class Meta:
11 | ordering = ['name']
12 |
13 | def __str__(self):
14 | return self.name
15 |
--------------------------------------------------------------------------------
/categories/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from categories.models import Category
3 |
4 |
5 | class CategorySerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | model = Category
9 | fields = '__all__'
10 |
--------------------------------------------------------------------------------
/categories/templates/category_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/categories/templates/category_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Deletar Categoria
6 |
7 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/categories/templates/category_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/categories/templates/category_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Categorias
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
20 | {% if perms.categories.add_category %}
21 |
26 | {% endif %}
27 |
28 |
29 |
30 |
31 |
32 |
33 | ID |
34 | Nome |
35 | Descrição |
36 | Ações |
37 |
38 |
39 |
40 | {% for category in categories %}
41 |
42 |
43 |
44 | {{ category.id }}
45 |
46 | |
47 | {{ category.name }} |
48 | {{ category.description }} |
49 |
50 |
51 |
52 |
53 |
54 | {% if perms.categories.change_category %}
55 |
56 |
57 |
58 | {% endif %}
59 |
60 | {% if perms.categories.delete_category %}
61 |
62 |
63 |
64 | {% endif %}
65 | |
66 |
67 | {% endfor %}
68 |
69 |
70 |
71 |
72 | {% include 'components/_pagination.html' %}
73 |
74 | {% endblock %}
--------------------------------------------------------------------------------
/categories/templates/category_update.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/categories/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 |
5 | urlpatterns = [
6 | path('categories/list/', views.CategoryListView.as_view(), name='category_list'),
7 | path('categories/create/', views.CategoryCreateView.as_view(), name='category_create'),
8 | path('categories//detail/', views.CategoryDetailView.as_view(), name='category_detail'),
9 | path('categories//update/', views.CategoryUpdateView.as_view(), name='category_update'),
10 | path('categories//delete/', views.CategoryDeleteView.as_view(), name='category_delete'),
11 |
12 | path('api/v1/categories/', views.CategoryCreateListAPIView.as_view(), name='category-create-list-api-view'),
13 | path('api/v1/categories//', views.CategoryRetrieveUpdateDestroyAPIView.as_view(), name='category-detail-api-view'),
14 | ]
15 |
--------------------------------------------------------------------------------
/categories/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.urls import reverse_lazy
4 | from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
5 | from . import models, forms, serializers
6 |
7 |
8 | class CategoryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
9 | model = models.Category
10 | template_name = 'category_list.html'
11 | context_object_name = 'categories'
12 | paginate_by = 10
13 | permission_required = 'categories.view_category'
14 |
15 | def get_queryset(self):
16 | queryset = super().get_queryset()
17 | name = self.request.GET.get('name')
18 |
19 | if name:
20 | queryset = queryset.filter(name__icontains=name)
21 |
22 | return queryset
23 |
24 |
25 | class CategoryCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
26 | model = models.Category
27 | template_name = 'category_create.html'
28 | form_class = forms.CategoryForm
29 | success_url = reverse_lazy('category_list')
30 | permission_required = 'categories.add_category'
31 |
32 |
33 | class CategoryDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
34 | model = models.Category
35 | template_name = 'category_detail.html'
36 | permission_required = 'categories.view_category'
37 |
38 |
39 | class CategoryUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
40 | model = models.Category
41 | template_name = 'category_update.html'
42 | form_class = forms.CategoryForm
43 | success_url = reverse_lazy('category_list')
44 | permission_required = 'categories.change_category'
45 |
46 |
47 | class CategoryDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
48 | model = models.Category
49 | template_name = 'category_delete.html'
50 | success_url = reverse_lazy('category_list')
51 | permission_required = 'categories.delete_category'
52 |
53 |
54 | class CategoryCreateListAPIView(generics.ListCreateAPIView):
55 | queryset = models.Category.objects.all()
56 | serializer_class = serializers.CategorySerializer
57 |
58 |
59 | class CategoryRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
60 | queryset = models.Category.objects.all()
61 | serializer_class = serializers.CategorySerializer
62 |
--------------------------------------------------------------------------------
/cron:
--------------------------------------------------------------------------------
1 | * * * * * cd /sge && /usr/local/bin/python manage.py fazer_coisas >> /var/log/cron.log 2>&1
2 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | sge_web:
4 | build: .
5 | restart: always
6 | ports:
7 | - 8000:8000
8 | depends_on:
9 | - sge_db
10 |
11 | sge_db:
12 | image: postgres:15
13 | ports:
14 | - 5432:5432
15 | volumes:
16 | - postgres_data:/var/lib/postgresql/data/
17 | environment:
18 | - POSTGRES_USER=postgres
19 | - POSTGRES_PASSWORD=postgres
20 | - POSTGRES_DB=sge
21 |
22 | volumes:
23 | postgres_data:
24 |
--------------------------------------------------------------------------------
/inflows/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/inflows/__init__.py
--------------------------------------------------------------------------------
/inflows/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class InflowAdmin(admin.ModelAdmin):
6 | list_display = ('supplier', 'product', 'quantity', 'created_at', 'updated_at',)
7 | search_fields = ('supplier__name', 'product__title',)
8 |
9 |
10 | admin.site.register(models.Inflow, InflowAdmin)
11 |
--------------------------------------------------------------------------------
/inflows/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class InflowsConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'inflows'
7 |
8 | def ready(self):
9 | import inflows.signals # noqa: F401
10 |
--------------------------------------------------------------------------------
/inflows/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from . import models
3 |
4 |
5 | class InflowForm(forms.ModelForm):
6 |
7 | class Meta:
8 | model = models.Inflow
9 | fields = ['supplier', 'product', 'quantity', 'description']
10 | widgets = {
11 | 'supplier': forms.Select(attrs={'class': 'form-control'}),
12 | 'product': forms.Select(attrs={'class': 'form-control'}),
13 | 'quantity': forms.NumberInput(attrs={'class': 'form-control'}),
14 | 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
15 | }
16 | labels = {
17 | 'supplier': 'Fornecedor',
18 | 'product': 'Produto',
19 | 'quantity': 'Quantidade',
20 | 'description': 'Descrição',
21 | }
22 |
--------------------------------------------------------------------------------
/inflows/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-02-09 21:42
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ('products', '0001_initial'),
13 | ('suppliers', '0001_initial'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Inflow',
19 | fields=[
20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('quantity', models.IntegerField()),
22 | ('description', models.TextField(blank=True, null=True)),
23 | ('created_at', models.DateTimeField(auto_now_add=True)),
24 | ('updated_at', models.DateTimeField(auto_now=True)),
25 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inflows', to='products.product')),
26 | ('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inflows', to='suppliers.supplier')),
27 | ],
28 | options={
29 | 'ordering': ['-created_at'],
30 | },
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/inflows/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/inflows/migrations/__init__.py
--------------------------------------------------------------------------------
/inflows/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from products.models import Product
3 | from suppliers.models import Supplier
4 |
5 |
6 | class Inflow(models.Model):
7 | supplier = models.ForeignKey(Supplier, on_delete=models.PROTECT, related_name='inflows')
8 | product = models.ForeignKey(Product, on_delete=models.PROTECT, related_name='inflows')
9 | quantity = models.IntegerField()
10 | description = models.TextField(null=True, blank=True)
11 | created_at = models.DateTimeField(auto_now_add=True)
12 | updated_at = models.DateTimeField(auto_now=True)
13 |
14 | class Meta:
15 | ordering = ['-created_at']
16 |
17 | def __str__(self):
18 | return str(self.product)
19 |
--------------------------------------------------------------------------------
/inflows/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from inflows.models import Inflow
3 |
4 |
5 | class InflowSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | model = Inflow
9 | fields = '__all__'
10 |
--------------------------------------------------------------------------------
/inflows/signals.py:
--------------------------------------------------------------------------------
1 | from django.db.models.signals import post_save
2 | from django.dispatch import receiver
3 | from .models import Inflow
4 |
5 |
6 | @receiver(post_save, sender=Inflow)
7 | def update_product_quantity(sender, instance, created, **kwargs):
8 | if created:
9 | if instance.quantity > 0:
10 | product = instance.product
11 | product.quantity += instance.quantity
12 | product.save()
13 |
--------------------------------------------------------------------------------
/inflows/templates/inflow_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/inflows/templates/inflow_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Detalhes da Entrada
6 |
7 |
8 |
9 |
{{ object.product }}
10 |
Fornecedor: {{ object.supplier }}
11 |
Quantidade: {{ object.quantity }}
12 |
{{ object.description }}
13 |
14 |
15 |
16 |
Voltar para a Lista de Entradas
17 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/inflows/templates/inflow_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Entradas
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
20 | {% if perms.inflows.add_inflow %}
21 |
26 | {% endif %}
27 |
28 |
29 |
30 |
31 |
32 |
33 | ID |
34 | Produto |
35 | Fornecedor |
36 | Quantidade |
37 | Data de entrada |
38 | Ações |
39 |
40 |
41 |
42 | {% for inflow in inflows %}
43 |
44 |
45 |
46 | {{ inflow.id }}
47 |
48 | |
49 | {{ inflow.product }} |
50 | {{ inflow.supplier }} |
51 | {{ inflow.quantity }} |
52 | {{ inflow.created_at }} |
53 |
54 |
55 |
56 |
57 | |
58 |
59 | {% endfor %}
60 |
61 |
62 |
63 |
64 | {% include 'components/_pagination.html' %}
65 |
66 | {% endblock %}
--------------------------------------------------------------------------------
/inflows/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 |
5 | urlpatterns = [
6 | path('inflows/list/', views.InflowListView.as_view(), name='inflow_list'),
7 | path('inflows/create/', views.InflowCreateView.as_view(), name='inflow_create'),
8 | path('inflows//detail/', views.InflowDetailView.as_view(), name='inflow_detail'),
9 |
10 | path('api/v1/inflows/', views.InflowCreateListAPIView.as_view(), name='inflow-create-list-api-view'),
11 | path('api/v1/inflows//', views.InflowRetrieveAPIView.as_view(), name='inflow-detail-api-view'),
12 | ]
13 |
--------------------------------------------------------------------------------
/inflows/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.urls import reverse_lazy
4 | from django.views.generic import ListView, CreateView, DetailView
5 | from . import models, forms, serializers
6 |
7 |
8 | class InflowListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
9 | model = models.Inflow
10 | template_name = 'inflow_list.html'
11 | context_object_name = 'inflows'
12 | paginate_by = 10
13 | permission_required = 'inflows.view_inflow'
14 |
15 | def get_queryset(self):
16 | queryset = super().get_queryset()
17 | product = self.request.GET.get('product')
18 |
19 | if product:
20 | queryset = queryset.filter(product__title__icontains=product)
21 |
22 | return queryset
23 |
24 |
25 | class InflowCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
26 | model = models.Inflow
27 | template_name = 'inflow_create.html'
28 | form_class = forms.InflowForm
29 | success_url = reverse_lazy('inflow_list')
30 | permission_required = 'inflows.add_inflow'
31 |
32 |
33 | class InflowDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
34 | model = models.Inflow
35 | template_name = 'inflow_detail.html'
36 | permission_required = 'inflows.view_inflow'
37 |
38 |
39 | class InflowCreateListAPIView(generics.ListCreateAPIView):
40 | queryset = models.Inflow.objects.all()
41 | serializer_class = serializers.InflowSerializer
42 |
43 |
44 | class InflowRetrieveAPIView(generics.RetrieveAPIView):
45 | queryset = models.Inflow.objects.all()
46 | serializer_class = serializers.InflowSerializer
47 |
--------------------------------------------------------------------------------
/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', 'app.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 |
--------------------------------------------------------------------------------
/outflows/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/outflows/__init__.py
--------------------------------------------------------------------------------
/outflows/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class OutflowAdmin(admin.ModelAdmin):
6 | list_display = ('product', 'quantity', 'created_at', 'updated_at',)
7 | search_fields = ('product__title',)
8 |
9 |
10 | admin.site.register(models.Outflow, OutflowAdmin)
11 |
--------------------------------------------------------------------------------
/outflows/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class OutflowsConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'outflows'
7 |
8 | def ready(self):
9 | import outflows.signals # noqa: F401
10 |
--------------------------------------------------------------------------------
/outflows/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.core.exceptions import ValidationError
3 | from . import models
4 |
5 |
6 | class OutflowForm(forms.ModelForm):
7 | class Meta:
8 | model = models.Outflow
9 | fields = ['product', 'quantity', 'description']
10 | widgets = {
11 | 'product': forms.Select(attrs={'class': 'form-control'}),
12 | 'quantity': forms.NumberInput(attrs={'class': 'form-control'}),
13 | 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
14 | }
15 | labels = {
16 | 'product': 'Produto',
17 | 'quantity': 'Quantidade',
18 | 'description': 'Descrição',
19 | }
20 |
21 | def clean_quantity(self):
22 | quantity = self.cleaned_data.get('quantity')
23 | product = self.cleaned_data.get('product')
24 |
25 | if quantity > product.quantity:
26 | raise ValidationError(
27 | f'A quantidade disponível em estoque para o produto {product.title} é de {product.quantity} unidades.'
28 | )
29 |
30 | return quantity
31 |
--------------------------------------------------------------------------------
/outflows/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-02-09 21:42
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ('products', '0001_initial'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Outflow',
18 | fields=[
19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('quantity', models.IntegerField()),
21 | ('description', models.TextField(blank=True, null=True)),
22 | ('created_at', models.DateTimeField(auto_now_add=True)),
23 | ('updated_at', models.DateTimeField(auto_now=True)),
24 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='outflows', to='products.product')),
25 | ],
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/outflows/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/outflows/migrations/__init__.py
--------------------------------------------------------------------------------
/outflows/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from products.models import Product
3 |
4 |
5 | class Outflow(models.Model):
6 | product = models.ForeignKey(Product, on_delete=models.PROTECT, related_name='outflows')
7 | quantity = models.IntegerField()
8 | description = models.TextField(null=True, blank=True)
9 | created_at = models.DateTimeField(auto_now_add=True)
10 | updated_at = models.DateTimeField(auto_now=True)
11 |
12 | def __str__(self):
13 | return str(self.product)
14 |
--------------------------------------------------------------------------------
/outflows/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from outflows.models import Outflow
3 |
4 |
5 | class OutflowSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | model = Outflow
9 | fields = '__all__'
10 |
--------------------------------------------------------------------------------
/outflows/signals.py:
--------------------------------------------------------------------------------
1 | from django.db.models.signals import post_save
2 | from django.dispatch import receiver
3 | from .models import Outflow
4 |
5 |
6 | @receiver(post_save, sender=Outflow)
7 | def update_product_quantity(sender, instance, created, **kwargs):
8 | if created:
9 | if instance.quantity > 0:
10 | product = instance.product
11 | product.quantity -= instance.quantity
12 | product.save()
13 |
--------------------------------------------------------------------------------
/outflows/templates/outflow_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/outflows/templates/outflow_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Detalhes da Saída
6 |
7 |
8 |
9 |
{{ object.product }}
10 |
Fornecedor: {{ object.supplier }}
11 |
Quantidade: {{ object.quantity }}
12 |
{{ object.description }}
13 |
14 |
15 |
16 |
Voltar para a Lista de Saídas
17 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/outflows/templates/outflow_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Saídas
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% if perms.outflows.view_outflow %}
10 | {% include 'components/_sales_metrics.html' %}
11 | {% endif %}
12 |
13 |
14 |
15 |
16 |
26 | {% if perms.outflows.add_outflow %}
27 |
32 | {% endif %}
33 |
34 |
35 |
36 |
37 |
38 |
39 | ID |
40 | Produto |
41 | Fornecedor |
42 | Quantidade |
43 | Data de entrada |
44 | Ações |
45 |
46 |
47 |
48 | {% for outflow in outflows %}
49 |
50 |
51 |
52 | {{ outflow.id }}
53 |
54 | |
55 | {{ outflow.product }} |
56 | {{ outflow.supplier }} |
57 | {{ outflow.quantity }} |
58 | {{ outflow.created_at }} |
59 |
60 |
61 |
62 |
63 | |
64 |
65 | {% endfor %}
66 |
67 |
68 |
69 |
70 | {% include 'components/_pagination.html' %}
71 |
72 | {% endblock %}
--------------------------------------------------------------------------------
/outflows/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 |
5 | urlpatterns = [
6 | path('outflows/list/', views.OutflowListView.as_view(), name='outflow_list'),
7 | path('outflows/create/', views.OutflowCreateView.as_view(), name='outflow_create'),
8 | path('outflows//detail/', views.OutflowDetailView.as_view(), name='outflow_detail'),
9 |
10 | path('api/v1/outflows/', views.OutflowCreateListAPIView.as_view(), name='outflow-create-list-api-view'),
11 | path('api/v1/outflows//', views.OutflowRetrieveAPIView.as_view(), name='outflow-detail-api-view'),
12 | ]
13 |
--------------------------------------------------------------------------------
/outflows/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.urls import reverse_lazy
4 | from django.views.generic import ListView, CreateView, DetailView
5 | from app import metrics
6 | from . import models, forms, serializers
7 |
8 |
9 | class OutflowListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
10 | model = models.Outflow
11 | template_name = 'outflow_list.html'
12 | context_object_name = 'outflows'
13 | paginate_by = 10
14 | permission_required = 'outflows.view_outflow'
15 |
16 | def get_queryset(self):
17 | queryset = super().get_queryset()
18 | product = self.request.GET.get('product')
19 |
20 | if product:
21 | queryset = queryset.filter(product__title__icontains=product)
22 |
23 | return queryset
24 |
25 | def get_context_data(self, **kwargs):
26 | context = super().get_context_data(**kwargs)
27 | context['product_metrics'] = metrics.get_product_metrics()
28 | context['sales_metrics'] = metrics.get_sales_metrics()
29 | return context
30 |
31 |
32 | class OutflowCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
33 | model = models.Outflow
34 | template_name = 'outflow_create.html'
35 | form_class = forms.OutflowForm
36 | success_url = reverse_lazy('outflow_list')
37 | permission_required = 'outflows.add_outflow'
38 |
39 |
40 | class OutflowDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
41 | model = models.Outflow
42 | template_name = 'outflow_detail.html'
43 | permission_required = 'outflows.view_outflow'
44 |
45 |
46 | class OutflowCreateListAPIView(generics.ListCreateAPIView):
47 | queryset = models.Outflow.objects.all()
48 | serializer_class = serializers.OutflowSerializer
49 |
50 |
51 | class OutflowRetrieveAPIView(generics.RetrieveAPIView):
52 | queryset = models.Outflow.objects.all()
53 | serializer_class = serializers.OutflowSerializer
54 |
--------------------------------------------------------------------------------
/products/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/products/__init__.py
--------------------------------------------------------------------------------
/products/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class ProductAdmin(admin.ModelAdmin):
6 | list_display = ('title', 'serie_number',)
7 | search_fields = ('title',)
8 |
9 |
10 | admin.site.register(models.Product, ProductAdmin)
11 |
--------------------------------------------------------------------------------
/products/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ProductsConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'products'
7 |
--------------------------------------------------------------------------------
/products/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from . import models
3 |
4 |
5 | class ProductForm(forms.ModelForm):
6 |
7 | class Meta:
8 | model = models.Product
9 | fields = ['title', 'category', 'brand', 'description', 'serie_number', 'cost_price', 'selling_price']
10 | widgets = {
11 | 'title': forms.TextInput(attrs={'class': 'form-control'}),
12 | 'category': forms.Select(attrs={'class': 'form-control'}),
13 | 'brand': forms.Select(attrs={'class': 'form-control'}),
14 | 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
15 | 'serie_number': forms.TextInput(attrs={'class': 'form-control'}),
16 | 'cost_price': forms.NumberInput(attrs={'class': 'form-control'}),
17 | 'selling_price': forms.NumberInput(attrs={'class': 'form-control'}),
18 | }
19 | labels = {
20 | 'title': 'Título',
21 | 'category': 'Categoria',
22 | 'brand': 'Marca',
23 | 'description': 'Descrição',
24 | 'serie_number': 'Número de Série',
25 | 'cost_price': 'Preço de Custo',
26 | 'selling_price': 'Preço de Venda',
27 | }
28 |
--------------------------------------------------------------------------------
/products/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/products/management/__init__.py
--------------------------------------------------------------------------------
/products/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/products/management/commands/__init__.py
--------------------------------------------------------------------------------
/products/management/commands/fazer_coisas.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 |
4 | class Command(BaseCommand):
5 |
6 | def handle(self, *args, **options):
7 | self.stdout.write(
8 | self.style.SUCCESS('COISAS FEITAS COM SUCESSO!')
9 | )
10 |
--------------------------------------------------------------------------------
/products/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-02-09 21:42
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ('brands', '0001_initial'),
13 | ('categories', '0001_initial'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Product',
19 | fields=[
20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=500)),
22 | ('description', models.TextField(blank=True, null=True)),
23 | ('serie_number', models.CharField(blank=True, max_length=200, null=True)),
24 | ('cost_price', models.DecimalField(decimal_places=2, max_digits=20)),
25 | ('selling_price', models.DecimalField(decimal_places=2, max_digits=20)),
26 | ('quantity', models.IntegerField(default=0)),
27 | ('created_at', models.DateTimeField(auto_now_add=True)),
28 | ('updated_at', models.DateTimeField(auto_now=True)),
29 | ('brand', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='brands.brand')),
30 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='categories.category')),
31 | ],
32 | options={
33 | 'ordering': ['title'],
34 | },
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/products/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/products/migrations/__init__.py
--------------------------------------------------------------------------------
/products/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from categories.models import Category
3 | from brands.models import Brand
4 |
5 |
6 | class Product(models.Model):
7 | title = models.CharField(max_length=500)
8 | category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name='products')
9 | brand = models.ForeignKey(Brand, on_delete=models.PROTECT, related_name='products')
10 | description = models.TextField(null=True, blank=True)
11 | serie_number = models.CharField(max_length=200, null=True, blank=True)
12 | cost_price = models.DecimalField(max_digits=20, decimal_places=2)
13 | selling_price = models.DecimalField(max_digits=20, decimal_places=2)
14 | quantity = models.IntegerField(default=0)
15 | created_at = models.DateTimeField(auto_now_add=True)
16 | updated_at = models.DateTimeField(auto_now=True)
17 |
18 | class Meta:
19 | ordering = ['title']
20 |
21 | def __str__(self):
22 | return self.title
23 |
--------------------------------------------------------------------------------
/products/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from products.models import Product
3 |
4 |
5 | class ProductSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | model = Product
9 | fields = '__all__'
10 |
--------------------------------------------------------------------------------
/products/templates/product_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/products/templates/product_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Deletar Produto
6 |
7 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/products/templates/product_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Detalhes do Produto
6 |
7 |
8 |
9 |
{{ object.title }}
10 |
Categoria: {{ object.category }}
11 |
Marca: {{ object.brand }}
12 |
{{ object.description }}
13 |
Número de Série: {{ object.serie_number }}
14 |
Preço de Custo: R$ {{ object.cost_price }}
15 |
Preço de Venda: R$ {{ object.selling_price }}
16 |
Quantidade em Estoque: {{ object.quantity }}
17 |
18 |
19 |
20 |
Voltar para a Lista de Produtos
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/products/templates/product_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Produtos
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% if perms.products.view_product and perms.inflows.view_inflow %}
10 | {% include 'components/_product_metrics.html' %}
11 | {% endif %}
12 |
13 |
14 |
15 |
16 |
27 | {% if perms.products.add_product %}
28 |
33 | {% endif %}
34 |
35 |
36 |
59 |
60 |
61 |
62 |
63 |
64 | ID |
65 | Título |
66 | Categoria |
67 | Marca |
68 | Preço de Custo |
69 | Preço de Venda |
70 | Número de Série |
71 | Quantidade |
72 | Ações |
73 |
74 |
75 |
76 | {% for product in products %}
77 |
78 |
79 |
80 | {{ product.id }}
81 |
82 | |
83 | {{ product.title }} |
84 | {{ product.category }} |
85 | {{ product.brand }} |
86 | R$ {{ product.cost_price }} |
87 | R$ {{ product.selling_price }} |
88 | {{ product.serie_number }} |
89 | {{ product.quantity }} |
90 |
91 |
92 |
93 |
94 |
95 | {% if perms.products.change_product %}
96 |
97 |
98 |
99 | {% endif %}
100 |
101 | {% if perms.products.delete_product %}
102 |
103 |
104 |
105 | {% endif %}
106 | |
107 |
108 | {% endfor %}
109 |
110 |
111 |
112 |
113 | {% include 'components/_pagination.html' %}
114 |
115 | {% endblock %}
116 |
--------------------------------------------------------------------------------
/products/templates/product_update.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/products/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 |
5 | urlpatterns = [
6 | path('products/list/', views.ProductListView.as_view(), name='product_list'),
7 | path('products/create/', views.ProductCreateView.as_view(), name='product_create'),
8 | path('products//detail/', views.ProductDetailView.as_view(), name='product_detail'),
9 | path('products//update/', views.ProductUpdateView.as_view(), name='product_update'),
10 | path('products//delete/', views.ProductDeleteView.as_view(), name='product_delete'),
11 |
12 | path('api/v1/products/', views.ProductCreateListAPIView.as_view(), name='product-create-list-api-view'),
13 | path('api/v1/products//', views.ProductRetrieveUpdateDestroyAPIView.as_view(), name='product-detail-api-view'),
14 | ]
15 |
--------------------------------------------------------------------------------
/products/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.urls import reverse_lazy
4 | from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
5 | from app import metrics
6 | from brands.models import Brand
7 | from categories.models import Category
8 | from . import models, forms, serializers
9 |
10 |
11 | class ProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
12 | model = models.Product
13 | template_name = 'product_list.html'
14 | context_object_name = 'products'
15 | paginate_by = 10
16 | permission_required = 'products.view_product'
17 |
18 | def get_queryset(self):
19 | queryset = super().get_queryset()
20 | title = self.request.GET.get('title')
21 | serie_number = self.request.GET.get('serie_number')
22 | category = self.request.GET.get('category')
23 | brand = self.request.GET.get('brand')
24 |
25 | if title:
26 | queryset = queryset.filter(title__icontains=title)
27 | if serie_number:
28 | queryset = queryset.filter(serie_number__icontains=serie_number)
29 | if category:
30 | queryset = queryset.filter(category_id=category)
31 | if brand:
32 | queryset = queryset.filter(brand__id=brand)
33 |
34 | return queryset
35 |
36 | def get_context_data(self, **kwargs):
37 | context = super().get_context_data(**kwargs)
38 | context['product_metrics'] = metrics.get_product_metrics()
39 | context['sales_metrics'] = metrics.get_sales_metrics()
40 | context['categories'] = Category.objects.all()
41 | context['brands'] = Brand.objects.all()
42 | return context
43 |
44 |
45 | class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
46 | model = models.Product
47 | template_name = 'product_create.html'
48 | form_class = forms.ProductForm
49 | success_url = reverse_lazy('product_list')
50 | permission_required = 'products.add_product'
51 |
52 |
53 | class ProductDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
54 | model = models.Product
55 | template_name = 'product_detail.html'
56 | permission_required = 'products.view_product'
57 |
58 |
59 | class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
60 | model = models.Product
61 | template_name = 'product_update.html'
62 | form_class = forms.ProductForm
63 | success_url = reverse_lazy('product_list')
64 | permission_required = 'products.change_product'
65 |
66 |
67 | class ProductDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
68 | model = models.Product
69 | template_name = 'product_delete.html'
70 | success_url = reverse_lazy('product_list')
71 | permission_required = 'products.delete_product'
72 |
73 |
74 | class ProductCreateListAPIView(generics.ListCreateAPIView):
75 | queryset = models.Product.objects.all()
76 | serializer_class = serializers.ProductSerializer
77 |
78 |
79 | class ProductRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
80 | queryset = models.Product.objects.all()
81 | serializer_class = serializers.ProductSerializer
82 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.7.0
2 | anyio==4.8.0
3 | asgiref==3.8.1
4 | certifi==2024.12.14
5 | distro==1.9.0
6 | Django==5.0.1
7 | djangorestframework==3.15.1
8 | djangorestframework-simplejwt==5.3.1
9 | h11==0.14.0
10 | httpcore==1.0.7
11 | httpx==0.28.1
12 | idna==3.10
13 | openai==1.38.0
14 | psycopg2-binary==2.9.10
15 | pydantic==2.10.6
16 | pydantic_core==2.27.2
17 | PyJWT==2.10.1
18 | sniffio==1.3.1
19 | sqlparse==0.5.3
20 | tqdm==4.67.1
21 | typing_extensions==4.12.2
22 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | flake8==7.0.0
2 |
--------------------------------------------------------------------------------
/suppliers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/suppliers/__init__.py
--------------------------------------------------------------------------------
/suppliers/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 |
4 |
5 | class SupplierAdmin(admin.ModelAdmin):
6 | list_display = ('name', 'description',)
7 | search_fields = ('name',)
8 |
9 |
10 | admin.site.register(models.Supplier, SupplierAdmin)
11 |
--------------------------------------------------------------------------------
/suppliers/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class SuppliersConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'suppliers'
7 |
--------------------------------------------------------------------------------
/suppliers/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from . import models
3 |
4 |
5 | class SupplierForm(forms.ModelForm):
6 |
7 | class Meta:
8 | model = models.Supplier
9 | fields = ['name', 'description']
10 | widgets = {
11 | 'name': forms.TextInput(attrs={'class': 'form-control'}),
12 | 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
13 | }
14 | labels = {
15 | 'name': 'Nome',
16 | 'description': 'Descrição',
17 | }
18 |
--------------------------------------------------------------------------------
/suppliers/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.0.1 on 2024-02-09 21:42
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='Supplier',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=500)),
19 | ('description', models.TextField(blank=True, null=True)),
20 | ('created_at', models.DateTimeField(auto_now_add=True)),
21 | ('updated_at', models.DateTimeField(auto_now=True)),
22 | ],
23 | options={
24 | 'ordering': ['name'],
25 | },
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/suppliers/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycodebr/sge/18d17f898802a3c67b3fbb0463757f0c182d1717/suppliers/migrations/__init__.py
--------------------------------------------------------------------------------
/suppliers/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Supplier(models.Model):
5 | name = models.CharField(max_length=500)
6 | description = models.TextField(null=True, blank=True)
7 | created_at = models.DateTimeField(auto_now_add=True)
8 | updated_at = models.DateTimeField(auto_now=True)
9 |
10 | class Meta:
11 | ordering = ['name']
12 |
13 | def __str__(self):
14 | return self.name
15 |
--------------------------------------------------------------------------------
/suppliers/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from suppliers.models import Supplier
3 |
4 |
5 | class SupplierSerializer(serializers.ModelSerializer):
6 |
7 | class Meta:
8 | model = Supplier
9 | fields = '__all__'
10 |
--------------------------------------------------------------------------------
/suppliers/templates/supplier_create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/suppliers/templates/supplier_delete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 |
Deletar Fornecedor
6 |
7 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/suppliers/templates/supplier_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/suppliers/templates/supplier_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | SGE - Fornecedores
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
20 | {% if perms.suppliers.add_supplier %}
21 |
26 | {% endif %}
27 |
28 |
29 |
30 |
31 |
32 |
33 | ID |
34 | Nome |
35 | Descrição |
36 | Ações |
37 |
38 |
39 |
40 | {% for supplier in suppliers %}
41 |
42 |
43 |
44 | {{ supplier.id }}
45 |
46 | |
47 | {{ supplier.name }} |
48 | {{ supplier.description }} |
49 |
50 |
51 |
52 |
53 |
54 | {% if perms.suppliers.change_supplier %}
55 |
56 |
57 |
58 | {% endif %}
59 |
60 | {% if perms.suppliers.delete_supplier %}
61 |
62 |
63 |
64 | {% endif %}
65 | |
66 |
67 | {% endfor %}
68 |
69 |
70 |
71 |
72 | {% include 'components/_pagination.html' %}
73 |
74 | {% endblock %}
--------------------------------------------------------------------------------
/suppliers/templates/supplier_update.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/suppliers/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from . import views
3 |
4 |
5 | urlpatterns = [
6 | path('suppliers/list/', views.SupplierListView.as_view(), name='supplier_list'),
7 | path('suppliers/create/', views.SupplierCreateView.as_view(), name='supplier_create'),
8 | path('suppliers//detail/', views.SupplierDetailView.as_view(), name='supplier_detail'),
9 | path('suppliers//update/', views.SupplierUpdateView.as_view(), name='supplier_update'),
10 | path('suppliers//delete/', views.SupplierDeleteView.as_view(), name='supplier_delete'),
11 |
12 | path('api/v1/suppliers/', views.SupplierCreateListAPIView.as_view(), name='supplier-create-list-api-view'),
13 | path('api/v1/suppliers//', views.SupplierRetrieveUpdateDestroyAPIView.as_view(), name='supplier-detail-api-view'),
14 | ]
15 |
--------------------------------------------------------------------------------
/suppliers/views.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics
2 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
3 | from django.urls import reverse_lazy
4 | from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
5 | from . import models, forms, serializers
6 |
7 |
8 | class SupplierListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
9 | model = models.Supplier
10 | template_name = 'supplier_list.html'
11 | context_object_name = 'suppliers'
12 | paginate_by = 10
13 | permission_required = 'suppliers.view_supplier'
14 |
15 | def get_queryset(self):
16 | queryset = super().get_queryset()
17 | name = self.request.GET.get('name')
18 |
19 | if name:
20 | queryset = queryset.filter(name__icontains=name)
21 |
22 | return queryset
23 |
24 |
25 | class SupplierCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
26 | model = models.Supplier
27 | template_name = 'supplier_create.html'
28 | form_class = forms.SupplierForm
29 | success_url = reverse_lazy('supplier_list')
30 | permission_required = 'suppliers.add_supplier'
31 |
32 |
33 | class SupplierDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
34 | model = models.Supplier
35 | template_name = 'supplier_detail.html'
36 | permission_required = 'suppliers.view_supplier'
37 |
38 |
39 | class SupplierUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
40 | model = models.Supplier
41 | template_name = 'supplier_update.html'
42 | form_class = forms.SupplierForm
43 | success_url = reverse_lazy('supplier_list')
44 | permission_required = 'suppliers.change_supplier'
45 |
46 |
47 | class SupplierDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
48 | model = models.Supplier
49 | template_name = 'supplier_delete.html'
50 | success_url = reverse_lazy('supplier_list')
51 | permission_required = 'suppliers.delete_supplier'
52 |
53 |
54 | class SupplierCreateListAPIView(generics.ListCreateAPIView):
55 | queryset = models.Supplier.objects.all()
56 | serializer_class = serializers.SupplierSerializer
57 |
58 |
59 | class SupplierRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
60 | queryset = models.Supplier.objects.all()
61 | serializer_class = serializers.SupplierSerializer
62 |
--------------------------------------------------------------------------------