parent
1cdf03de85
commit
331de4f08d
@ -0,0 +1 @@ |
||||
venv/ |
||||
@ -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', 'registro.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() |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,16 @@ |
||||
""" |
||||
ASGI config for registro 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', 'registro.settings') |
||||
|
||||
application = get_asgi_application() |
||||
@ -0,0 +1,33 @@ |
||||
""" |
||||
URL configuration for registro 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 tasks import views |
||||
|
||||
urlpatterns = [ |
||||
path('admin/', admin.site.urls), |
||||
path('',views.signin, name='signin'), |
||||
path('signup/',views.signup, name='signup'), |
||||
path('cuentas/', views.lista_cuentas, name='lista_cuentas'), |
||||
path('cuentas/nueva/', views.registrar_cuenta, name='registrar_cuenta'), |
||||
path('signout/',views.signout, name='signout'), |
||||
path('movimientos/<int:cuenta_id>/', views.lista_movimientos, name='lista_movimientos'), |
||||
path('movimientos/<int:cuenta_id>/nuevo', views.registrar_movimiento, name='registrar_movimiento'), |
||||
path('movimientos/editar/<int:movimiento_id>/', views.actualizar_movimiento, name='actualizar_movimiento'), |
||||
path('movimientos/excel/<int:cuenta_id>/', views.exportar_movimientos_excel, name='exportar_movimientos_excel'), |
||||
|
||||
] |
||||
@ -0,0 +1,16 @@ |
||||
""" |
||||
WSGI config for registro 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', 'registro.settings') |
||||
|
||||
application = get_wsgi_application() |
||||
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 TasksConfig(AppConfig): |
||||
default_auto_field = 'django.db.models.BigAutoField' |
||||
name = 'tasks' |
||||
@ -0,0 +1,36 @@ |
||||
from django import forms |
||||
from .models import Cuenta, Movimientos, TipoMovimiento |
||||
|
||||
class RegistroCuentaForm(forms.ModelForm): |
||||
class Meta: |
||||
model = Cuenta |
||||
fields = ['cuenta', 'observacion'] |
||||
widgets = { |
||||
'cuenta': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre de la cuenta'}), |
||||
'observacion': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones'}), |
||||
} |
||||
class RegistroMovimientosForm(forms.ModelForm): |
||||
tipo_movimiento = forms.ModelChoiceField( |
||||
queryset=TipoMovimiento.objects.all(), |
||||
empty_label="Seleccione un tipo de movimiento", |
||||
widget=forms.Select(attrs={'class': 'form-control'}), |
||||
label="Tipo de movimiento" |
||||
) |
||||
fecha_factura = forms.DateField( |
||||
widget=forms.DateInput( |
||||
attrs={'type': 'date', 'class': 'form-control'} |
||||
), |
||||
label="Fecha de factura", |
||||
) |
||||
|
||||
class Meta: |
||||
model = Movimientos |
||||
fields = ['tipo_movimiento','fecha_factura', 'saldo','n_factura', 'observacion','proveedor','responsable_cuenta'] |
||||
widgets = { |
||||
'saldo': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Monto'}), |
||||
'observacion': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones'}), |
||||
'n_factura': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Ingrese numero de factura'}), |
||||
'proveedor': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre del proveedor'}), |
||||
'responsable_cuenta': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre del responsable'}), |
||||
|
||||
} |
||||
@ -0,0 +1,46 @@ |
||||
# Generated by Django 5.1.5 on 2025-02-06 13:47 |
||||
|
||||
import django.db.models.deletion |
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
initial = True |
||||
|
||||
dependencies = [ |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='TipoMovimiento', |
||||
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='Cuenta', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('cuenta', models.CharField(max_length=100)), |
||||
('fecha_creacion', models.DateField(auto_now_add=True)), |
||||
('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=15, null=True)), |
||||
('observacion', models.TextField(blank=True)), |
||||
('responsable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='Movimientos', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=15, null=True)), |
||||
('fecha_insersion', models.DateField(auto_now_add=True)), |
||||
('observacion', models.TextField(blank=True)), |
||||
('responsable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
||||
('tipo_movimiento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tipomovimiento')), |
||||
], |
||||
), |
||||
] |
||||
@ -0,0 +1,31 @@ |
||||
# Generated by Django 5.1.5 on 2025-02-06 15:50 |
||||
|
||||
import django.db.models.deletion |
||||
import django.utils.timezone |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tasks', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='movimientos', |
||||
name='cuentas', |
||||
field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, to='tasks.cuenta'), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AlterField( |
||||
model_name='cuenta', |
||||
name='fecha_creacion', |
||||
field=models.DateField(default=django.utils.timezone.now), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='movimientos', |
||||
name='fecha_insersion', |
||||
field=models.DateField(default=django.utils.timezone.now), |
||||
), |
||||
] |
||||
@ -0,0 +1,19 @@ |
||||
# Generated by Django 5.1.5 on 2025-02-06 18:13 |
||||
|
||||
import django.db.models.deletion |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tasks', '0002_movimientos_cuentas_alter_cuenta_fecha_creacion_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='movimientos', |
||||
name='cuentas', |
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.cuenta'), |
||||
), |
||||
] |
||||
@ -0,0 +1,34 @@ |
||||
# Generated by Django 5.1.5 on 2025-02-07 01:41 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('tasks', '0003_alter_movimientos_cuentas'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='movimientos', |
||||
name='fecha_factura', |
||||
field=models.DateField(blank=True, null=True), |
||||
), |
||||
migrations.AddField( |
||||
model_name='movimientos', |
||||
name='n_factura', |
||||
field=models.PositiveIntegerField(default=1), |
||||
preserve_default=False, |
||||
), |
||||
migrations.AddField( |
||||
model_name='movimientos', |
||||
name='proveedor', |
||||
field=models.CharField(blank=True, max_length=100), |
||||
), |
||||
migrations.AddField( |
||||
model_name='movimientos', |
||||
name='responsable_cuenta', |
||||
field=models.CharField(blank=True, max_length=100), |
||||
), |
||||
] |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,33 @@ |
||||
from django.db import models |
||||
from django.contrib.auth.models import User |
||||
from django.utils import timezone |
||||
from decimal import Decimal |
||||
|
||||
|
||||
# Create your models here. |
||||
class TipoMovimiento(models.Model): |
||||
tipo_movimiento=models.CharField(max_length=100) |
||||
def __str__(self): |
||||
return self.tipo_movimiento # Retorna el nombre legible |
||||
|
||||
|
||||
class Cuenta (models.Model): |
||||
cuenta=models.CharField(max_length=100) |
||||
fecha_creacion=models.DateField(default=timezone.now) |
||||
saldo=models.DecimalField(max_digits=15, decimal_places=2, null=True, default=Decimal('0.00')) |
||||
responsable = models.ForeignKey(User, on_delete=models.CASCADE) |
||||
observacion= models.TextField(blank=True) |
||||
def __str__(self): |
||||
return self.cuenta |
||||
|
||||
class Movimientos(models.Model): |
||||
responsable = models.ForeignKey(User, on_delete=models.CASCADE) |
||||
tipo_movimiento=models.ForeignKey(TipoMovimiento, on_delete=models.CASCADE) |
||||
saldo=models.DecimalField(max_digits=15, decimal_places=2, null=True, default=Decimal('0.00')) |
||||
fecha_insersion=models.DateField(default=timezone.now) |
||||
observacion= models.TextField(blank=True) |
||||
cuentas= models.ForeignKey(Cuenta, on_delete=models.CASCADE) |
||||
n_factura=models.PositiveIntegerField() |
||||
fecha_factura = models.DateField(blank=True) |
||||
proveedor= models.CharField(max_length=100, blank=True) |
||||
responsable_cuenta=models.CharField(max_length=100, blank=True) |
||||
@ -0,0 +1,77 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="es"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>{% block title %}Registro{% endblock %}</title> |
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> |
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"> |
||||
</head> |
||||
<style> |
||||
.badge-success { |
||||
background-color: #28a745; |
||||
} |
||||
.badge-warning { |
||||
background-color: #ffc107; |
||||
} |
||||
.badge-danger { |
||||
background-color: #dc3545; |
||||
} |
||||
|
||||
.table-striped tbody tr:nth-of-type(odd) { |
||||
background-color: #f9f9f9; |
||||
} |
||||
.btn-primary { |
||||
background-color: #007bff; |
||||
border-color: #007bff; |
||||
} |
||||
.btn-secondary { |
||||
background-color: #6c757d; |
||||
border-color: #6c757d; |
||||
} |
||||
.btn-sm { |
||||
font-size: 0.875rem; |
||||
} |
||||
html, body { |
||||
height: 100%; |
||||
margin: 0; |
||||
} |
||||
.container { |
||||
min-height: 80%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
.footer { |
||||
margin-top: auto; |
||||
} |
||||
</style> |
||||
<body> |
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> |
||||
<div class="container-fluid"> |
||||
<a class="navbar-brand">Registro</a> |
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> |
||||
<span class="navbar-toggler-icon"></span> |
||||
</button> |
||||
<div class="collapse navbar-collapse" id="navbarNav"> |
||||
<ul class="navbar-nav ms-auto"> |
||||
{% if user.is_authenticated %} |
||||
<li class="nav-item"><a class="nav-link" href="{% url 'lista_cuentas' %}">Nuevo registro de cuenta</a></li> |
||||
<li class="nav-item"><a class="nav-link" href="{% url 'signout' %}">Cerrar Sesión</a></li> |
||||
{% else %} |
||||
<li class="nav-item"><a class="nav-link" href="{% url 'signin' %}">Inicio de Sesión</a></li> |
||||
|
||||
{% endif %} |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</nav> |
||||
<div class="container mt-4"> |
||||
{% block content %} |
||||
{% endblock %} |
||||
</div> |
||||
<footer class="bg-dark text-white text-center py-3 mt-4"> |
||||
<p>© 2025 registro</p> |
||||
</footer> |
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,21 @@ |
||||
{% extends 'base.html' %} |
||||
|
||||
{% block content %} |
||||
<div class="container mt-5"> |
||||
<div class="card shadow-lg"> |
||||
<div class="card-header bg-primary text-white"> |
||||
<h3>Registrar Nueva Cuenta</h3> |
||||
</div> |
||||
<div class="card-body"> |
||||
<form method="POST"> |
||||
{% csrf_token %} |
||||
{{ form.as_p }} |
||||
|
||||
<button type="submit" class="btn btn-success">Guardar</button> |
||||
<a href="{% url 'lista_cuentas' %}" class="btn btn-secondary">Cancelar</a> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
{% endblock %} |
||||
@ -0,0 +1,80 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
{% if user.is_authenticated %} |
||||
|
||||
{% else %} |
||||
<style> |
||||
body { |
||||
background: #2c2f36; /* Color de fondo oscuro */ |
||||
color: #fff; /* Texto en blanco para contraste */ |
||||
} |
||||
.card { |
||||
background-color: #ffffff; /* Fondo blanco para el formulario */ |
||||
border-radius: 12px; |
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
||||
} |
||||
.card-body { |
||||
padding: 30px; |
||||
} |
||||
.form-label { |
||||
color: #333; /* Color de las etiquetas */ |
||||
} |
||||
.btn-primary { |
||||
background-color: #007bff; /* Botón azul */ |
||||
border-color: #007bff; |
||||
} |
||||
.btn-primary:hover { |
||||
background-color: #0056b3; /* Color al pasar el ratón */ |
||||
border-color: #0056b3; |
||||
} |
||||
.alert { |
||||
background-color: #f8d7da; /* Fondo rojo suave para los errores */ |
||||
border-color: #f5c6cb; |
||||
} |
||||
.alert p { |
||||
margin-bottom: 0; |
||||
} |
||||
</style> |
||||
<div class="container-fluid p-0" style="height: 100vh; background-color: #2c2f36;"> |
||||
<div class="d-flex justify-content-center align-items-center" style="height: 100vh;"> |
||||
<div class="card shadow-lg p-4" style="max-width: 400px; border-radius: 12px; background-color: #ffffff;"> |
||||
<div class="card-body"> |
||||
<h3 class="text-center mb-4" style="color: #333;">Iniciar Sesión</h3> |
||||
|
||||
<form method="post"> |
||||
{% csrf_token %} |
||||
<div class="mb-3"> |
||||
<label for="username" class="form-label" style="color: #333;">Nombre de Usuario</label> |
||||
<input type="text" class="form-control" id="username" name="username" required |
||||
style="border-radius: 10px; box-shadow: none; transition: all 0.3s ease;"> |
||||
</div> |
||||
|
||||
<div class="mb-3"> |
||||
<label for="password" class="form-label" style="color: #333;">Contraseña</label> |
||||
<input type="password" class="form-control" id="password" name="password" required |
||||
style="border-radius: 10px; box-shadow: none; transition: all 0.3s ease;"> |
||||
</div> |
||||
|
||||
<button type="submit" class="btn btn-dark w-100" style="border-radius: 10px; font-size: 16px; letter-spacing: 1px;"> |
||||
Iniciar Sesión |
||||
</button> |
||||
</form> |
||||
<div class="text-center mt-3"> |
||||
<a href="{% url 'signup' %}" class="text-muted" style="font-size: 14px;">Registrar</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endif %} |
||||
|
||||
{% endblock %} |
||||
{% block footer %} |
||||
<footer class="footer mt-auto py-3 bg-dark text-white"> |
||||
<div class="container"> |
||||
<span class="text-muted">© 2024 Mi Aplicación. Todos los derechos reservados.</span> |
||||
</div> |
||||
</footer> |
||||
{% endblock %} |
||||
|
||||
@ -0,0 +1,54 @@ |
||||
{% extends 'base.html' %} |
||||
|
||||
{% block content %} |
||||
<div class="container mt-5"> |
||||
<h2>Lista de Cuentas</h2> |
||||
<div class="d-flex gap-2 mb-3"> |
||||
{% if es_superusuario %} |
||||
<a href="{% url 'lista_cuentas' %}?todas=1" class="btn btn-secondary"> |
||||
Ver Todas las Cuentas |
||||
</a> |
||||
<a href="{% url 'lista_cuentas' %}" class="btn btn-secondary"> |
||||
Ver Mis Cuentas |
||||
</a> |
||||
{% endif %} |
||||
</div> |
||||
<div class="table-responsive"> |
||||
<table class="table table-striped"> |
||||
<thead> |
||||
<tr> |
||||
<th>Nombre</th> |
||||
<th>Saldo</th> |
||||
<th>Responsable</th> |
||||
<th>Fecha Creación</th> |
||||
<th>Observaciones</th> |
||||
<th>Acciones</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for cuenta in cuentas %} |
||||
<tr> |
||||
<td>{{ cuenta.cuenta }}</td> |
||||
<td>{{ cuenta.saldo }}</td> |
||||
<td>{{ cuenta.responsable.username }}</td> |
||||
<td>{{ cuenta.fecha_creacion }}</td> |
||||
<td>{{ cuenta.observacion }}</td> |
||||
<td> |
||||
<a href="{% url 'lista_movimientos' cuenta.id %}" class="btn btn-info btn-sm"> |
||||
Ver Movimientos |
||||
</a> |
||||
<a href="{% url 'exportar_movimientos_excel' cuenta.id %}" class="btn btn-success btn-sm"> |
||||
Exportar a Excel |
||||
</a> |
||||
</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<a href="{% url 'registrar_cuenta' %}" class="btn btn-success btn-lg rounded-circle position-fixed bottom-0 end-0 m-3 shadow-lg"> |
||||
<i class="fas fa-plus"></i> |
||||
</a> |
||||
{% endblock %} |
||||
@ -0,0 +1,49 @@ |
||||
{% extends 'base.html' %} |
||||
|
||||
{% block content %} |
||||
<div class="container mt-5"> |
||||
<h2>Movimientos de la cuenta: {{ cuenta.cuenta }}</h2> |
||||
<div class="table-responsive"> |
||||
<table class="table table-striped"> |
||||
<thead> |
||||
<tr> |
||||
<th>Tipo</th> |
||||
<th>Monto</th> |
||||
<th>Fecha</th> |
||||
<th>Observación</th> |
||||
<th># de factura</th> |
||||
<th>fecha de factura</th> |
||||
<th>responsable</th> |
||||
<th>Acciones</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{% for movimiento in movimientos %} |
||||
<tr> |
||||
<td>{{ movimiento.tipo_movimiento }}</td> |
||||
<td>{{ movimiento.saldo }}</td> |
||||
<td>{{ movimiento.fecha_insersion }}</td> |
||||
<td>{{ movimiento.observacion }}</td> |
||||
<td>{{ movimiento.n_factura }}</td> |
||||
<td>{{ movimiento.fecha_factura }}</td> |
||||
<td>{{ movimiento.responsable_cuenta }}</td> |
||||
<td> |
||||
<a href="{% url 'actualizar_movimiento' movimiento.id %}" class="btn btn-warning btn-sm"> |
||||
Editar |
||||
</a> |
||||
</td> |
||||
</tr> |
||||
{% empty %} |
||||
<tr> |
||||
<td colspan="4" class="text-center">No hay movimientos registrados.</td> |
||||
</tr> |
||||
{% endfor %} |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<a href="{% url 'registrar_movimiento' cuenta.id %}" class="btn btn-success btn-lg rounded-circle position-fixed bottom-0 end-0 m-3 shadow-lg"> |
||||
<i class="fas fa-plus"></i> |
||||
</a> |
||||
{% endblock %} |
||||
@ -0,0 +1,49 @@ |
||||
{% extends 'base.html' %} |
||||
|
||||
{% block content %} |
||||
<div class="container mt-5"> |
||||
<div class="card shadow-lg"> |
||||
<div class="card-header bg-info text-white"> |
||||
<h3>Registrar Nuevo Movimiento</h3> |
||||
</div> |
||||
<div class="card-body"> |
||||
<form method="POST"> |
||||
{% csrf_token %} |
||||
<input type="hidden" name="cuentas" value="{{ cuenta.id }}"> <!-- Captura automática --> |
||||
|
||||
<div class="mb-3"> |
||||
<label class="form-label">Cuenta</label> |
||||
<input type="text" class="form-control" value="{{ cuenta.cuenta }} (Saldo: {{ cuenta.saldo }})" disabled> |
||||
</div> |
||||
<div class="mb-3"> |
||||
<label for="id_tipo_movimiento" class="form-label">Tipo de Movimiento</label> |
||||
{{ form.tipo_movimiento }} |
||||
</div> |
||||
<div class="mb-3"> |
||||
<label for="id_saldo" class="form-label">Monto</label> |
||||
{{ form.saldo }} |
||||
</div> |
||||
<div class="mb-3"> |
||||
<label for="id_observacion" class="form-label">Observación</label> |
||||
{{ form.observacion }} |
||||
</div> |
||||
<div class="mb-3"> |
||||
<label for="id_n_factura" class="form-label"># de factura</label> |
||||
{{ form.n_factura }} |
||||
</div> <div class="mb-3"> |
||||
<label for="id_fecha_factura" class="form-label">fecha de factura</label> |
||||
{{ form.fecha_factura }} |
||||
</div> <div class="mb-3"> |
||||
<label for="proveedor" class="form-label">proveedor</label> |
||||
{{ form.proveedor }} |
||||
</div> <div class="mb-3"> |
||||
<label for="id_responsable_cuenta" class="form-label">responsable</label> |
||||
{{ form.responsable_cuenta }} |
||||
</div> |
||||
<button type="submit" class="btn btn-success">Guardar</button> |
||||
<a href="{% url 'lista_movimientos' cuenta.id %}" class="btn btn-secondary">Cancelar</a> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,108 @@ |
||||
{% extends "Base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<style> |
||||
body { |
||||
background: #2c2f36; /* Color de fondo oscuro */ |
||||
color: #fff; /* Texto en blanco para contraste */ |
||||
} |
||||
.card { |
||||
background-color: #ffffff; /* Fondo blanco para el formulario */ |
||||
border-radius: 12px; |
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
||||
} |
||||
.card-body { |
||||
padding: 30px; |
||||
} |
||||
.form-label { |
||||
color: #333; /* Color de las etiquetas */ |
||||
} |
||||
.btn-primary { |
||||
background-color: #007bff; /* Botón azul */ |
||||
border-color: #007bff; |
||||
} |
||||
.btn-primary:hover { |
||||
background-color: #0056b3; /* Color al pasar el ratón */ |
||||
border-color: #0056b3; |
||||
} |
||||
.alert { |
||||
background-color: #f8d7da; /* Fondo rojo suave para los errores */ |
||||
border-color: #f5c6cb; |
||||
} |
||||
.alert p { |
||||
margin-bottom: 0; |
||||
} |
||||
</style> |
||||
|
||||
<div class="container"> |
||||
<div class="row justify-content-center mt-5"> |
||||
<div class="col-md-6"> |
||||
<div class="card shadow-lg"> |
||||
<div class="card-body"> |
||||
<h2 class="text-center mb-4" style="color: #333;">Registrar Nueva Cuenta</h2> |
||||
|
||||
<form method="POST"> |
||||
{% csrf_token %} |
||||
|
||||
<div class="form-group mb-3"> |
||||
<label for="username" class="form-label">Nombre de Usuario</label> |
||||
<input type="text" class="form-control" id="username" name="username" value="{{ form.username.value }}"> |
||||
{% if form.username.errors %} |
||||
<div class="alert alert-danger mt-2"> |
||||
{% for error in form.username.errors %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
|
||||
<div class="form-group mb-3"> |
||||
<label for="email" class="form-label">Correo Electrónico</label> |
||||
<input type="email" class="form-control" id="email" name="email" value="{{ form.email.value }}"> |
||||
{% if form.email.errors %} |
||||
<div class="alert alert-danger mt-2"> |
||||
{% for error in form.email.errors %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
|
||||
<div class="form-group mb-3"> |
||||
<label for="password1" class="form-label">Contraseña</label> |
||||
<input type="password" class="form-control" id="password1" name="password1" value="{{ form.password1.value }}"> |
||||
{% if form.password1.errors %} |
||||
<div class="alert alert-danger mt-2"> |
||||
{% for error in form.password1.errors %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
|
||||
<div class="form-group mb-3"> |
||||
<label for="password2" class="form-label">Confirmar Contraseña</label> |
||||
<input type="password" class="form-control" id="password2" name="password2" value="{{ form.password2.value }}"> |
||||
{% if form.password2.errors %} |
||||
<div class="alert alert-danger mt-2"> |
||||
{% for error in form.password2.errors %} |
||||
<p>{{ error }}</p> |
||||
{% endfor %} |
||||
</div> |
||||
{% endif %} |
||||
</div> |
||||
|
||||
<div class="text-center"> |
||||
<button type="submit" class="btn btn-primary w-100">Registrar</button> |
||||
</div> |
||||
</form> |
||||
<div class="text-center mt-3"> |
||||
<a href="{% url 'signin' %}" class="text-muted" style="font-size: 14px;">¿Ya tienes cuenta? Inicia sesión</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,3 @@ |
||||
from django.test import TestCase |
||||
|
||||
# Create your tests here. |
||||
@ -0,0 +1,215 @@ |
||||
from django.shortcuts import render, redirect, get_object_or_404 |
||||
from django.contrib.auth.decorators import login_required |
||||
from django.utils import timezone |
||||
from decimal import Decimal |
||||
from django.http import HttpResponse |
||||
from django.contrib.auth import login, authenticate, logout |
||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm |
||||
from .models import Cuenta, Movimientos, TipoMovimiento |
||||
from django import forms |
||||
import openpyxl |
||||
from django.contrib.auth.models import User |
||||
from .forms import RegistroCuentaForm, RegistroMovimientosForm |
||||
|
||||
|
||||
|
||||
def base(request): |
||||
return render(request, 'index.html') |
||||
|
||||
def signin(request): |
||||
if request.method == 'GET': |
||||
return render ( request, 'index.html',{ |
||||
'form': AuthenticationForm |
||||
}) |
||||
else: |
||||
user=authenticate(request, username=request.POST['username'], password=request.POST['password']) |
||||
if user is None: |
||||
return render ( request, 'index.html',{ |
||||
'form': AuthenticationForm , |
||||
'error': 'Username or password is incorrect' |
||||
}) |
||||
else: |
||||
login(request, user) |
||||
return redirect('lista_cuentas') |
||||
|
||||
def signout(request): |
||||
logout(request) |
||||
return redirect('signin') |
||||
|
||||
class CustomUserCreationForm(UserCreationForm): |
||||
email = forms.EmailField(required=True, help_text="Introduce un correo electrónico válido.") |
||||
|
||||
class Meta: |
||||
model = User |
||||
fields = ('username', 'email', 'password1', 'password2') |
||||
|
||||
def save(self, commit=True): |
||||
user = super().save(commit=False) |
||||
user.email = self.cleaned_data["email"] |
||||
if commit: |
||||
user.save() |
||||
return user |
||||
|
||||
def signup(request): |
||||
if request.method == 'POST': |
||||
form = CustomUserCreationForm(request.POST) |
||||
|
||||
# Verificamos si el formulario es válido |
||||
if form.is_valid(): |
||||
# Guardamos el usuario si todo está bien |
||||
form.save() |
||||
|
||||
# Mensaje de éxito |
||||
return redirect('signin') # Redirige a la página de inicio de sesión |
||||
else: |
||||
# Si el formulario no es válido, mostramos los errores en los campos específicos |
||||
if 'username' not in form.cleaned_data: |
||||
form.add_error('username', 'El nombre de usuario es obligatorio.') |
||||
if 'email' not in form.cleaned_data: |
||||
form.add_error('email', 'El correo electrónico es obligatorio.') |
||||
if 'password1' not in form.cleaned_data: |
||||
form.add_error('password1', 'La contraseña es obligatoria.') |
||||
if 'password2' not in form.cleaned_data: |
||||
form.add_error('password2', 'Debes confirmar la contraseña.') |
||||
|
||||
return render(request, 'singup.html', {'form': form}) |
||||
|
||||
else: |
||||
# Si es un GET, mostramos un formulario vacío |
||||
form = CustomUserCreationForm() |
||||
return render(request, 'singup.html', {'form': form}) |
||||
|
||||
@login_required |
||||
def registrar_cuenta(request): |
||||
if request.method == "POST": |
||||
form = RegistroCuentaForm(request.POST) |
||||
if form.is_valid(): |
||||
cuenta = form.save(commit=False) |
||||
cuenta.responsable = request.user |
||||
cuenta.save() |
||||
return redirect('lista_cuentas') # Cambia el nombre de la vista destino |
||||
else: |
||||
form = RegistroCuentaForm() |
||||
|
||||
return render(request, 'cuentas.html', {'form': form}) |
||||
|
||||
@login_required |
||||
def registrar_movimiento(request, cuenta_id): |
||||
cuenta = get_object_or_404(Cuenta, id=cuenta_id) # Obtiene la cuenta específica |
||||
|
||||
if request.method == "POST": |
||||
form = RegistroMovimientosForm(request.POST) |
||||
if form.is_valid(): |
||||
movimiento = form.save(commit=False) |
||||
movimiento.cuentas = cuenta # Asigna la cuenta automáticamente |
||||
movimiento.responsable = request.user # Asigna el usuario actual |
||||
|
||||
# Ajusta el saldo según el tipo de movimiento |
||||
if movimiento.tipo_movimiento.id == 1: # Supongamos que ID 1 es ingreso |
||||
cuenta.saldo += movimiento.saldo |
||||
elif movimiento.tipo_movimiento.id == 2: # ID 2 es egreso |
||||
cuenta.saldo -= movimiento.saldo |
||||
|
||||
cuenta.save() |
||||
movimiento.save() |
||||
return redirect('lista_movimientos', cuenta_id=cuenta.id) |
||||
else: |
||||
form = RegistroMovimientosForm() |
||||
|
||||
return render(request, 'movimiento.html', {'form': form, 'cuenta': cuenta}) |
||||
|
||||
@login_required |
||||
def actualizar_movimiento(request, movimiento_id): |
||||
movimiento = get_object_or_404(Movimientos, id=movimiento_id) |
||||
cuenta = movimiento.cuentas # Obtener la cuenta asociada |
||||
saldo_anterior = movimiento.saldo # Guardar saldo anterior |
||||
tipo_movimiento_anterior = movimiento.tipo_movimiento_id # Guardar tipo anterior |
||||
|
||||
if request.method == "POST": |
||||
form = RegistroMovimientosForm(request.POST, instance=movimiento) |
||||
if form.is_valid(): |
||||
nuevo_movimiento = form.save(commit=False) |
||||
|
||||
# Restaurar saldo antes de actualizar (solo si cambió el tipo o saldo) |
||||
if tipo_movimiento_anterior == 1: # Ingreso anterior |
||||
cuenta.saldo -= saldo_anterior |
||||
elif tipo_movimiento_anterior == 2: # Egreso anterior |
||||
cuenta.saldo += saldo_anterior |
||||
|
||||
# Aplicar nuevo saldo |
||||
if nuevo_movimiento.tipo_movimiento_id == 1: # Nuevo ingreso |
||||
cuenta.saldo += nuevo_movimiento.saldo |
||||
elif nuevo_movimiento.tipo_movimiento_id == 2: # Nuevo egreso |
||||
cuenta.saldo -= nuevo_movimiento.saldo |
||||
|
||||
cuenta.save() # Guardar la cuenta con el saldo actualizado |
||||
nuevo_movimiento.save() # Guardar el movimiento |
||||
|
||||
return redirect('lista_movimientos', cuenta_id=cuenta.id) |
||||
else: |
||||
form = RegistroMovimientosForm(instance=movimiento) |
||||
|
||||
return render(request, 'movimiento.html', {'form': form, 'cuenta': cuenta, 'movimiento': movimiento}) |
||||
|
||||
@login_required |
||||
def lista_cuentas(request): |
||||
if request.user.is_superuser: |
||||
cuentas = Cuenta.objects.all() # Si es superusuario, puede ver todas las cuentas |
||||
mostrar_todas = request.GET.get('todas', False) # Verifica si quiere ver todas las cuentas |
||||
if not mostrar_todas: |
||||
cuentas = cuentas.filter(responsable=request.user) # Muestra solo las propias si no activa la opción |
||||
else: |
||||
cuentas = Cuenta.objects.filter(responsable=request.user) # Usuario normal solo ve sus cuentas |
||||
|
||||
return render(request, 'lista_cuentas.html', {'cuentas': cuentas, 'es_superusuario': request.user.is_superuser}) |
||||
|
||||
@login_required |
||||
def lista_movimientos(request, cuenta_id): |
||||
cuenta = get_object_or_404(Cuenta, id=cuenta_id) # Obtiene la cuenta o muestra 404 |
||||
movimientos = Movimientos.objects.filter(cuentas=cuenta) # Filtra por cuenta |
||||
|
||||
return render(request, 'lista_movimientos.html', { |
||||
'movimientos': movimientos, |
||||
'cuenta': cuenta # Para mostrar el nombre en el template |
||||
}) |
||||
|
||||
@login_required |
||||
def exportar_movimientos_excel(request, cuenta_id): |
||||
# Obtener la cuenta |
||||
cuenta = get_object_or_404(Cuenta, id=cuenta_id) |
||||
|
||||
# Obtener los movimientos de la cuenta |
||||
movimientos = Movimientos.objects.filter(cuentas=cuenta) |
||||
|
||||
# Crear el archivo de Excel |
||||
wb = openpyxl.Workbook() |
||||
ws = wb.active |
||||
ws.title = f"Movimientos de {cuenta.cuenta}" |
||||
|
||||
# Establecer el nombre de la cuenta en la primera fila |
||||
ws.append([f"Cuenta: {cuenta.cuenta}"]) |
||||
ws.append([]) # Espacio vacío antes de la tabla |
||||
|
||||
# Agregar encabezados |
||||
encabezados = ["Fecha Inserción", "Tipo de Movimiento", "N° Factura", "Fecha Factura", "Proveedor", "Responsable", "Responsable Cuenta", "Saldo"] |
||||
ws.append(encabezados) |
||||
|
||||
# Agregar datos de los movimientos |
||||
for mov in movimientos: |
||||
ws.append([ |
||||
mov.fecha_insersion.strftime('%Y-%m-%d'), |
||||
mov.tipo_movimiento.tipo_movimiento, # Asegúrate de que el modelo tenga 'nombre' |
||||
mov.n_factura, |
||||
mov.fecha_factura.strftime('%Y-%m-%d') if mov.fecha_factura else '', |
||||
mov.proveedor, |
||||
mov.responsable.username, |
||||
mov.responsable_cuenta, |
||||
mov.saldo |
||||
]) |
||||
|
||||
# Crear la respuesta HTTP con el archivo Excel |
||||
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') |
||||
response['Content-Disposition'] = f'attachment; filename="Movimientos_{cuenta.cuenta}.xlsx"' |
||||
wb.save(response) |
||||
|
||||
return response |
||||
Loading…
Reference in new issue