From 740547a8ed9787e0d56cd53e90718d8c939d21f1 Mon Sep 17 00:00:00 2001 From: Eberton Kociba Date: Sun, 31 May 2026 01:12:30 -0300 Subject: [PATCH] =?UTF-8?q?BatchType=20|=20Autoriza=C3=A7=C3=A3o=20de=20us?= =?UTF-8?q?u=C3=A1rios=20|=20Fatores=20de=20Consumo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Com a finalização da estrutura da tela de configuração da fazenda, três recursos foram implementados: 1 - Fatores de consumo: Agora é possível definir três fatores diferentes, o consumo padrão (ainda passará por ajustes) que representará o consumo do animal com base em seu peso, o consumo no verão, valor utilizado para correção de consumo nos meses de verão e consumo de inverno segundo a mesma lógica da informação anterior; 2 - BatchTypes: Tipos de lote, permite ao usuário, na tela de configuração da sua fazenda, criar tipos para os lotes, os quais carregam um multiplicador de consumo, utilizado futuramente na geração de relatório como uma camada de correção a mais nos valores; 3 - Permissão de acesso para outros usuários: A tela de configuração da fazenda agora permite que sejam inclusos outros usuários na fazenda selecionada, carregando as mesmas permissões e capacidades do dono. --- multigranja/apps/core/static/core/js/main.js | 7 + .../apps/core/templates/layouts/base.html | 4 +- multigranja/apps/entities/admin.py | 3 +- multigranja/apps/entities/forms/animal.py | 15 +- .../0002_batchtype_alter_batch_batch_type.py | 29 ++++ ...mption_farm_summer_consumption_and_more.py | 28 ++++ multigranja/apps/entities/models/animal.py | 30 +++- multigranja/apps/entities/models/farm.py | 17 +++ multigranja/apps/entities/services/animal.py | 118 ++++++++++++++-- multigranja/apps/entities/services/farm.py | 92 ++++++++++-- .../static/farm/css/user_authorize.css | 120 ++++++++++++++++ .../static/rotation/js/rotation_list.js | 9 +- .../templates/batch/batchtype_create.html | 11 ++ .../templates/batch/batchtype_update.html | 11 ++ .../entities/templates/farm/farm_home.html | 131 +++++++++++++----- .../templates/farm/user-auth-card.html | 22 +++ .../templates/farm/user_authorize.html | 12 ++ .../templates/layouts/entity_list.html | 2 +- multigranja/apps/entities/urls/animal.py | 4 + multigranja/apps/entities/urls/farm.py | 6 + multigranja/apps/entities/views/animal.py | 38 ++++- multigranja/apps/entities/views/farm.py | 34 ++++- 22 files changed, 672 insertions(+), 71 deletions(-) create mode 100644 multigranja/apps/entities/migrations/0002_batchtype_alter_batch_batch_type.py create mode 100644 multigranja/apps/entities/migrations/0003_farm_default_consumption_farm_summer_consumption_and_more.py create mode 100644 multigranja/apps/entities/static/farm/css/user_authorize.css create mode 100644 multigranja/apps/entities/templates/batch/batchtype_create.html create mode 100644 multigranja/apps/entities/templates/batch/batchtype_update.html create mode 100644 multigranja/apps/entities/templates/farm/user-auth-card.html create mode 100644 multigranja/apps/entities/templates/farm/user_authorize.html diff --git a/multigranja/apps/core/static/core/js/main.js b/multigranja/apps/core/static/core/js/main.js index ad7c8c0..6164005 100644 --- a/multigranja/apps/core/static/core/js/main.js +++ b/multigranja/apps/core/static/core/js/main.js @@ -41,4 +41,11 @@ document.addEventListener("DOMContentLoaded", () => { } }); }); +}); + +// Blocks scroll form changing the value on inputs type number +document.querySelectorAll('input[type="number"]').forEach(input => { + input.addEventListener('wheel', function(event) { + event.target.blur(); + }); }); \ No newline at end of file diff --git a/multigranja/apps/core/templates/layouts/base.html b/multigranja/apps/core/templates/layouts/base.html index 21ac856..8b88ec7 100644 --- a/multigranja/apps/core/templates/layouts/base.html +++ b/multigranja/apps/core/templates/layouts/base.html @@ -11,10 +11,12 @@ - + + + {% block extra_css %}{% endblock %} diff --git a/multigranja/apps/entities/admin.py b/multigranja/apps/entities/admin.py index 6c53895..d4374f7 100644 --- a/multigranja/apps/entities/admin.py +++ b/multigranja/apps/entities/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from .models.user import User from .models.farm import Farm, PaddockGroup, Paddock -from .models.animal import Animal, Batch, BatchAnimal, FeedingRecord +from .models.animal import Animal, BatchType, Batch, BatchAnimal, FeedingRecord from .models.rotation import Rotation admin.site.register([ @@ -15,4 +15,5 @@ admin.site.register([ BatchAnimal, FeedingRecord, Rotation, + BatchType ]) \ No newline at end of file diff --git a/multigranja/apps/entities/forms/animal.py b/multigranja/apps/entities/forms/animal.py index f7c9e93..6216035 100644 --- a/multigranja/apps/entities/forms/animal.py +++ b/multigranja/apps/entities/forms/animal.py @@ -1,5 +1,5 @@ from django import forms -from ..models.animal import Batch, Animal +from ..models.animal import Batch, Animal, BatchType class AnimalCreateForm(forms.ModelForm): class Meta: @@ -42,4 +42,15 @@ class BatchForm(forms.ModelForm): 'batch_type': 'Tipo', 'animals': 'Animais', } -BatchCreateForm = BatchEditForm = BatchForm # Alias to BatchForm since its implementations are the same \ No newline at end of file +BatchCreateForm = BatchEditForm = BatchForm # Alias to BatchForm since its implementations are the same + +class BatchTypeForm(forms.ModelForm): + class Meta: + model = BatchType + fields = ['name', 'consumption_factor'] + + labels = { + 'name': 'Nome', + 'consumption_factor': 'Fator de consumo', + } +BatchTypeCreateForm = BatchTypeEditForm = BatchTypeForm \ No newline at end of file diff --git a/multigranja/apps/entities/migrations/0002_batchtype_alter_batch_batch_type.py b/multigranja/apps/entities/migrations/0002_batchtype_alter_batch_batch_type.py new file mode 100644 index 0000000..d8e317b --- /dev/null +++ b/multigranja/apps/entities/migrations/0002_batchtype_alter_batch_batch_type.py @@ -0,0 +1,29 @@ +# Generated by Django 6.0.2 on 2026-05-30 03:13 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('entities', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='BatchType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_active', models.BooleanField(default=True)), + ('name', models.CharField(max_length=45)), + ('consumption_factor', models.DecimalField(decimal_places=2, default=1.0, max_digits=4)), + ('farm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='batch_types', to='entities.farm')), + ], + ), + migrations.AlterField( + model_name='batch', + name='batch_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='batches', to='entities.batchtype'), + ), + ] diff --git a/multigranja/apps/entities/migrations/0003_farm_default_consumption_farm_summer_consumption_and_more.py b/multigranja/apps/entities/migrations/0003_farm_default_consumption_farm_summer_consumption_and_more.py new file mode 100644 index 0000000..346dc94 --- /dev/null +++ b/multigranja/apps/entities/migrations/0003_farm_default_consumption_farm_summer_consumption_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 6.0.2 on 2026-05-30 19:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('entities', '0002_batchtype_alter_batch_batch_type'), + ] + + operations = [ + migrations.AddField( + model_name='farm', + name='default_consumption', + field=models.DecimalField(decimal_places=2, default=1.0, max_digits=4), + ), + migrations.AddField( + model_name='farm', + name='summer_consumption', + field=models.DecimalField(decimal_places=2, default=1.0, max_digits=4), + ), + migrations.AddField( + model_name='farm', + name='winter_consumption', + field=models.DecimalField(decimal_places=2, default=1.0, max_digits=4), + ), + ] diff --git a/multigranja/apps/entities/models/animal.py b/multigranja/apps/entities/models/animal.py index 995fa4c..f3c9b83 100644 --- a/multigranja/apps/entities/models/animal.py +++ b/multigranja/apps/entities/models/animal.py @@ -35,6 +35,10 @@ class Animal(models.Model): return f"{months} meses" # MM months else: return "Menos de um mês" # Less than a month + + @property + def is_in_batch(self): + return self.batchanimal_set.filter(is_active=True).exists() class Meta: constraints = [ @@ -45,11 +49,25 @@ class Animal(models.Model): ) ] +class BatchType(models.Model): + is_active = models.BooleanField(default=True) + farm = models.ForeignKey('entities.Farm', on_delete=models.CASCADE, related_name='batch_types') + name = models.CharField(max_length=45) + consumption_factor = models.DecimalField(max_digits=4, decimal_places=2, default=1.00) + + def __str__(self): + state = 'Ativo' if self.is_active else 'Inativo' + return f"{state} - {self.name}, {self.consumption_factor}" + + @property + def consumption_percentage(self): + return int((self.consumption_factor - 1) * 100) + class Batch(models.Model): is_active = models.BooleanField(default=True) identifier = models.CharField(max_length=45) animal_category = models.CharField(max_length=45, null=True, blank=True) - batch_type = models.CharField(max_length=45, null=True, blank=True) + batch_type = models.ForeignKey(BatchType, on_delete=models.SET_NULL, related_name='batches', null=True, blank=True) animals = models.ManyToManyField(Animal, through='BatchAnimal', related_name='batches') def __init__(self, *args, **kwargs): @@ -93,6 +111,16 @@ class BatchAnimal(models.Model): def __str__(self): return f"{self.animal.identifier} in {self.batch.identifier}" + + def save(self, *args, **kwargs): + is_active_changed = self.__original_is_active != self.is_active + if is_active_changed and self.is_active: + raise ValueError( + "Reactivating a BatchAnimal is not allowed automatically because it may violate " + "the unique active batch constraint for an animal. Review the logic in " + "models/animal.py (BatchAnimal.save) before proceeding." + ) + super().save(*args, **kwargs) class FeedingRecord(models.Model): animal = models.ForeignKey(Animal, on_delete=models.CASCADE, related_name='feeding_record') diff --git a/multigranja/apps/entities/models/farm.py b/multigranja/apps/entities/models/farm.py index ecc7aeb..db53649 100644 --- a/multigranja/apps/entities/models/farm.py +++ b/multigranja/apps/entities/models/farm.py @@ -4,9 +4,26 @@ class Farm(models.Model): is_active = models.BooleanField(default=True) name = models.CharField(max_length=45) total_area = models.IntegerField(null=True, blank=True) + default_consumption = models.DecimalField(max_digits=4, decimal_places=2, default=1.00) + summer_consumption = models.DecimalField(max_digits=4, decimal_places=2, default=1.00) + winter_consumption = models.DecimalField(max_digits=4, decimal_places=2, default=1.00) + def __str__(self): return self.name + + @property + def animal_count(self): + return self.animals.filter( + is_active=True + ).count() + + @property + def paddock_count(self): + return self.paddocks.filter( + is_active=True + ).count() + class PaddockGroup(models.Model): is_active = models.BooleanField(default=True) diff --git a/multigranja/apps/entities/services/animal.py b/multigranja/apps/entities/services/animal.py index 10f58d7..f180638 100644 --- a/multigranja/apps/entities/services/animal.py +++ b/multigranja/apps/entities/services/animal.py @@ -1,8 +1,8 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse -from ..models import Animal, Batch -from ..forms import AnimalCreateForm, AnimalEditForm, BatchForm +from ..models import Animal, Batch, BatchType +from ..forms import AnimalCreateForm, AnimalEditForm, BatchForm, BatchTypeCreateForm, BatchTypeEditForm from entities.models import Farm # Local utility functions @@ -15,15 +15,36 @@ def _get_selected_farm(request): def _get_animal_by_id(request, animal_id): return get_object_or_404(Animal, pk=animal_id, farm_id=_get_selected_farm_id(request)) -def _get_farms_active_animals(request): - return Animal.objects.filter( - farm=_get_selected_farm_id(request), - is_active=True - ) +# def _get_farms_active_animals(request): +# return Animal.objects.filter( +# farm=_get_selected_farm_id(request), +# is_active=True +# ) def _get_batch_by_id(request, batch_id): return get_object_or_404(Batch, pk=batch_id, is_active=True) +def _get_farms_batch_types(request): + return BatchType.objects.filter(is_active=True, farm_id=_get_selected_farm_id(request)) + +def _get_no_batch_farms_animals(request): + return Animal.objects.filter( + farm_id=_get_selected_farm_id(request), + is_active=True + ).exclude( + batchanimal__is_active=True + ) + +def _get_animals_from_batch(batch_id): + return Batch.objects.get( + id=batch_id + ).animals.filter( + batchanimal__is_active=True + ) + +def _get_batchtype_by_id(request, batchtype_id): + return get_object_or_404(BatchType, pk=batchtype_id, is_active=True, farm=_get_selected_farm(request)) + # Animal Services def get_animal_create_context(): form = AnimalCreateForm() @@ -92,7 +113,8 @@ def delete_animal(request, animal_id): # Batch Services def get_batch_create_context(request): form = BatchForm() - form.fields['animals'].queryset = _get_farms_active_animals(request) + form.fields['animals'].queryset = _get_no_batch_farms_animals(request) + form.fields['batch_type'].queryset = _get_farms_batch_types(request) context = { 'title': 'Novo Lote', @@ -117,8 +139,12 @@ def create_batch(request): def get_batch_update_context(request, batch_id): batch_old = _get_batch_by_id(request, batch_id) + animals_querset = _get_no_batch_farms_animals(request) | _get_animals_from_batch(batch_id) + animals_querset = animals_querset.distinct() + form = BatchForm(instance=batch_old) - form.fields['animals'].queryset = _get_farms_active_animals(request) + form.fields['animals'].queryset = animals_querset + form.fields['batch_type'].queryset = _get_farms_batch_types(request) context = { 'title': f'Editar Lote {batch_old.identifier}', @@ -163,4 +189,76 @@ def get_batch_delete_context(request, batch_id): def delete_batch(request, batch_id): batch = _get_batch_by_id(request, batch_id) batch.is_active = False - batch.save() \ No newline at end of file + batch.save() + +# Batch Type Services +def get_batchtype_create_context(request): + form = BatchTypeCreateForm() + + context = { + 'title': 'Novo Tipo de Lote', + 'form': form, + 'action_url': reverse('entities:animal:batchtype_create'), + 'return_url': reverse('entities:farm:farm_home'), + } + + return context + +def create_batchtype(request): + form = BatchTypeCreateForm(request.POST) + + if form.is_valid(): + batchtype = form.save(commit=False) + batchtype.farm = _get_selected_farm(request) + batchtype.save() + + return True + + return False + +def get_batchtype_update_context(request, batchtype_id): + batchtype = _get_batchtype_by_id(request, batchtype_id) + form = BatchTypeEditForm(instance=batchtype) + + context = {} + context['title'] = 'Editar tipo de lote' + context['action_url'] = reverse( + 'entities:animal:batchtype_update', + kwargs={'batchtype_id': batchtype.pk} + ) + context['return_url'] = reverse('entities:farm:farm_home') + context['form'] = form + + return context + +def update_batchtype(request, batchtype_id): + batchtype = _get_batchtype_by_id(request, batchtype_id) + form = BatchTypeEditForm(request.POST, instance=batchtype) + + if form.is_valid(): + form.save() + return True + + return False + +def get_batchtype_delete_context(request, batchtype_id): + batchtype = _get_batchtype_by_id(request, batchtype_id) + context = {} + context['description'] = f"Excluir tipo de lote {batchtype.name}?" + context['positive_url'] = reverse('entities:animal:batchtype_delete', kwargs={'batchtype_id': batchtype_id}) + context['negative_url'] = reverse('entities:farm:farm_home') + + return context + +def delete_batchtype(request, batchtype_id): + batchtype = _get_batchtype_by_id(request, batchtype_id) + + Batch.objects.filter( + is_active=True, + batch_type=batchtype + ).update( + batch_type=None + ) + + batchtype.is_active = False + batchtype.save() \ No newline at end of file diff --git a/multigranja/apps/entities/services/farm.py b/multigranja/apps/entities/services/farm.py index f22008f..5ad9fcd 100644 --- a/multigranja/apps/entities/services/farm.py +++ b/multigranja/apps/entities/services/farm.py @@ -1,7 +1,9 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse +from django.db.models import Count, Q +from decimal import Decimal -from ..models import Farm, Paddock, PaddockGroup +from ..models import Farm, Paddock, PaddockGroup, BatchType, User from ..forms import FarmCreateForm, FarmEditForm, PaddockCreateForm, PaddockEditForm, PaddockGroupCreateForm, PaddockGroupEditForm @@ -43,20 +45,36 @@ def _get_paddock_group_by_id(request, paddock_group_id): is_active=True ) +def _get_farms_batch_types(request): + return BatchType.objects.filter(is_active=True, farm=_get_selected_farm(request)) # Farm Services def get_farm_home_context(request): authenticated_user = request.user + selected_farm = _get_selected_farm(request) + + farms_query = authenticated_user.farms.filter(is_active=True) + farms_with_counts = farms_query.annotate( + total_animals=Count( + 'animals', + filter=Q(animals__is_active=True), + distinct=True + ), + total_paddocks=Count( + 'paddocks', + filter=Q(paddocks__is_active=True), + distinct=True + ) + ) + allowed_users = selected_farm.users.exclude(pk=authenticated_user.pk) + batch_types = _get_farms_batch_types(request) context = {} - context['title'] = 'Fazenda' - - farms = Farm.objects.filter( - users=authenticated_user, - is_active=True - ) - - context['farms'] = farms + context['title'] = selected_farm.name + context['selected_farm'] = selected_farm + context['farms'] = farms_with_counts + context['allowed_users'] = allowed_users + context['batch_types'] = batch_types return context @@ -67,7 +85,7 @@ def get_farm_create_context(): context['title'] = 'Nova fazenda' context['form'] = form context['action_url'] = reverse('entities:farm:farm_create') - context['return_url'] = reverse('entities:farm:farm_list') + context['return_url'] = reverse('entities:farm:farm_home') return context @@ -92,7 +110,7 @@ def get_update_farm_context(request, farm_id): 'entities:farm:farm_update', kwargs={'farm_id': farm.pk} ) - context['return_url'] = reverse('entities:farm:farm_list') + context['return_url'] = reverse('entities:farm:farm_home') context['form'] = form return context @@ -116,7 +134,7 @@ def get_farm_delete_context(request, farm_id): 'entities:farm:farm_delete', kwargs={'farm_id': farm_id} ) - context['negative_url'] = reverse('entities:farm:farm_list') + context['negative_url'] = reverse('entities:farm:farm_home') return context @@ -125,6 +143,17 @@ def delete_farm(request, farm_id): farm.is_active = False farm.save() +def update_consumption(request): + default_consumption = Decimal(request.POST.get('default-consume')) + summer_consumption = Decimal(request.POST.get('summer-consume')) + winter_consumption = Decimal(request.POST.get('winter-consume')) + + selected_farm = _get_selected_farm(request) + selected_farm.default_consumption = default_consumption + selected_farm.summer_consumption = summer_consumption + selected_farm.winter_consumption = winter_consumption + + selected_farm.save() # Paddock Services def get_paddock_create_context(): @@ -270,4 +299,41 @@ def get_paddock_group_delete_context(request, paddock_group_id): def delete_paddock_group(request, paddock_group_id): paddock_group = _get_paddock_group_by_id(request, paddock_group_id) paddock_group.is_active = False - paddock_group.save() \ No newline at end of file + paddock_group.save() + +# User Authorize Services +def authorize_user(request, user_id): + farm = _get_selected_farm(request) + user = get_object_or_404(User, id=user_id, is_active=True) + user.farms.add(farm) + +def get_deauthorize_user_context(request, user_id): + user = get_object_or_404(User, id=user_id, is_active=True) + + context = {} + context['description'] = f"Remover acesso de {user.short_name}?" + context['positive_url'] = reverse( + 'entities:farm:farm_user_deauthorize', + kwargs={'user_id': user_id} + ) + context['negative_url'] = reverse('entities:farm:farm_home') + + return context + +def deauthorize_user(request, user_id): + farm = _get_selected_farm(request) + user = get_object_or_404(User, id=user_id, is_active=True) + user.farms.remove(farm) + +def get_user_card_context(request): + context = {} + informed_username = request.GET.get('username') + + found_user = User.objects.filter(login=informed_username).first() + + if found_user.farms.contains(_get_selected_farm(request)): + context['already_allowed'] = 'True' + else: + context['found_user'] = found_user + + return context \ No newline at end of file diff --git a/multigranja/apps/entities/static/farm/css/user_authorize.css b/multigranja/apps/entities/static/farm/css/user_authorize.css new file mode 100644 index 0000000..4f9507a --- /dev/null +++ b/multigranja/apps/entities/static/farm/css/user_authorize.css @@ -0,0 +1,120 @@ +.user-authorize-title { + margin-bottom: 20px; + font-size: 28px; + font-weight: 600; +} + +.user-search { + background: #fff; + border: 1px solid #e9ecef; + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.user-search label { + display: block; + margin-bottom: 8px; + font-weight: 500; +} + +.user-search input { + width: 100%; + padding: 10px 12px; + border: 1px solid #dee2e6; + border-radius: 8px; + margin-bottom: 12px; + transition: border-color 0.2s ease; +} + +.user-search input:focus { + outline: none; + border-color: #198754; +} + +.user-search button { + width: 100%; + border: none; + border-radius: 8px; + padding: 10px; + cursor: pointer; + font-weight: 500; + color: white; + background: #198754; + transition: 0.2s ease; +} + +.user-search button:hover { + background: #157347; +} + +.user-card-container { + margin-top: 20px; + min-height: 150px; + width: 350px; +} + +.user-card { + border: 1px solid #e9ecef; + border-radius: 10px; + padding: 16px; + background: #fafafa; + display: flex; + flex-direction: column; + gap: 10px; +} + +.user-card .name { + margin: 0; + font-size: 16px; + font-weight: 600; +} + +.user-card .name i { + margin-right: 6px; + color: #6c757d; +} + +.user-card .alert { + margin: 0; + font-size: 13px; + color: #6c757d; +} + +.user-card button { + align-self: flex-end; + text-decoration: none; + background: #198754; + color: white; + padding: 8px 14px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + transition: 0.2s ease; +} + +.user-card button:hover { + background: #157347; +} + +.failed-user-find { + text-align: center; + padding: 15px; + color: #dc3545; + font-size: 14px; +} + +.failed-user-find p { + margin: 0; +} + +.already-allowed-user { + text-align: center; + padding: 15px; + color: #198754; + font-size: 14px; +} + +.already-allowed-user p { + margin: 0; +} \ No newline at end of file diff --git a/multigranja/apps/entities/static/rotation/js/rotation_list.js b/multigranja/apps/entities/static/rotation/js/rotation_list.js index a4247b2..44c4400 100644 --- a/multigranja/apps/entities/static/rotation/js/rotation_list.js +++ b/multigranja/apps/entities/static/rotation/js/rotation_list.js @@ -1,13 +1,16 @@ document.addEventListener("DOMContentLoaded", () => { const container = document.querySelector(".open-rotations-container"); - + if (!container) return; - + let targetScroll = container.scrollLeft; let currentScroll = container.scrollLeft; let isAnimating = false; - + container.addEventListener("wheel", (event) => { + let isMobile = window.innerWidth <= 768; + if (isMobile) return; + event.preventDefault(); const maxScroll = diff --git a/multigranja/apps/entities/templates/batch/batchtype_create.html b/multigranja/apps/entities/templates/batch/batchtype_create.html new file mode 100644 index 0000000..af837e3 --- /dev/null +++ b/multigranja/apps/entities/templates/batch/batchtype_create.html @@ -0,0 +1,11 @@ +{% extends 'layouts/base_modal.html' %} +{% load static %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} + NOVO TIPO DE LOTE
+ {% include 'components/form.html' %} +{% endblock %} \ No newline at end of file diff --git a/multigranja/apps/entities/templates/batch/batchtype_update.html b/multigranja/apps/entities/templates/batch/batchtype_update.html new file mode 100644 index 0000000..2137f73 --- /dev/null +++ b/multigranja/apps/entities/templates/batch/batchtype_update.html @@ -0,0 +1,11 @@ +{% extends 'layouts/base_modal.html' %} +{% load static %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} + EDITAR TIPO DE LOTE
+ {% include 'components/form.html' %} +{% endblock %} \ No newline at end of file diff --git a/multigranja/apps/entities/templates/farm/farm_home.html b/multigranja/apps/entities/templates/farm/farm_home.html index cbde976..66e6c6e 100644 --- a/multigranja/apps/entities/templates/farm/farm_home.html +++ b/multigranja/apps/entities/templates/farm/farm_home.html @@ -1,46 +1,105 @@ {% extends 'layouts/base.html' %} {% load static %} +{% load l10n %} {% block extra_css %} - + {{ block.super }} + {% endblock %} {% block content %} -
-

