commit
21978f7420
@ -0,0 +1 @@ |
||||
venv/ |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@ |
||||
from django.contrib import admin |
||||
|
||||
# Register your models here. |
||||
@ -0,0 +1,6 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class InventarioConfig(AppConfig): |
||||
default_auto_field = 'django.db.models.BigAutoField' |
||||
name = 'Inventario' |
||||
@ -0,0 +1,99 @@ |
||||
from django import forms |
||||
from .models import proveedor, bodega, tipo_articulo, articulo,inventario,tipo_inv_movimiento |
||||
|
||||
class formulario_proveedor(forms.ModelForm): |
||||
class Meta: |
||||
model = proveedor |
||||
fields = '__all__' |
||||
widgets={ |
||||
'nombre_proveedor': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'Escribe el nombre del proveedor'}), |
||||
'email': forms.EmailInput(attrs={'class':'form-control','placeholder':'email del proveedor'}), |
||||
'direccion': forms.Textarea(attrs={'class':'form-control','placeholder':'direccion del proveedor','rows':'5'}), |
||||
'telefono': forms.TextInput(attrs={'type':'tel','class':'form-control','placeholder':'0000-0000','maxlength':'9' ,'oninput':'formatearInput(this)'}), |
||||
} |
||||
|
||||
class formulario_bodega(forms.ModelForm): |
||||
class Meta: |
||||
model = bodega |
||||
fields = '__all__' |
||||
widgets={ |
||||
'nombre_bodega': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'Escribe el nombre de la bodega'}), |
||||
'estado': forms.CheckboxInput(attrs={'class':'form-check-input'}), |
||||
'direccion_bodega': forms.Textarea(attrs={'class':'form-control','placeholder':'direccion de la bodega','rows':'5'}), |
||||
'codigo_bodega': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'Escribe el codigo de la bodega','oninput':'permitirSoloNumeros(this)'}), |
||||
} |
||||
|
||||
class formulario_tipo(forms.ModelForm): |
||||
class Meta: |
||||
model = tipo_articulo |
||||
fields = '__all__' |
||||
widgets={ |
||||
'tipo_articulo': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'escriba el tipo de articulo'}), |
||||
'descripcion': forms.Textarea(attrs={'class':'form-control','placeholder':'escriba una descripcion de este tipo de articulo','rows':'5'}), |
||||
} |
||||
|
||||
class formulario_articulo(forms.ModelForm): |
||||
tipo_articulo=forms.ModelChoiceField(queryset=tipo_articulo.objects.all(), |
||||
empty_label="Selecciona un tipo de articulo", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="tipo_articulo") |
||||
proveedor=forms.ModelChoiceField(queryset=proveedor.objects.all(), |
||||
empty_label="Selecciona un proveedor", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle ', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="proveedor") |
||||
class Meta: |
||||
model = articulo |
||||
fields = '__all__' |
||||
widgets={ |
||||
'nombre_articulo': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'escriba el nombre del articulo'}), |
||||
'medida': forms.TextInput(attrs={'class':'form-control','placeholder':'medida del producto lts, cm, etc'}), |
||||
'descripcion': forms.Textarea(attrs={'class':'form-control','placeholder':'describa el producto','rows':'5'}), |
||||
} |
||||
|
||||
class formilario_inventarios(forms.ModelForm): |
||||
|
||||
articulo=forms.ModelChoiceField(queryset=articulo.objects.all(), |
||||
empty_label="selecciones un articulo", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="articulo") |
||||
bodega=forms.ModelChoiceField(queryset=bodega.objects.all(), |
||||
empty_label="Selecciona una bodega", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle ', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="bodega") |
||||
class Meta: |
||||
model = inventario |
||||
fields = '__all__' |
||||
widgets={ |
||||
'cantidad': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'Cantidad de productos','oninput':'permitirSoloNumeros(this)',}), |
||||
'precio': forms.NumberInput(attrs={'class':'form-control','placeholder':'Ej: 99.99','step': '0.01','min':'0','max':'10000',}), |
||||
} |
||||
|
||||
class formilario_inventario(forms.ModelForm): |
||||
tipo_inventario=forms.ModelChoiceField(queryset=tipo_inv_movimiento.objects.all(), |
||||
empty_label="selecciones un articulo", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="tipo de inventario") |
||||
bodega=forms.ModelChoiceField(queryset=bodega.objects.all(), |
||||
empty_label="Selecciona una bodega", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle ', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="bodega") |
||||
articulo=forms.ModelChoiceField(queryset=articulo.objects.all(), |
||||
empty_label="Selecciona una bodega", |
||||
widget=forms.Select(attrs={'class': 'btn btn-secondary dropdown-toggle ', 'data-bs-toggle': 'dropdown', 'aria-expanded': 'false'}), |
||||
label="articulo") |
||||
def __init__(self, *args, **kwargs): |
||||
super().__init__(*args, **kwargs) |
||||
# Personaliza cómo se muestran las opciones en el dropdown |
||||
self.fields['articulo'].label_from_instance = lambda obj: f"{obj.nombre_articulo} ({obj.medida})" if obj.medida else obj.nombre_articulo |
||||
self.fields['bodega'].label_from_instance = lambda obj: f"{obj.nombre_bodega} ({obj.codigo_bodega})" if obj.codigo_bodega else obj.nombre_bodega |
||||
|
||||
|
||||
class Meta: |
||||
model = inventario |
||||
fields = '__all__' |
||||
widgets={ |
||||
'fecha': forms.DateInput(attrs={'type': 'date','class': 'form-control','placeholder': 'Seleccione una fecha'}), |
||||
'precio': forms.NumberInput(attrs={'class':'form-control','placeholder':'Ej: 99.99','step': '0.01','min':'0','max':'10000',}), |
||||
'cantidad': forms.TextInput(attrs={'class': 'form-control' , 'placeholder': 'Cantidad de productos','oninput':'permitirSoloNumeros(this)',}), |
||||
'observacion': forms.Textarea(attrs={'class':'form-control','placeholder':'observacion','rows':'5'}), |
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
# Generated by Django 5.1.4 on 2024-12-11 06:38 |
||||
|
||||
import django.db.models.deletion |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
initial = True |
||||
|
||||
dependencies = [ |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='bodega', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('codigo_bodega', models.IntegerField()), |
||||
('nombre_bodega', models.CharField(max_length=40)), |
||||
('estado', models.BooleanField(default=True)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='proveedor', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('nombre_proveedor', models.CharField(max_length=100)), |
||||
('email', models.EmailField(blank=True, max_length=254, null=True)), |
||||
('telefono', models.CharField(blank=True, max_length=20, null=True)), |
||||
('direccion', models.TextField(blank=True, null=True)), |
||||
('estado', models.BooleanField(default=True)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='tipo_articulo', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('tipo_articulo', models.CharField(max_length=100)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='tipo_inv_movimiento', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('tipo_movimiento', models.CharField(max_length=100)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='articulo', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('nombre_articulo', models.CharField(max_length=100)), |
||||
('precio_articulo', models.DecimalField(decimal_places=2, max_digits=10)), |
||||
('stock', models.IntegerField()), |
||||
('creado', models.DateTimeField(auto_now_add=True)), |
||||
('actualizado', models.DateTimeField(auto_now=True)), |
||||
('activo', models.BooleanField(default=True)), |
||||
('bodega', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bodega', to='Inventario.bodega')), |
||||
('proveedor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='productos', to='Inventario.proveedor')), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='movimiento_inventario', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('cantidad', models.IntegerField()), |
||||
('descripcion', models.TextField(blank=True)), |
||||
('total_costo', models.DecimalField(decimal_places=2, max_digits=10)), |
||||
('numero_mov', models.IntegerField()), |
||||
('fecha', models.DateTimeField(auto_now_add=True)), |
||||
('articulo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articulos', to='Inventario.articulo')), |
||||
('tipo_movimiento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mov_inv', to='Inventario.tipo_inv_movimiento')), |
||||
], |
||||
), |
||||
] |
||||
@ -0,0 +1,138 @@ |
||||
# Generated by Django 5.1.4 on 2024-12-13 02:59 |
||||
|
||||
import django.db.models.deletion |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('Inventario', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RenameField( |
||||
model_name='movimiento_inventario', |
||||
old_name='descripcion', |
||||
new_name='observaciones', |
||||
), |
||||
migrations.RenameField( |
||||
model_name='movimiento_inventario', |
||||
old_name='total_costo', |
||||
new_name='precio', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='activo', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='actualizado', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='bodega', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='creado', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='precio_articulo', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='stock', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='movimiento_inventario', |
||||
name='numero_mov', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='movimiento_inventario', |
||||
name='tipo_movimiento', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='proveedor', |
||||
name='estado', |
||||
), |
||||
migrations.AddField( |
||||
model_name='articulo', |
||||
name='codigo_articulo', |
||||
field=models.IntegerField(null=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='articulo', |
||||
name='descripcion', |
||||
field=models.TextField(blank=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='articulo', |
||||
name='tipo_articulo', |
||||
field=models.ForeignKey(default=40, on_delete=django.db.models.deletion.CASCADE, to='Inventario.tipo_articulo'), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AddField( |
||||
model_name='bodega', |
||||
name='direccion_bodega', |
||||
field=models.TextField(blank=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='movimiento_inventario', |
||||
name='bodega', |
||||
field=models.ForeignKey(default=60, on_delete=django.db.models.deletion.CASCADE, to='Inventario.bodega'), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AddField( |
||||
model_name='movimiento_inventario', |
||||
name='tipo_inventario', |
||||
field=models.ForeignKey(default=100, on_delete=django.db.models.deletion.CASCADE, to='Inventario.tipo_inv_movimiento'), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AddField( |
||||
model_name='tipo_articulo', |
||||
name='descripcion', |
||||
field=models.TextField(blank=True), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='articulo', |
||||
name='proveedor', |
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Inventario.proveedor'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='bodega', |
||||
name='codigo_bodega', |
||||
field=models.IntegerField(null=True), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='movimiento_inventario', |
||||
name='articulo', |
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Inventario.articulo'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='movimiento_inventario', |
||||
name='cantidad', |
||||
field=models.IntegerField(null=True), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='movimiento_inventario', |
||||
name='fecha', |
||||
field=models.DateTimeField(null=True), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='tipo_articulo', |
||||
name='tipo_articulo', |
||||
field=models.CharField(max_length=40), |
||||
), |
||||
migrations.CreateModel( |
||||
name='inventario', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('cantidad', models.IntegerField(null=True)), |
||||
('precio', models.DecimalField(decimal_places=2, max_digits=10)), |
||||
('articulo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Inventario.articulo')), |
||||
('bodega', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='Inventario.bodega')), |
||||
], |
||||
), |
||||
] |
||||
@ -0,0 +1,22 @@ |
||||
# Generated by Django 5.1.4 on 2024-12-15 18:34 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('Inventario', '0002_rename_descripcion_movimiento_inventario_observaciones_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RemoveField( |
||||
model_name='articulo', |
||||
name='codigo_articulo', |
||||
), |
||||
migrations.AddField( |
||||
model_name='articulo', |
||||
name='medida', |
||||
field=models.CharField(blank=True, max_length=100, null=True), |
||||
), |
||||
] |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,58 @@ |
||||
from django.db import models |
||||
from django.contrib.auth.models import User |
||||
|
||||
|
||||
# Create your models here. |
||||
class tipo_articulo(models.Model): |
||||
tipo_articulo= models.CharField(max_length=40) |
||||
descripcion=models.TextField(blank=True) |
||||
def __str__(self): |
||||
return self.tipo_articulo |
||||
|
||||
class proveedor(models.Model): |
||||
nombre_proveedor=models.CharField(max_length=100) |
||||
email = models.EmailField(null=True, blank=True) |
||||
telefono = models.CharField(max_length=20, null=True, blank=True) |
||||
direccion = models.TextField(null=True, blank=True) |
||||
def __str__(self): |
||||
return self.nombre_proveedor |
||||
|
||||
class tipo_inv_movimiento(models.Model): |
||||
tipo_movimiento=models.CharField(max_length=100) |
||||
def __str__(self): |
||||
return self.tipo_movimiento |
||||
|
||||
class bodega(models.Model): |
||||
codigo_bodega=models.IntegerField(null=True) |
||||
nombre_bodega= models.CharField(max_length=40) |
||||
direccion_bodega=models.TextField(blank=True) |
||||
estado= models.BooleanField(default=True) |
||||
def __str__(self): |
||||
return self.nombre_bodega |
||||
|
||||
class articulo(models.Model): |
||||
nombre_articulo=models.CharField(max_length=100) |
||||
medida = models.CharField(max_length=100, blank=True, null=True) |
||||
descripcion = models.TextField(blank=True) |
||||
proveedor = models.ForeignKey(proveedor, on_delete=models.CASCADE) |
||||
tipo_articulo=models.ForeignKey(tipo_articulo, on_delete=models.CASCADE) |
||||
def __str__(self): |
||||
return self.nombre_articulo |
||||
|
||||
|
||||
class inventario(models.Model): |
||||
bodega=models.ForeignKey(bodega, on_delete=models.CASCADE) |
||||
articulo=models.ForeignKey(articulo, on_delete=models.CASCADE) |
||||
cantidad=models.IntegerField(null=True) |
||||
precio=models.DecimalField( max_digits=10, decimal_places=2) |
||||
|
||||
|
||||
class movimiento_inventario(models.Model): |
||||
fecha=models.DateTimeField(null=True) |
||||
tipo_inventario=models.ForeignKey(tipo_inv_movimiento,on_delete=models.CASCADE) |
||||
bodega=models.ForeignKey(bodega, on_delete=models.CASCADE) |
||||
articulo=models.ForeignKey(articulo, on_delete=models.CASCADE) |
||||
cantidad=models.IntegerField(null=True) |
||||
precio=models.DecimalField(max_digits=10, decimal_places=2) |
||||
observaciones=models.TextField(blank=True) |
||||
|
||||
@ -0,0 +1,105 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>Tienda la esquina</title> |
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"> |
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"> |
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
||||
</head> |
||||
<body> |
||||
<nav class="navbar bg-dark navbar-dark"> |
||||
<div class="container-fluid "> |
||||
<a class="navbar-brand " href="/">Tienda la esquina</a> |
||||
<button class="navbar-toggler " type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar" aria-label="Toggle navigation"> |
||||
<span class="navbar-toggler-icon"></span> |
||||
</button> |
||||
<div class="offcanvas offcanvas-end bg-dark navbar-dark" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel"> |
||||
<div class="offcanvas-header"> |
||||
<h5 class="offcanvas-title"style="color:white;" id="offcanvasNavbarLabel">Bienvenido {{user.username}}</h5> |
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button> |
||||
</div> |
||||
<div class="offcanvas-body"> |
||||
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3"> |
||||
<a style="color:white;"></a> |
||||
<li class="nav-item"> |
||||
<a class="nav-link active" aria-current="page" href="/">Home</a> |
||||
</li> |
||||
{% if user.is_authenticated %} |
||||
<li class="nav-item dropdown "> |
||||
<a class="nav-link dropdown-toggle " role="button" data-bs-toggle="dropdown" aria-expanded="false"> |
||||
Gestores |
||||
</a> |
||||
<ul class="dropdown-menu dropdown-menu-dark"> |
||||
<li> |
||||
<p class="nav-link disabled text-center" aria-disabled="true">Gestor de proveedores</p> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li><a class="dropdown-item" href="{% url 'proveedor' %}">Lista de proveedores</a></li> |
||||
<li><a class="dropdown-item" href="{% url 'proveedor_registro' %}">Registro de proveedores</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
<a class="nav-link disabled text-center" aria-disabled="true">Gestor de bodegas</a> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li><a class="dropdown-item" href="{% url 'bodega_vista' %}">Lista de Bodegas</a></li> |
||||
<li><a class="dropdown-item" href="{% url 'bodega_registro' %}">Registro de bodegas</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
<a class="nav-link disabled text-center" aria-disabled="true">Gestor de tipos de articulo</a> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li><a class="dropdown-item" href="{% url 'tipo_vista' %}">Lista de tipos de articulos</a></li> |
||||
<li><a class="dropdown-item" href="{% url 'tipo_registro' %}">Registro de tipos de articulos</li> |
||||
</ul> |
||||
</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="{% url 'articulo_vista' %}">Articulos</a> |
||||
</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="{% url 'inventario_vista' %}">inventario</a> |
||||
</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="{% url 'vista_inventario_mov' %}">movimiento de inventario</a> |
||||
</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="{% url 'logout' %}">logout</a> |
||||
</li> |
||||
<li> |
||||
<hr class="dropdown-divider"> |
||||
</li> |
||||
{% else %} |
||||
<li class="nav-item"> |
||||
<a class="nav-link" href="{% url 'signin' %}">login</a> |
||||
</li> |
||||
{% endif %} |
||||
|
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</nav> |
||||
|
||||
{% block content %} |
||||
|
||||
{% endblock %} |
||||
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> |
||||
|
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,51 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form action="/articulo/crear/" method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'articulo_vista' %}"><- volver</a> |
||||
<h3 class="col-5">Registro articulo</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form.as_p}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">Crear nuevo articulo</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
<script> |
||||
function permitirSoloNumeros(input) { |
||||
input.value = input.value.replace(/[^0-9]/g, ''); |
||||
} |
||||
</script> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,44 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-10 offset-md-1 mt-4"> |
||||
<form action="/articulo/" method="POST"> |
||||
<div class="card-header row col-12 justify-content-end mb-2 pr-0 mt-2"> |
||||
<h3 class="col-md-4">Vista articulo</h3> |
||||
<a class="btn btn-success col-md-3" href="{% url 'articulo_registro' %}">Nuevo articulo</a> |
||||
</div> |
||||
<table class="table mt-3"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">#</th> |
||||
<th scope="col">nombre del articulo</th> |
||||
<th scope="col">medida</th> |
||||
<th scope="col">descripcion</th> |
||||
<th scope="col">tipo_articulo</th> |
||||
<th scope="col">proveedors</th> |
||||
<th scope="col">actualizar</th> |
||||
|
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for articulo in persona %} |
||||
<tr> |
||||
<th scope="row">{{ articulo.id }}</th> |
||||
<td> {{ articulo.nombre_articulo }} </td> |
||||
<td> {{ articulo.medida }} </td> |
||||
<td> {{ articulo.descripcion }} </td> |
||||
<td> {{ articulo.tipo_articulo.tipo_articulo }} </td> |
||||
<td> {{ articulo.proveedor.nombre_proveedor }} </td> |
||||
<td> <a class="btn btn-warning" href="{% url 'articulo_update' articulo.id %}">Editar</a> </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,29 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'articulo_vista' %}"><- volver</a> |
||||
<h3 class="col-5">Actualizar articulo</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% csrf_token %} |
||||
{{form.as_p}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">actualizar datos de articulo</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,51 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form action="/bodega/crearbodega/" method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'bodega_vista' %}"> <- volver</a> |
||||
<h3 class="col-5">Registro bodega</h3> |
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">Crear nueva bodega</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
<script> |
||||
function permitirSoloNumeros(input) { |
||||
input.value = input.value.replace(/[^0-9]/g, ''); |
||||
} |
||||
</script> |
||||
|
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,60 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-10 offset-md-1 mt-4"> |
||||
<form action="/bodega/" method="POST"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
<div class="card-header row col-12 justify-content-end mb-2 pr-0 mt-2"> |
||||
<h3 class="col-md-4">Vista Bodega</h3> |
||||
<a class="btn btn-success col-md-3" href="{% url 'bodega_registro' %}">Nueva bodega</a> |
||||
</div> |
||||
<table class="table mt-3"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">#</th> |
||||
<th scope="col">codigo de la bodega</th> |
||||
<th scope="col">nombre del supervisor</th> |
||||
<th scope="col">estado</th> |
||||
<th scope="col">Direccion</th> |
||||
<th scope="col">Editar</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for bodega in persona %} |
||||
<tr> |
||||
<th scope="row"> {{bodega.id}} </th> |
||||
<td> {{bodega.codigo_bodega}} </td> |
||||
<td> {{bodega.nombre_bodega}} </td> |
||||
{% if bodega.estado %} |
||||
<td> activo </td> |
||||
{% else %} |
||||
<td> inactivo </td> |
||||
{% endif %} |
||||
<td> {{bodega.direccion_bodega}} </td> |
||||
<td> <a class="btn btn-warning" href="{% url 'bodega_update' bodega.id %}">Editar</a> </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,43 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'bodega_vista' %}"><- volver</a> |
||||
<h3 class="col-5">Actualizar bodega</h3> |
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
bodega a sido actualizado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">actualizar datos de proveedor</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
{% endblock %} |
||||
@ -0,0 +1,36 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<title>Gráfico Interactivo</title> |
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
||||
</head> |
||||
<body> |
||||
<h1>Gráfico de cantidad de productos</h1> |
||||
<canvas id="grafico" width="400" height="200"></canvas> |
||||
<script> |
||||
const ctx = document.getElementById('grafico').getContext('2d'); |
||||
const myChart = new Chart(ctx, { |
||||
type: 'bar', |
||||
data: { |
||||
labels: {{ productos|safe }}, // Nombres de los productos |
||||
datasets: [{ |
||||
label: 'Cantidad de Productos', |
||||
data: {{ cantidades|safe }}, // Cantidades de productos |
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)', |
||||
borderColor: 'rgba(75, 192, 192, 1)', |
||||
borderWidth: 1 |
||||
}] |
||||
}, |
||||
options: { |
||||
scales: { |
||||
y: { |
||||
beginAtZero: true |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
</script> |
||||
<a href="{% url 'inventario_vista' %}" class="btn btn-secondary">volver</a> |
||||
|
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,181 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
{% if user.is_authenticated %} |
||||
<main class="conteiner col-md-10 offset-md-1 mt-5 g-4"> |
||||
<div class="row row-cols-1 row-cols-md-2 g-4"> |
||||
<div class="col"> |
||||
<div class="card h-100"> |
||||
<div class="card-body"> |
||||
<p class="text-center">Precio promedio de cada tipo articulo</p> |
||||
<div class="chart-container"> |
||||
<canvas id="avgPriceChart"></canvas> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="col"> |
||||
<div class="card h-100"> |
||||
<div class="card-body"> |
||||
<p class="text-center">articulos en bodegas</p> |
||||
<div class="chart-container"> |
||||
<canvas id="barChart"></canvas> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="col"> |
||||
<div class="card h-100"> |
||||
<div class="card-body"> |
||||
<p class="text-center">Cantidad de tipos de articulos</p> |
||||
<div class="chart-container"> |
||||
<canvas id="graficoLinealMovimientos"></canvas> |
||||
</div> |
||||
<br> |
||||
<div class="text-center"> |
||||
<p class="text-center">Reportes del inventario</p> |
||||
<a href="{% url 'generar_reporte_pdf' %}" class="btn btn-primary">Descargar PDF</a> |
||||
<a href="{% url 'generar_reporte_excel' %}" class="btn btn-success">Descargar Excel</a> |
||||
<a href="{% url 'generar_reporte_csv' %}" class="btn btn-info">Descargar CSV</a> |
||||
</div> |
||||
<br> |
||||
<p class="text-center">Grafico de productos</p> |
||||
<div class="text-center"> |
||||
<a href="{% url 'generar_grafico_chartjs' %}" class="btn btn-secondary">generar grafico de produccion</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="col"> |
||||
<div class="card h-100"> |
||||
<div class="card-body"> |
||||
<p class="text-center">Cantidad de tipos de articulos</p> |
||||
<div class="chart-container"> |
||||
<canvas id="pieChart"></canvas> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
</main> |
||||
<script> |
||||
// Datos dinámicos generados desde Django |
||||
const tiposArticulo = {{ tipos_articulo | safe }}; |
||||
const cantidadesPorTipo = {{ cantidades_por_tipo | safe }}; |
||||
const bodegas = {{ bodegas | safe }}; |
||||
const cantidadesPorBodega = {{ cantidades_por_bodega | safe }}; |
||||
const tiposArticuloProm = {{ tipos_articulo_prom | safe }}; |
||||
const preciosPromedio = {{ precios_promedio | safe }}; |
||||
|
||||
|
||||
// 1. Gráfico de Pastel: Distribución de artículos por tipo |
||||
new Chart(document.getElementById('pieChart').getContext('2d'), { |
||||
type: 'pie', |
||||
data: { |
||||
labels: tiposArticulo, |
||||
datasets: [{ |
||||
data: cantidadesPorTipo, |
||||
backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 206, 86, 0.6)', 'rgba(75, 192, 192, 0.6)'], |
||||
}] |
||||
}, |
||||
options: { |
||||
responsive: true, |
||||
plugins: { |
||||
legend: { position: 'top' }, |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// 2. Gráfico de Barras: Cantidad de artículos por bodega |
||||
new Chart(document.getElementById('barChart').getContext('2d'), { |
||||
type: 'bar', |
||||
data: { |
||||
labels: bodegas, |
||||
datasets: [{ |
||||
label: 'Cantidad de Artículos', |
||||
data: cantidadesPorBodega, |
||||
backgroundColor: 'rgba(75, 192, 192, 0.6)', |
||||
}] |
||||
}, |
||||
options: { |
||||
responsive: true, |
||||
plugins: { |
||||
legend: { display: false }, |
||||
}, |
||||
scales: { |
||||
x: { title: { display: true, text: 'Bodegas' } }, |
||||
y: { title: { display: true, text: 'Cantidad' } }, |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// 3. Gráfico de Línea: Evolución de movimientos de inventario |
||||
|
||||
new Chart(document.getElementById('avgPriceChart').getContext('2d'), { |
||||
type: 'bar', |
||||
data: { |
||||
labels: tiposArticuloProm, |
||||
datasets: [{ |
||||
label: 'Precio Promedio', |
||||
data: preciosPromedio, |
||||
backgroundColor: 'rgba(255, 159, 64, 0.6)', |
||||
}] |
||||
}, |
||||
options: { |
||||
responsive: true, |
||||
plugins: { |
||||
legend: { position: 'top' }, |
||||
}, |
||||
scales: { |
||||
x: { title: { display: true, text: 'Tipos de Artículos' } }, |
||||
y: { title: { display: true, text: 'Precio Promedio' } }, |
||||
} |
||||
} |
||||
}); |
||||
const ctx = document.getElementById('graficoLinealMovimientos').getContext('2d'); |
||||
const graficoLineal = new Chart(ctx, { |
||||
type: 'line', |
||||
data: { |
||||
labels: {{ nombres_tipos|safe }}, // Nombres de los tipos de movimientos |
||||
datasets: [{ |
||||
label: 'Cantidad Total Movida', |
||||
data: {{ cantidades_por_tipos|safe }}, // Cantidades totales por tipo de movimiento |
||||
borderColor: 'rgba(54, 162, 235, 1)', |
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)', |
||||
borderWidth: 2, |
||||
tension: 0.3, // Para suavizar la línea |
||||
}] |
||||
}, |
||||
options: { |
||||
responsive: true, |
||||
plugins: { |
||||
legend: { |
||||
display: true, |
||||
}, |
||||
tooltip: { |
||||
enabled: true, |
||||
} |
||||
}, |
||||
scales: { |
||||
x: { |
||||
title: { |
||||
display: true, |
||||
text: 'Tipos de Movimiento' |
||||
} |
||||
}, |
||||
y: { |
||||
beginAtZero: true, |
||||
title: { |
||||
display: true, |
||||
text: 'Cantidad Total' |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
</script> |
||||
{% else %} |
||||
{% endif %} |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,52 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form action="/inventariomov/crear/" method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'vista_inventario_mov' %}"><- volver</a> |
||||
<h3 class="col-5">Registro inventario</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
inventario a sido creado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form.as_p}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">Crear nuevo inventario</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
<script> |
||||
function permitirSoloNumeros(input) { |
||||
input.value = input.value.replace(/[^0-9]/g, ''); |
||||
} |
||||
</script> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,45 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-10 offset-md-1 mt-4"> |
||||
<form action="/inventario/" method="POST"> |
||||
<div class="card-header row col-12 justify-content-end mb-2 pr-0 mt-2"> |
||||
<h3 class="col-md-4">Vista inventario</h3> |
||||
</div> |
||||
<a href="{% url 'generar_reporte_pdf' %}" class="btn btn-primary">Descargar PDF</a> |
||||
<a href="{% url 'generar_reporte_excel' %}" class="btn btn-success">Descargar Excel</a> |
||||
<a href="{% url 'generar_reporte_csv' %}" class="btn btn-info">Descargar CSV</a> |
||||
<a href="{% url 'generar_grafico_chartjs' %}" class="btn btn-secondary">grafico</a> |
||||
<br> |
||||
<table class="table mt-3"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">#</th> |
||||
<th scope="col">articulo</th> |
||||
<th scope="col">Descripcion de articulo</th> |
||||
<th scope="col">bodega</th> |
||||
<th scope="col">cantidad</th> |
||||
<th scope="col">precio</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for inventario in persona %} |
||||
<tr> |
||||
<th scope="row">{{ inventario.id }}</th> |
||||
<td> {{ inventario.articulo.nombre_articulo }} {{inventario.articulo.medida}} </td> |
||||
<td> {{ inventario.articulo.descripcion }} </td> |
||||
<td> {{ inventario.bodega.nombre_bodega }} </td> |
||||
<td> {{ inventario.cantidad }} </td> |
||||
<td> {{ inventario.precio }} </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,41 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-10 offset-md-1 mt-4"> |
||||
<form action="/inventariomov/" method="POST"> |
||||
<div class="card-header row col-12 justify-content-end mb-2 pr-0 mt-2"> |
||||
<h3 class="col-md-4">Vista inventario</h3> |
||||
<a class="btn btn-success col-md-3" href="{% url 'registrar_movimiento' %}"> Nuevo movimiento de inventario</a> |
||||
</div> |
||||
<table class="table mt-3"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">#</th> |
||||
<th scope="col">bodega</th> |
||||
<th scope="col">articulo</th> |
||||
<th scope="col">cantidad</th> |
||||
<th scope="col">precio</th> |
||||
<th scope="col">tipo de movimiento</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for movimiento_inventario in persona %} |
||||
<tr> |
||||
<th scope="row">{{ movimiento_inventario.id }}</th> |
||||
<td> {{ movimiento_inventario.articulo.nombre_articulo }} {{movimiento_inventario.articulo.medida}} </td> |
||||
<td> {{ movimiento_inventario.bodega.nombre_bodega }} </td> |
||||
<td> {{ movimiento_inventario.cantidad }} </td> |
||||
<td> {{ movimiento_inventario.precio }} </td> |
||||
<td> {{ movimiento_inventario.tipo_inventario }} </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,36 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<style> |
||||
body { font-family: Arial, sans-serif; } |
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; } |
||||
th, td { border: 1px solid #000; padding: 8px; text-align: left; } |
||||
th { background-color: #f2f2f2; } |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<h1>Reporte de Inventario</h1> |
||||
<table> |
||||
<thead> |
||||
<tr> |
||||
<th>ID</th> |
||||
<th>Bodega</th> |
||||
<th>Artículo</th> |
||||
<th>Cantidad</th> |
||||
<th>Precio</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for item in inventarios %} |
||||
<tr> |
||||
<td>{{ item.id }}</td> |
||||
<td>{{ item.bodega.nombre_bodega }}</td> |
||||
<td>{{ item.articulo.nombre_articulo }}</td> |
||||
<td>{{ item.cantidad }}</td> |
||||
<td>{{ item.precio }}</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,56 @@ |
||||
|
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form action="/proveedor/crear/" method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'proveedor' %}"><- volver</a> |
||||
<h3 class="col-5">Registro Proveedor</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">Crear nuevo proveedor</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
<script> |
||||
function formatearInput(input) { |
||||
// Remover caracteres no numéricos |
||||
let valor = input.value.replace(/[^0-9]/g, ''); |
||||
// Insertar el guion después de los primeros 4 dígitos |
||||
if (valor.length > 4) { |
||||
valor = valor.slice(0, 4) + '-' + valor.slice(4); |
||||
} |
||||
input.value = valor; |
||||
} |
||||
</script> |
||||
{% endblock %} |
||||
@ -0,0 +1,56 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-10 offset-md-1 mt-4"> |
||||
<form action="/proveedor/" method="POST"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
<div class="card-header row col-12 justify-content-end mb-2 pr-0 mt-2"> |
||||
<h3 class="col-md-4">Proveedores</h3> |
||||
<a class="btn btn-success col-md-3" href="{% url 'proveedor_registro' %}">Nuevo proveedor</a> |
||||
</div> |
||||
<table class="table mt-3"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">#</th> |
||||
<th scope="col">Nombre</th> |
||||
<th scope="col">Email</th> |
||||
<th scope="col">Telefono</th> |
||||
<th scope="col">Direccion</th> |
||||
<th scope="col">Editar</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for proveedor in persona %} |
||||
<tr> |
||||
<th scope="row"> {{proveedor.id}} </th> |
||||
<td> {{proveedor.nombre_proveedor}} </td> |
||||
<td> {{proveedor.email}} </td> |
||||
<td> {{proveedor.telefono}} </td> |
||||
<td> {{proveedor.direccion}} </td> |
||||
<td> <a class="btn btn-warning" href="{% url 'Proveedor_update' proveedor.id %}">Editar</a> </td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,43 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'proveedor' %}"><- volver</a> |
||||
<h3 class="col-5">Actualizar Proveedor</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido actualizado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">actualizar datos de proveedor</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
{% endblock %} |
||||
@ -0,0 +1,28 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-4 offset-md-4 mt-5"> |
||||
<form action="/signin/" method="POST" class="card card-body"> |
||||
<h1 class="text-center">Login</h1> |
||||
{% csrf_token %} |
||||
<div class="mb-3"> |
||||
<label for="username"> Usuario:</label> |
||||
<input type="text" name="username" id="username" |
||||
class="form-control" placeholder="Escribe tu usuario"> |
||||
</div> |
||||
<div class="mb-3"> |
||||
<label for="password"> Contraseña:</label> |
||||
<input type="password" name="password" id="password" |
||||
class="form-control" placeholder="escribe tu contraseña"> |
||||
</div> |
||||
<button class="btn btn-primary"> Signin </button> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,44 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form action="/tipo/creartipo_articulo/" method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'tipo_vista' %}"><- volver</a> |
||||
<h3 class="col-5">Registro tipo articulo</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
el tipo de articulo a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">Crear nuevo tipo de articulo</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
{% endblock %} |
||||
@ -0,0 +1,51 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-10 offset-md-1 mt-4"> |
||||
<form action="/tipo/" method="POST"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
Proveedor a sido guardado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
<div class="card-header row col-12 justify-content-end mb-2 pr-0 mt-2"> |
||||
<h3 class="col-md-4">Tipos de articulos</h3> |
||||
<a class="btn btn-success col-md-3" href="{% url 'tipo_registro' %}">Nuevo tipo de articulo</a> |
||||
</div> |
||||
<table class="table mt-3"> |
||||
<thead> |
||||
<tr> |
||||
<th scope="col">#</th> |
||||
<th scope="col">tipo de articulo</th> |
||||
<th scope="col">Descripcion</th> |
||||
<th scope="col">Actualizar</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for tipo_articulo in persona %} |
||||
<tr> |
||||
<th scope="row"> {{tipo_articulo.id}} </th> |
||||
<td> {{tipo_articulo.tipo_articulo}} </td> |
||||
<td> {{tipo_articulo.descripcion}} </td> |
||||
<td> <a class="btn btn-warning" href="{% url 'tipo_update' tipo_articulo.id %}">Editar</a> </td> </tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,43 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
<main class="conteiner"> |
||||
<div class="row"> |
||||
<div class="col-md-8 offset-md-2 mt-4"> |
||||
<form method="POST"> |
||||
<div class="card"> |
||||
<div class="card-header row col-12 justify-content-start mb-2 pr-0 mt-2"> |
||||
<a class="btn btn-danger col-3" href="{% url 'tipo_vista' %}"><- volver</a> |
||||
<h3 class="col-5">Actualizar tipo de articulo</h3> |
||||
|
||||
</div> |
||||
<div class="card-body"> |
||||
{% if error %} |
||||
<div class="alert alert-danger" role="alert"> |
||||
{{error}} |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> </div> |
||||
{% endif %} |
||||
{% if mensaje %} |
||||
<div class="alert alert-success" role="alert"> |
||||
tipo de articulo a sido actualizado con exito |
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> |
||||
<span aria-hidden="true">×</span> |
||||
</button> |
||||
</div> |
||||
{% endif %} |
||||
{% csrf_token %} |
||||
{{form}} |
||||
|
||||
<div class="d-grid gap-2 col-6 mx-auto mt-3"> |
||||
<button class="btn btn-primary btn-lg ">actualizar datos de tipo articulo</button> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</main> |
||||
{% endblock %} |
||||
@ -0,0 +1,3 @@ |
||||
from django.test import TestCase |
||||
|
||||
# Create your tests here. |
||||
@ -0,0 +1,338 @@ |
||||
from django.shortcuts import render,redirect,get_object_or_404 |
||||
from django.template.loader import get_template |
||||
from django.http import HttpResponse |
||||
from django.contrib.auth.forms import AuthenticationForm |
||||
from django.contrib.auth import login, logout, authenticate |
||||
from django.db import transaction |
||||
from django.utils import timezone |
||||
from django.contrib.auth.decorators import login_required |
||||
from django.db.models import Avg, Sum |
||||
from decimal import Decimal |
||||
from .forms import formulario_proveedor,formulario_bodega, formulario_tipo, formulario_articulo,formilario_inventario |
||||
from .models import proveedor, bodega, tipo_articulo, articulo,inventario,tipo_inv_movimiento,movimiento_inventario |
||||
|
||||
# Create your views here. |
||||
def base(request): |
||||
return render(request, 'index.html') |
||||
|
||||
def generar_grafico_chartjs(request): |
||||
# Gráfico de Pastel: Cantidad por tipo de artículo |
||||
tipos_articulo = tipo_articulo.objects.all() |
||||
tipos_nombres = [t.tipo_articulo for t in tipos_articulo] |
||||
cantidades_por_tipo = [articulo.objects.filter(tipo_articulo=t).count() for t in tipos_articulo] |
||||
|
||||
# Gráfico de Barras: Cantidad de artículos por bodega |
||||
bodegas = bodega.objects.all() |
||||
bodegas_nombres = [b.nombre_bodega for b in bodegas] |
||||
cantidades_por_bodega = [inventario.objects.filter(bodega=b).count() for b in bodegas] |
||||
|
||||
# Gráfico de Línea: Movimientos en el tiempo |
||||
|
||||
tipos_movimientos = tipo_inv_movimiento.objects.all() |
||||
nombres_tipos = [mov.tipo_movimiento for mov in tipos_movimientos] |
||||
|
||||
# Obtener la cantidad total movida para cada tipo de movimiento |
||||
cantidades_por_tipos = [ |
||||
movimiento_inventario.objects.filter(tipo_inventario=mov).aggregate(total=Sum('cantidad'))['total'] or 0 |
||||
for mov in tipos_movimientos |
||||
] |
||||
tipos_articulo_prom = [t.tipo_articulo for t in tipos_articulo] |
||||
|
||||
precios_promedio = [ |
||||
float(inventario.objects.filter(articulo__tipo_articulo=t).aggregate(avg=Avg('precio'))['avg'] or 0) |
||||
for t in tipos_articulo |
||||
] |
||||
|
||||
context = { |
||||
'tipos_articulo': tipos_nombres, |
||||
'cantidades_por_tipo': cantidades_por_tipo, |
||||
'bodegas': bodegas_nombres, |
||||
'cantidades_por_bodega': cantidades_por_bodega, |
||||
'tipos_articulo_prom': tipos_articulo_prom, |
||||
'precios_promedio': precios_promedio, |
||||
'nombres_tipos': nombres_tipos, |
||||
'cantidades_por_tipos': cantidades_por_tipos, |
||||
} |
||||
return render(request, 'index.html', context) |
||||
|
||||
def signin(request): |
||||
if request.method == 'GET': |
||||
return render ( request, 'signin.html',{ |
||||
'form': AuthenticationForm |
||||
}) |
||||
else: |
||||
user=authenticate(request, username=request.POST['username'], password=request.POST['password']) |
||||
if user is None: |
||||
return render ( request, 'signin.html',{ |
||||
'form': AuthenticationForm , |
||||
'error': 'Username or password is incorrect' |
||||
}) |
||||
else: |
||||
login(request, user) |
||||
return redirect('home') |
||||
|
||||
@login_required |
||||
def signout(request): |
||||
logout(request) |
||||
return redirect('home') |
||||
|
||||
@login_required |
||||
def proveedor_registro(request): |
||||
if request.method=='GET': |
||||
return render(request, 'proveedor_nuevo.html',{ |
||||
'form': formulario_proveedor |
||||
}) |
||||
else: |
||||
try: |
||||
form = formulario_proveedor (request.POST) |
||||
form.save() |
||||
form = formulario_proveedor () |
||||
return render(request, 'proveedor_nuevo.html',{ |
||||
'form': formulario_proveedor, |
||||
'mensaje':'Please provide valid data' |
||||
}) |
||||
except ValueError: |
||||
return render(request, 'proveedor_nuevo.html',{ |
||||
'form': formulario_proveedor, |
||||
'error':'Please provide valid data' |
||||
}) |
||||
|
||||
@login_required |
||||
def proveedor_lista(request): |
||||
persona = proveedor.objects.all |
||||
return render(request,"proveedor_registro.html", {'persona':persona}) |
||||
|
||||
@login_required |
||||
def Proveedor_update(request,task_id): |
||||
if request.method == 'GET': |
||||
tarea = get_object_or_404 (proveedor, pk=task_id) |
||||
form = formulario_proveedor(instance=tarea) |
||||
return render(request, 'proveedor_update.html',{'tasks':tarea, 'form':form} ) |
||||
else: |
||||
try: |
||||
tarea=get_object_or_404(proveedor,pk=task_id) |
||||
form = formulario_proveedor(request.POST, instance=tarea) |
||||
form.save() |
||||
return redirect('proveedor') |
||||
except ValueError: |
||||
return render(request, 'proveedor_update.html',{'tasks':tarea, 'form':form, 'error': 'error updating task'} ) |
||||
|
||||
@login_required |
||||
def bodega_registro(request): |
||||
if request.method=='GET': |
||||
return render(request, 'bodega_nuevo.html',{ |
||||
'form': formulario_bodega |
||||
}) |
||||
else: |
||||
try: |
||||
form = formulario_bodega (request.POST) |
||||
form.save() |
||||
form = formulario_bodega () |
||||
return render(request, 'bodega_nuevo.html',{ |
||||
'form': formulario_bodega, |
||||
'mensaje':'Please provide valid data' |
||||
}) |
||||
except ValueError: |
||||
return render(request, 'bodega_nuevo.html',{ |
||||
'form': formulario_bodega, |
||||
'error':'Please provide valid data' |
||||
}) |
||||
|
||||
@login_required |
||||
def bodega_vista(request): |
||||
persona = bodega.objects.all |
||||
return render(request,"bodega_registro.html", {'persona':persona}) |
||||
|
||||
@login_required |
||||
def bodega_update(request,task_id): |
||||
if request.method == 'GET': |
||||
tarea = get_object_or_404 (bodega, pk=task_id) |
||||
form = formulario_bodega(instance=tarea) |
||||
return render(request, 'bodega_update.html',{'tasks':tarea, 'form':form} ) |
||||
else: |
||||
try: |
||||
tarea=get_object_or_404(bodega,pk=task_id) |
||||
form = formulario_bodega(request.POST, instance=tarea) |
||||
form.save() |
||||
return redirect('bodega_vista') |
||||
except ValueError: |
||||
return render(request, 'bodega_update.html',{'tasks':tarea, 'form':form, 'error': 'error updating task'} ) |
||||
|
||||
@login_required |
||||
def tipo_registro(request): |
||||
if request.method=='GET': |
||||
return render(request, 'tipo_articulo_nuevo.html',{ |
||||
'form': formulario_tipo |
||||
}) |
||||
else: |
||||
try: |
||||
form = formulario_tipo (request.POST) |
||||
form.save() |
||||
form = formulario_tipo () |
||||
return render(request, 'tipo_articulo_nuevo.html',{ |
||||
'form': formulario_tipo, |
||||
'mensaje':'Please provide valid data' |
||||
}) |
||||
except ValueError: |
||||
return render(request, 'tipo_articulo_nuevo.html',{ |
||||
'form': formulario_tipo, |
||||
'error':'Please provide valid data' |
||||
}) |
||||
|
||||
@login_required |
||||
def tipo_vista(request): |
||||
persona = tipo_articulo.objects.all |
||||
return render(request,"tipo_articulo_registro.html", {'persona':persona}) |
||||
|
||||
@login_required |
||||
def tipo_update(request,task_id): |
||||
if request.method == 'GET': |
||||
tarea = get_object_or_404 (tipo_articulo, pk=task_id) |
||||
form = formulario_tipo(instance=tarea) |
||||
return render(request, 'tipo_articulo_update.html',{'tasks':tarea, 'form':form} ) |
||||
else: |
||||
try: |
||||
tarea=get_object_or_404(tipo_articulo,pk=task_id) |
||||
form = formulario_tipo(request.POST, instance=tarea) |
||||
form.save() |
||||
return redirect('tipo_vista') |
||||
except ValueError: |
||||
return render(request, 'tipo_articulo_update.html',{'tasks':tarea, 'form':form, 'error': 'error updating task'} ) |
||||
|
||||
@login_required |
||||
def articulo_registro(request): |
||||
if request.method=='GET': |
||||
return render(request, 'articulo_nuevo.html',{ |
||||
'form': formulario_articulo |
||||
}) |
||||
else: |
||||
try: |
||||
form = formulario_articulo (request.POST) |
||||
form.save() |
||||
form = formulario_articulo () |
||||
return redirect('articulo_registro') |
||||
|
||||
except ValueError: |
||||
return render(request, 'articulo_nuevo.html',{ |
||||
'form': formulario_articulo, |
||||
'error':'Please provide valid data' |
||||
}) |
||||
|
||||
@login_required |
||||
def articulo_vista(request): |
||||
persona = articulo.objects.select_related('tipo_articulo','proveedor').all() |
||||
return render(request,"articulo_registro.html", {'persona':persona}) |
||||
|
||||
@login_required |
||||
def articulo_update(request,task_id): |
||||
if request.method == 'GET': |
||||
tarea = get_object_or_404 (articulo, pk=task_id) |
||||
form = formulario_articulo(instance=tarea) |
||||
return render(request, 'articulo_update.html',{'tasks':tarea, 'form':form} ) |
||||
else: |
||||
try: |
||||
tarea=get_object_or_404(articulo,pk=task_id) |
||||
form = formulario_articulo(request.POST, instance=tarea) |
||||
form.save() |
||||
return redirect('articulo_vista') |
||||
except ValueError: |
||||
return render(request, 'articulo_update.html',{'tasks':tarea, 'form':form, 'error': 'error updating'} ) |
||||
|
||||
@login_required |
||||
def inventario_registro(request): |
||||
if request.method=='GET': |
||||
return render(request, 'inventario_nuevo.html',{ |
||||
'form': formilario_inventario |
||||
}) |
||||
else: |
||||
try: |
||||
form = formilario_inventario (request.POST) |
||||
form.save() |
||||
form = formilario_inventario () |
||||
return render(request, 'inventario_nuevo.html',{ |
||||
'form': formilario_inventario, |
||||
'mensaje':'Please provide valid data' |
||||
}) |
||||
except ValueError: |
||||
return render(request, 'inventario_nuevo.html',{ |
||||
'form': formilario_inventario, |
||||
'error':'Please provide valid data' |
||||
}) |
||||
|
||||
@login_required |
||||
def inventario_vista(request): |
||||
persona = inventario.objects.select_related('articulo','bodega').all() |
||||
return render(request,"inventario_registro.html", {'persona':persona}) |
||||
|
||||
def registrar_movimiento_y_actualizar_inventario(movimiento_data): |
||||
""" |
||||
Registra un movimiento de inventario y actualiza el inventario relacionado. |
||||
""" |
||||
try: |
||||
with transaction.atomic(): |
||||
# Registrar el movimiento de inventario |
||||
movimiento = movimiento_inventario.objects.create( |
||||
articulo=movimiento_data['articulo'], |
||||
bodega=movimiento_data['bodega'], |
||||
tipo_inventario=movimiento_data['tipo_inventario'], |
||||
cantidad=movimiento_data['cantidad'], |
||||
precio=movimiento_data['precio'], |
||||
observaciones=movimiento_data.get('observaciones', '') |
||||
) |
||||
|
||||
# Buscar o crear el inventario correspondiente |
||||
inventario_obj, created = inventario.objects.get_or_create( |
||||
articulo=movimiento.articulo, |
||||
bodega=movimiento.bodega, |
||||
defaults={'cantidad': 0, 'precio': movimiento.precio} |
||||
) |
||||
|
||||
# Actualizar stock según el tipo de movimiento |
||||
if movimiento.tipo_inventario.id == 1: # Asegúrate de que 'nombre' sea un campo válido |
||||
inventario_obj.cantidad += movimiento.cantidad |
||||
elif movimiento.tipo_inventario.id == 2: |
||||
if inventario_obj.cantidad < movimiento.cantidad: |
||||
raise ValueError( |
||||
f"No hay suficiente stock. Disponible: {inventario_obj.cantidad}, solicitado: {movimiento.cantidad}" |
||||
) |
||||
inventario_obj.cantidad -= movimiento.cantidad |
||||
|
||||
# Actualizar el precio en Inventario si es necesario |
||||
if inventario_obj.precio != movimiento.precio: |
||||
inventario_obj.precio = movimiento.precio |
||||
|
||||
# Guardar los cambios en el inventario |
||||
inventario_obj.save() |
||||
|
||||
return movimiento, inventario_obj |
||||
|
||||
except Exception as e: |
||||
raise ValueError(f"Error al registrar movimiento y actualizar inventario: {str(e)}") |
||||
|
||||
|
||||
def registrar_movimiento(request): |
||||
""" |
||||
Vista para registrar un nuevo movimiento de inventario. |
||||
""" |
||||
if request.method == 'POST': |
||||
form = formilario_inventario(request.POST) |
||||
if form.is_valid(): |
||||
movimiento_data = form.cleaned_data |
||||
try: |
||||
movimiento, inventario_obj = registrar_movimiento_y_actualizar_inventario(movimiento_data) |
||||
return redirect('registrar_movimiento') # Redirigir a una página de éxito |
||||
except ValueError as e: |
||||
return render(request, 'inventario_nuevo.html', { |
||||
'form': form, |
||||
'error': str(e) |
||||
}) |
||||
else: |
||||
form = formilario_inventario() |
||||
|
||||
return render(request, 'inventario_nuevo.html', {'form': form}) |
||||
|
||||
@login_required |
||||
def vista_inventario_mov(request): |
||||
persona = movimiento_inventario.objects.select_related('articulo','bodega','tipo_inventario').all() |
||||
return render(request,"mov_inventario_registro.html", {'persona':persona}) |
||||
|
||||
@ -0,0 +1,83 @@ |
||||
from django.contrib.auth.decorators import login_required |
||||
from django.http import HttpResponse |
||||
import openpyxl |
||||
import csv |
||||
from django.template.loader import get_template |
||||
from xhtml2pdf import pisa |
||||
from django.shortcuts import render |
||||
from .models import inventario |
||||
|
||||
@login_required |
||||
def generar_reporte_pdf(request): |
||||
# Obtener datos del inventario |
||||
inventarios = inventario.objects.all() |
||||
|
||||
# Cargar la plantilla |
||||
template = get_template('pdf.html') |
||||
context = {'inventarios': inventarios} |
||||
|
||||
# Renderizar la plantilla a HTML |
||||
html = template.render(context) |
||||
|
||||
# Crear un objeto de respuesta PDF |
||||
response = HttpResponse(content_type='application/pdf') |
||||
response['Content-Disposition'] = 'inline; filename="reporte.pdf"' |
||||
|
||||
# Convertir HTML a PDF |
||||
pisa_status = pisa.CreatePDF(html, dest=response) |
||||
|
||||
if pisa_status.err: |
||||
return HttpResponse(f'Error al generar PDF: {pisa_status.err}', status=500) |
||||
|
||||
return response |
||||
|
||||
@login_required |
||||
def generar_reporte_excel(request): |
||||
# Crear un nuevo libro de Excel |
||||
wb = openpyxl.Workbook() |
||||
ws = wb.active |
||||
ws.title = 'Reporte de Inventario' |
||||
|
||||
# Encabezados |
||||
headers = ['ID', 'Bodega', 'Artículo', 'Cantidad', 'Precio'] |
||||
ws.append(headers) |
||||
|
||||
# Datos del inventario |
||||
for item in inventario.objects.all(): |
||||
ws.append([item.id, item.bodega.nombre_bodega, item.articulo.nombre_articulo, item.cantidad, item.precio]) |
||||
|
||||
# Configurar la respuesta HTTP |
||||
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') |
||||
response['Content-Disposition'] = 'attachment; filename="reporte_inventario.xlsx"' |
||||
|
||||
# Guardar el archivo en la respuesta |
||||
wb.save(response) |
||||
return response |
||||
|
||||
@login_required |
||||
def generar_reporte_csv(request): |
||||
# Configurar la respuesta HTTP |
||||
response = HttpResponse(content_type='text/csv') |
||||
response['Content-Disposition'] = 'attachment; filename="reporte_inventario.csv"' |
||||
|
||||
writer = csv.writer(response) |
||||
|
||||
# Encabezados |
||||
writer.writerow(['ID', 'Bodega', 'Artículo', 'Cantidad', 'Precio']) |
||||
|
||||
# Datos del inventario |
||||
for item in inventario.objects.all(): |
||||
writer.writerow([item.id, item.bodega.nombre_bodega, item.articulo.nombre_articulo, item.cantidad, item.precio]) |
||||
|
||||
return response |
||||
|
||||
@login_required |
||||
def generar_grafico_chartjs(request): |
||||
inventarios = inventario.objects.all() |
||||
productos = [item.articulo.nombre_articulo for item in inventarios] |
||||
cantidades = [item.cantidad for item in inventarios] |
||||
|
||||
return render(request, 'grafico_chartjs.html', { |
||||
'productos': productos, |
||||
'cantidades': cantidades, |
||||
}) |
||||
@ -0,0 +1,2 @@ |
||||
import pymysql |
||||
pymysql.install_as_MySQLdb() |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,16 @@ |
||||
""" |
||||
ASGI config for TiendAlfa project. |
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``. |
||||
|
||||
For more information on this file, see |
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ |
||||
""" |
||||
|
||||
import os |
||||
|
||||
from django.core.asgi import get_asgi_application |
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TiendAlfa.settings') |
||||
|
||||
application = get_asgi_application() |
||||
@ -0,0 +1,132 @@ |
||||
""" |
||||
Django settings for TiendAlfa project. |
||||
|
||||
Generated by 'django-admin startproject' using Django 5.1.4. |
||||
|
||||
For more information on this file, see |
||||
https://docs.djangoproject.com/en/5.1/topics/settings/ |
||||
|
||||
For the full list of settings and their values, see |
||||
https://docs.djangoproject.com/en/5.1/ref/settings/ |
||||
""" |
||||
|
||||
from pathlib import Path |
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'. |
||||
BASE_DIR = Path(__file__).resolve().parent.parent |
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production |
||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ |
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret! |
||||
SECRET_KEY = 'django-insecure-f)f0@t_(a&ht0mo$&#nftao+_=s&^31u9))d=685^vs8&np=5k' |
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production! |
||||
DEBUG = True |
||||
|
||||
ALLOWED_HOSTS = ['store.stevz.dev','127.0.0.1'] |
||||
|
||||
|
||||
# Application definition |
||||
|
||||
INSTALLED_APPS = [ |
||||
'django.contrib.admin', |
||||
'django.contrib.auth', |
||||
'django.contrib.contenttypes', |
||||
'django.contrib.sessions', |
||||
'django.contrib.messages', |
||||
'django.contrib.staticfiles', |
||||
'Inventario', |
||||
|
||||
] |
||||
|
||||
MIDDLEWARE = [ |
||||
'django.middleware.security.SecurityMiddleware', |
||||
'django.contrib.sessions.middleware.SessionMiddleware', |
||||
'django.middleware.common.CommonMiddleware', |
||||
'django.middleware.csrf.CsrfViewMiddleware', |
||||
'django.contrib.auth.middleware.AuthenticationMiddleware', |
||||
'django.contrib.messages.middleware.MessageMiddleware', |
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
||||
] |
||||
|
||||
ROOT_URLCONF = 'TiendAlfa.urls' |
||||
|
||||
TEMPLATES = [ |
||||
{ |
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
||||
'DIRS': [], |
||||
'APP_DIRS': True, |
||||
'OPTIONS': { |
||||
'context_processors': [ |
||||
'django.template.context_processors.debug', |
||||
'django.template.context_processors.request', |
||||
'django.contrib.auth.context_processors.auth', |
||||
'django.contrib.messages.context_processors.messages', |
||||
], |
||||
}, |
||||
}, |
||||
] |
||||
|
||||
WSGI_APPLICATION = 'TiendAlfa.wsgi.application' |
||||
|
||||
|
||||
# Database |
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases |
||||
|
||||
DATABASES = { |
||||
'default': { |
||||
'ENGINE': 'django.db.backends.mysql', # Motor de MySQL |
||||
'NAME': 'ttienda', # Nombre de la base de datos |
||||
'USER': 'ttienda', # Usuario de MySQL |
||||
'PASSWORD': 'Tienda$2024', # Contraseña de MySQL |
||||
'HOST': '10.138.214.232', # Dirección del servidor (por defecto localhost) |
||||
'PORT': '3306', # Puerto de MySQL (por defecto 3306) |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
# Password validation |
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators |
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [ |
||||
{ |
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', |
||||
}, |
||||
{ |
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', |
||||
}, |
||||
{ |
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', |
||||
}, |
||||
{ |
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', |
||||
}, |
||||
] |
||||
|
||||
|
||||
# Internationalization |
||||
# https://docs.djangoproject.com/en/5.1/topics/i18n/ |
||||
|
||||
LANGUAGE_CODE = 'en-us' |
||||
|
||||
TIME_ZONE = 'UTC' |
||||
|
||||
USE_I18N = True |
||||
|
||||
USE_TZ = True |
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images) |
||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/ |
||||
|
||||
STATIC_URL = 'static/' |
||||
|
||||
LOGIN_URL= '/signin/' |
||||
|
||||
# Default primary key field type |
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field |
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' |
||||
@ -0,0 +1,54 @@ |
||||
""" |
||||
URL configuration for TiendAlfa project. |
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see: |
||||
https://docs.djangoproject.com/en/5.1/topics/http/urls/ |
||||
Examples: |
||||
Function views |
||||
1. Add an import: from my_app import views |
||||
2. Add a URL to urlpatterns: path('', views.home, name='home') |
||||
Class-based views |
||||
1. Add an import: from other_app.views import Home |
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') |
||||
Including another URLconf |
||||
1. Import the include() function: from django.urls import include, path |
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) |
||||
""" |
||||
from django.contrib import admin |
||||
from django.urls import path |
||||
from Inventario import views, viewspdf |
||||
|
||||
urlpatterns = [ |
||||
path('admin/', admin.site.urls), |
||||
path('',views.generar_grafico_chartjs, name='home'), |
||||
path('logout/',views.signout, name ='logout'), |
||||
path('signin/',views.signin, name ='signin'), |
||||
path('proveedor/',views.proveedor_lista, name ='proveedor'), |
||||
path('proveedor/crear/',views.proveedor_registro, name ='proveedor_registro'), |
||||
path('proveedor/<int:task_id>/',views.Proveedor_update, name ='Proveedor_update'), |
||||
path('bodega/',views.bodega_vista, name ='bodega_vista'), |
||||
path('bodega/crearbodega/',views.bodega_registro, name ='bodega_registro'), |
||||
path('bodega/<int:task_id>/',views.bodega_update, name ='bodega_update'), |
||||
path('tipo/',views.tipo_vista, name ='tipo_vista'), |
||||
path('tipo/creartipo_articulo/',views.tipo_registro, name ='tipo_registro'), |
||||
path('tipo/<int:task_id>/',views.tipo_update, name ='tipo_update'), |
||||
path('articulo/',views.articulo_vista, name ='articulo_vista'), |
||||
path('articulo/crear/',views.articulo_registro, name ='articulo_registro'), |
||||
path('articulo/<int:task_id>/',views.articulo_update, name ='articulo_update'), |
||||
path('inventario/',views.inventario_vista, name ='inventario_vista'), |
||||
path('inventariomov/crear/',views.registrar_movimiento, name ='registrar_movimiento'), |
||||
path('inventariomov/',views.vista_inventario_mov, name ='vista_inventario_mov'), |
||||
path('reporte/pdf/',viewspdf.generar_reporte_pdf, name ='generar_reporte_pdf'), |
||||
path('reporte/excel/',viewspdf.generar_reporte_excel, name ='generar_reporte_excel'), |
||||
path('reporte/csv/',viewspdf.generar_reporte_csv, name ='generar_reporte_csv'), |
||||
path('grafico/chartjs/',viewspdf.generar_grafico_chartjs, name ='generar_grafico_chartjs'), |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
] |
||||
@ -0,0 +1,16 @@ |
||||
""" |
||||
WSGI config for TiendAlfa project. |
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``. |
||||
|
||||
For more information on this file, see |
||||
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ |
||||
""" |
||||
|
||||
import os |
||||
|
||||
from django.core.wsgi import get_wsgi_application |
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TiendAlfa.settings') |
||||
|
||||
application = get_wsgi_application() |
||||
@ -0,0 +1,22 @@ |
||||
#!/usr/bin/env python |
||||
"""Django's command-line utility for administrative tasks.""" |
||||
import os |
||||
import sys |
||||
|
||||
|
||||
def main(): |
||||
"""Run administrative tasks.""" |
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TiendAlfa.settings') |
||||
try: |
||||
from django.core.management import execute_from_command_line |
||||
except ImportError as exc: |
||||
raise ImportError( |
||||
"Couldn't import Django. Are you sure it's installed and " |
||||
"available on your PYTHONPATH environment variable? Did you " |
||||
"forget to activate a virtual environment?" |
||||
) from exc |
||||
execute_from_command_line(sys.argv) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
||||
Loading…
Reference in new issue