Fazendas

- + +
+
+

Tipos de lote

+ Tipo +
+ {% for type in batch_types %} +
+
+

{{ type.name }}

+

Multiplicativo de consumo: {{ type.consumption_factor }} ({{ type.consumption_percentage }}%)

+
+
+ Editar + Excluir +
+
+ {% endfor %} +
+ +
+
+

Usuários com acesso

+ Permissão +
+ {% if allowed_users|length < 1 %} +

Seu usuário é o único vinculado a essa fazenda

+ {% endif %} + {% for user in allowed_users %} +
+

{{user.name}}

+ +
+ {% endfor %} +
+ +
+

Suas fazendas

+ {% for farm in farms %} +
+

{{ farm.name }}

+
+

{{ farm.animal_count }} Animais

+

{{ farm.paddock_count }} Piquetes

+
+ +
+ {% endfor %}
- {% if not farms %} -

Nenhuma fazenda encontrado.

- {% else %} - - - - - - - - {% for farm in farms %} - - - - - - - {% endfor %} -
NomeÁrea totalEditarExcluir
{{ farm.name }}{{ farm.total_area }} m2 -
- {% csrf_token %} - -
-
- {% endif %} {% endblock %} \ No newline at end of file diff --git a/multigranja/apps/entities/templates/farm/user-auth-card.html b/multigranja/apps/entities/templates/farm/user-auth-card.html new file mode 100644 index 0000000..b8b3ba5 --- /dev/null +++ b/multigranja/apps/entities/templates/farm/user-auth-card.html @@ -0,0 +1,22 @@ +
+ {% if found_user %} +
+

{{found_user.full_name}}

+

Ao liberar esse usuário ele terá as mesmas permissões que você

+
+ {% csrf_token %} + +
+
+ {% else %} + {% if already_allowed %} +
+

O usuário já possui acesso a essa fazenda

+
+ {% else %} +
+

Não foi possível localizar esse usuário

+
+ {% endif %} + {% endif %} +
\ No newline at end of file diff --git a/multigranja/apps/entities/templates/farm/user_authorize.html b/multigranja/apps/entities/templates/farm/user_authorize.html new file mode 100644 index 0000000..72d5302 --- /dev/null +++ b/multigranja/apps/entities/templates/farm/user_authorize.html @@ -0,0 +1,12 @@ +{% extends 'layouts/base_modal.html' %} +{% load static %} + +{% block content %} +

Autorizar usuário

+ +{% endblock content %} \ No newline at end of file diff --git a/multigranja/apps/entities/templates/layouts/entity_list.html b/multigranja/apps/entities/templates/layouts/entity_list.html index b9c4a03..21b5d94 100644 --- a/multigranja/apps/entities/templates/layouts/entity_list.html +++ b/multigranja/apps/entities/templates/layouts/entity_list.html @@ -27,8 +27,8 @@ + Novo - Novo
{% if not object_list %} diff --git a/multigranja/apps/entities/urls/animal.py b/multigranja/apps/entities/urls/animal.py index 1629998..3d3307a 100644 --- a/multigranja/apps/entities/urls/animal.py +++ b/multigranja/apps/entities/urls/animal.py @@ -15,4 +15,8 @@ urlpatterns = [ path('batch/create/', views.batch_create, name='batch_create'), path('batch/update/', views.batch_update, name='batch_update'), path('batch/delete/', views.batch_delete, name='batch_delete'), + + path('batch/type/create/', views.batchtype_create, name='batchtype_create'), + path('batch/type/update/', views.batchtype_update, name='batchtype_update'), + path('batch/type/delete/', views.batchtype_delete, name='batchtype_delete'), ] \ No newline at end of file diff --git a/multigranja/apps/entities/urls/farm.py b/multigranja/apps/entities/urls/farm.py index 8a90ee3..1bdc766 100644 --- a/multigranja/apps/entities/urls/farm.py +++ b/multigranja/apps/entities/urls/farm.py @@ -5,6 +5,11 @@ from ..views import PaddockListView, PaddockGroupListView app_name = 'farm' urlpatterns = [ + path('usercard/', views.get_user_card, name='get_user_card'), + path('authorize/', views.farm_user_authorize, name='new_authorization'), + path('authorize/', views.farm_user_authorize, name='farm_user_authorize'), + path('deauthorize/', views.farm_user_deauthorize, name='farm_user_deauthorize'), + path('', views.farm_home, name='farm_home'), path('paddock/list/', PaddockListView.as_view(), name='paddock_list'), path('paddock/group/list/', PaddockGroupListView.as_view(), name='paddock_group_list'), @@ -12,6 +17,7 @@ urlpatterns = [ path('create/', views.farm_create, name='farm_create'), path('update/', views.farm_update, name='farm_update'), path('delete/', views.farm_delete, name='farm_delete'), + path('consumption/update', views.consumption_update, name='consumption_update'), path('paddock/create/', views.paddock_create, name='paddock_create'), path('paddock/update/', views.paddock_update, name='paddock_update'), diff --git a/multigranja/apps/entities/views/animal.py b/multigranja/apps/entities/views/animal.py index e6624c8..2487302 100644 --- a/multigranja/apps/entities/views/animal.py +++ b/multigranja/apps/entities/views/animal.py @@ -79,4 +79,40 @@ def batch_delete(request, batch_id): if request.method == 'POST': services.delete_batch(request, batch_id) - return redirect('entities:animal:batch_list') \ No newline at end of file + return redirect('entities:animal:batch_list') + +# Batch Type views +@farm_required +def batchtype_create(request): + context = services.get_batchtype_create_context(request) + if request.method == 'GET': + return render(request, 'batch/batchtype_create.html', context) + + if request.method == 'POST': + if services.create_batchtype(request): + return redirect('entities:farm:farm_home') + context['error'] = "Não foi possível criar o tipo de lote" + return render(request, 'batch/batchtype_create.html', context) + +@farm_required +def batchtype_update(request, batchtype_id): + context = services.get_batchtype_update_context(request, batchtype_id) + if request.method == 'GET': + return render(request, 'batch/batchtype_update.html', context) + + if request.method == 'POST': + if services.update_batchtype(request, batchtype_id): + return redirect('entities:farm:farm_home') + context['error'] = "Não foi possível editar o tipo de lote" + return render(request, 'batch/batchtype_update.html', context) + +@farm_required +def batchtype_delete(request, batchtype_id): + if request.method == 'GET': + context = services.get_batchtype_delete_context(request, batchtype_id) + return render(request, 'components/confirmation_popup.html', context) + + if request.method == 'POST': + services.delete_batchtype(request, batchtype_id) + + return redirect('entities:farm:farm_home') \ No newline at end of file diff --git a/multigranja/apps/entities/views/farm.py b/multigranja/apps/entities/views/farm.py index 37abfb3..cc68054 100644 --- a/multigranja/apps/entities/views/farm.py +++ b/multigranja/apps/entities/views/farm.py @@ -17,7 +17,7 @@ def farm_create(request): if request.method == 'POST': if services.create_farm(request): - return redirect('entities:farm:farm_list') + return redirect('entities:farm:farm_home') context['error'] = "Não foi possível criar a fazenda" return render(request, 'farm/farm_create.html', context) @@ -44,6 +44,11 @@ def farm_delete(request, farm_id): return redirect('entities:farm:farm_create') +def consumption_update(request): + if request.method=='POST': + services.update_consumption(request) + + return redirect('entities:farm:farm_home') # Paddock Views @farm_required @@ -120,4 +125,29 @@ def paddock_group_delete(request, paddock_group_id): if request.method == 'POST': services.delete_paddock_group(request, paddock_group_id) - return redirect('entities:farm:paddock_group_list') \ No newline at end of file + return redirect('entities:farm:paddock_group_list') + +# User Authorization Views +@farm_required +def farm_user_authorize(request, user_id=None): + if request.method == 'GET': + return render(request, 'farm/user_authorize.html') + if request.method == 'POST': + if not user_id: + return redirect('entities:farm:farm_home') + services.authorize_user(request, user_id) + return redirect('entities:farm:farm_home') + +@farm_required +def farm_user_deauthorize(request, user_id): + if request.method == 'GET': + context = services.get_deauthorize_user_context(request, user_id) + return render(request, 'components/confirmation_popup.html', context) + if request.method == 'POST': + services.deauthorize_user(request, user_id) + return redirect('entities:farm:farm_home') + +@farm_required +def get_user_card(request): + context = services.get_user_card_context(request) + return render(request, 'farm/user-auth-card.html', context) \ No newline at end of file