Mon commit initial
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.ruff_cache/
|
||||||
|
.coverage
|
||||||
|
db.sqlite3
|
||||||
0
SGL/__init__.py
Normal file
0
SGL/__init__.py
Normal file
BIN
SGL/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
SGL/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
SGL/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/settings.cpython-310.pyc
Normal file
BIN
SGL/__pycache__/settings.cpython-310.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/settings.cpython-313.pyc
Normal file
BIN
SGL/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/urls.cpython-310.pyc
Normal file
BIN
SGL/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/urls.cpython-313.pyc
Normal file
BIN
SGL/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/views.cpython-310.pyc
Normal file
BIN
SGL/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/views.cpython-313.pyc
Normal file
BIN
SGL/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/wsgi.cpython-310.pyc
Normal file
BIN
SGL/__pycache__/wsgi.cpython-310.pyc
Normal file
Binary file not shown.
BIN
SGL/__pycache__/wsgi.cpython-313.pyc
Normal file
BIN
SGL/__pycache__/wsgi.cpython-313.pyc
Normal file
Binary file not shown.
16
SGL/asgi.py
Normal file
16
SGL/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for SGL 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.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "SGL.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
147
SGL/settings.py
Normal file
147
SGL/settings.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
"""
|
||||||
|
Django settings for SGL project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.2.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
# 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.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-3mmf81^pt+k6-^dj$n2&*kvf6m09n5i%*+ajv70(ynaxifj5fg"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"gestion_produit",
|
||||||
|
"gestion_fournisseur",
|
||||||
|
"gestion_vehicule",
|
||||||
|
"authentification",
|
||||||
|
]
|
||||||
|
|
||||||
|
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 = "SGL.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [join(BASE_DIR, "SGL", "templates")],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "SGL.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.2/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.2/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.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / "static",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
|
# Gestion de l'envoi des mails
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
|
EMAIL_HOST = "ssl0.ovh.net"
|
||||||
|
EMAIL_PORT = 465
|
||||||
|
EMAIL_USE_SSL = False
|
||||||
|
EMAIL_USE_TLS = False
|
||||||
|
EMAIL_HOST_USER = "support.it@cerfig.org"
|
||||||
|
EMAIL_HOST_PASSWORD = "Cerfig2025"
|
||||||
|
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
|
EMAIL_TIMEOUT = 10
|
||||||
|
|
||||||
|
# ALLOWED_HOSTS = ['support.cerfig.org', '41.223.50.187', 'localhost', '127.0.0.1']
|
||||||
114
SGL/templates/BASE.html
Normal file
114
SGL/templates/BASE.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/tabulator_bootstrap5.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
|
||||||
|
<title>{% block page_titre %} Tableau de bord {% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row vh-100">
|
||||||
|
<div class="col-3 bg-danger d-none d-lg-flex flex-column">
|
||||||
|
|
||||||
|
{% if user|has_group:"Administration" %}
|
||||||
|
<a href="{% url 'index' %}" class="btn btn-danger py-3 mt-4 fw-bold d-flex" style="font-size: 1.2em;">
|
||||||
|
<i class="bi bi-file-bar-graph d-block" style="width: 50px;"></i>
|
||||||
|
Tableau de bord
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user|has_group:"Employe" %}
|
||||||
|
<a href="{% url 'gestion_produit:index' %}" class="btn btn-danger py-3 mt-1 fw-bold d-flex" style="font-size: 1.2em;">
|
||||||
|
<i class="bi bi-backpack4 d-block" style="width: 50px;"></i>
|
||||||
|
Gestion des produits
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user|has_group:"Administration" %}
|
||||||
|
<a href="{% url 'gestion_fournisseur:index' %}" class="btn btn-danger py-3 mt-1 fw-bold d-flex" style="font-size: 1.2em;">
|
||||||
|
<i class="bi bi-truck d-block" style="width: 50px;"></i>
|
||||||
|
Gestion des fournisseurs
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user|has_group:"Employe" %}
|
||||||
|
<a href="{% url 'gestion_vehicule:index' %}" class="btn btn-danger py-3 mt-1 fw-bold d-flex" style="font-size: 1.2em;">
|
||||||
|
<i class="bi bi-car-front-fill d-block" style="width: 50px;"></i>
|
||||||
|
Gestion des véhicules
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'authentification:deconnexion' %}" class="btn btn-danger py-3 mt-1 fw-bold d-flex" style="font-size: 1.2em;">
|
||||||
|
<i class="bi bi-person-dash d-block" style="width: 50px;"></i>
|
||||||
|
Deconnexion
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-9" style="overflow-y: auto;">
|
||||||
|
<!-- --------------------------------------------------- Navigation mobile -->
|
||||||
|
<nav class="navbar navbar-danger bg-danger d-lg-none">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">CERFIG</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarTogglerDemo01" aria-controls="navbarTogglerDemo01" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarTogglerDemo01">
|
||||||
|
<!-- <a class="navbar-brand" href="#">Hidden brand</a> -->
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
{% if user|has_group:"Administration" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link d-flex text-white fw-bold" aria-current="page" href="{% url 'index' %}">
|
||||||
|
<i class="bi bi-backpack4 d-block" style="width: 50px;"></i>
|
||||||
|
Tableau de bord
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user|has_group:"Employe" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link d-flex text-white fw-bold" href="{% url 'gestion_produit:index' %}">
|
||||||
|
<i class="bi bi-backpack4 d-block" style="width: 50px;"></i>
|
||||||
|
Gestion des produits
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user|has_group:"Administration" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link d-flex text-white fw-bold" href="{% url 'gestion_fournisseur:index' %}" tabindex="-1" aria-disabled="true">
|
||||||
|
<i class="bi bi-truck d-block" style="width: 50px;"></i>
|
||||||
|
Gestion des fournisseurs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user|has_group:"Employe" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link d-flex text-white fw-bold" href="{% url 'gestion_vehicule:index' %}" tabindex="-1" aria-disabled="true">
|
||||||
|
<i class="bi bi-truck d-block" style="width: 50px;"></i>
|
||||||
|
Gestion des véhicules
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link d-flex text-white fw-bold" href="{% url 'authentification:deconnexion' %}" tabindex="-1" aria-disabled="true">
|
||||||
|
<i class="bi bi-person-dash d-block" style="width: 50px;"></i>
|
||||||
|
Deconnexion
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- ------------------------------------------------------ -->
|
||||||
|
{% block "contenu" %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{% static 'js\tabulator.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js\bootstrap.min.js' %}"></script>
|
||||||
|
{% block "js" %} {% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
SGL/templates/emails/email_template.html
Normal file
20
SGL/templates/emails/email_template.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Message test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="background-color: aliceblue; min-height: 100vh; width: 100%; display: flex; justify-content: center;">
|
||||||
|
<div style="background-color: white; width: 80%; margin-top: 10px; border-radius: 15px; padding: 10px;">
|
||||||
|
<img src="téléchargement.png" alt="Logo cerfig" style="display: block; margin: auto;">
|
||||||
|
<h1 style="text-align: center;">Réception de nouveau produit</h1>
|
||||||
|
<p>
|
||||||
|
Bonjour M. Sylla Soriba, la livraison de nouveaux ordinateurs au CERFIG a été effectuée
|
||||||
|
avec succès. Merci à vous
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
SGL/templates/emails/téléchargement.png
Normal file
BIN
SGL/templates/emails/téléchargement.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
SGL/templates/emails/téléchargement.png:Zone.Identifier
Normal file
BIN
SGL/templates/emails/téléchargement.png:Zone.Identifier
Normal file
Binary file not shown.
206
SGL/templates/index.html
Normal file
206
SGL/templates/index.html
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
|
||||||
|
{% block page_titre %} Tableau de bord {% endblock %}
|
||||||
|
{% block "contenu" %}
|
||||||
|
<div class="row p-2 d-flex justify-content-around">
|
||||||
|
<div class="col-5 col-lg-3 border border-3 rounded border-danger p-2 d-flex" style="cursor: pointer;" id="produit_rupture">
|
||||||
|
<img src="{% static 'images/stock-rupture.png' %}" alt="" style="width: 30%; height: 75%;">
|
||||||
|
<div class="d-flex justify-content-center flex-column ms-1">
|
||||||
|
<h3 class="text-center fw-bold">Rupture</h3>
|
||||||
|
<p class="text-center">{{ nombre_rupture }} produits</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-5 col-lg-3 border border-3 rounded border-warning p-2 d-flex" style="cursor: pointer;" id="produit_pre_rupture">
|
||||||
|
<img src="{% static 'images/stock-prerupture.png' %}" alt="" style="width: 30%; height: 75%;">
|
||||||
|
<div class="d-flex justify-content-center flex-column ms-1">
|
||||||
|
<h3 class="text-center fw-bold">Pré-rupture</h3>
|
||||||
|
<p class="text-center">{{ nombre_pre_rupture }} produits</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-5 col-lg-3 mt-2 mt-lg-0 border border-3 rounded border-success p-2 d-flex" style="cursor: pointer;" id="produit_normal">
|
||||||
|
<img src="{% static 'images/stock-bon.png' %}" alt="" style="width: 30%; height: 75%;">
|
||||||
|
<div class="d-flex justify-content-center flex-column ms-1">
|
||||||
|
<h3 class="text-center fw-bold">Correct</h3>
|
||||||
|
<p class="text-center">{{ nombre_normal }} produits</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary m-auto d-block" data-bs-toggle="modal" data-bs-target="#aRendreModal">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
Equipement à rendre ...
|
||||||
|
</button>
|
||||||
|
<div class="row mt-5 d-flex flex-column flex-lg-row">
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<h5 class="text-center">Demandes de produit ({{ nb_demande }})</h5>
|
||||||
|
<div id="demande_produit" data-url="{% url 'demande_en_cours' %}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-6 mt-3 mt-lg-0">
|
||||||
|
<h5 class="text-center">Commandes en attentes ({{ nb_commande }})</h5>
|
||||||
|
<div id="commande_attentes" data-url="{% url 'commande_en_cours' %}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ------------- Modal pour la liste des demandes en cours ---------------------- -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="listeDemandesModal" tabindex="-1" aria-labelledby="listeDemandesModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="listeDemandesModalLabel">Demande de produit</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="produit" class="form-label">Demandeur :</label>
|
||||||
|
<input type="text" class="form-control" id="demandeur" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="produit" class="form-label">Produit</label>
|
||||||
|
<input type="text" class="form-control" id="produit" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="quantite" class="form-label">Quantité</label>
|
||||||
|
<input type="number" class="form-control" id="quantite" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="motifs" class="form-label">Motifs</label>
|
||||||
|
<textarea class="form-control" id="motifs" rows="3" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="date_demande" class="form-label">Date de mise à disposition souhaitée :</label>
|
||||||
|
<input type="date" class="form-control" id="date_demande" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="a_rendre" class="form-label">À rendre ?</label>
|
||||||
|
<input type="checkbox" class="form-check-input" id="a_rendre" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="">Valider par le supérieur hiérarchique</label>
|
||||||
|
<input type="checkbox" class="form-check-input" id="valider_par_chef" disabled>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h6 class="text-center">Décision de la logistique</h6>
|
||||||
|
<div class="d-flex justify-content-around mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="radioDefault" id="radioValider">
|
||||||
|
<label class="form-check-label" for="radioValider">
|
||||||
|
Valider la demande
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="radioDefault" id="radioRefuser" data-url="{% url 'gestion_produit:update-demande' %}">
|
||||||
|
<label class="form-check-label" for="radioRefuser">
|
||||||
|
Refuser la demande
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 d-none" id="box-motif-refus">
|
||||||
|
<label for="motifs_refus" class="form-label">Motifs de refus</label>
|
||||||
|
<textarea class="form-control" id="motifs_refus" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="enregistrerModifs">Enregistrer les modifications</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ------------- Modal pour la liste des commandes en cours ---------------------- -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="listeCommandesModal" tabindex="-1" aria-labelledby="listeCommandesModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="listeCommandesModalLabel">Commande de produit</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="produit" class="form-label">Produit</label>
|
||||||
|
<input type="text" class="form-control" id="produitCommande" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="quantite" class="form-label">Fournisseur :</label>
|
||||||
|
<input type="text" class="form-control" id="fournisseur" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="motifs" class="form-label">Quantité</label>
|
||||||
|
<input class="form-control" id="quantiteCommande" readonly></input>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="date_commande" class="form-label">Date de commande</label>
|
||||||
|
<input type="date" class="form-control" id="date_commande" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="date_livraison" class="form-label">Date de Livraison</label>
|
||||||
|
<input type="date" class="form-control" id="date_livraison" readonly>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h6 class="text-center">Confirmation de la livraison du produit</h6>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="produit_livre">
|
||||||
|
<label class="form-check-label" for="produit_livre">Le produit a été livré</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="enregistrerModifsCommande" data-url="{% url 'enregistrer_livraison_produit' %}">Enregistrer les modifications</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ------------------------------------- Modal pour stock en rupture ------------------------------ -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="produitRuptureModal" tabindex="-1" aria-labelledby="produitRuptureModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="produitRuptureModalLabel">Produit en rupture</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="tableau-produit-rupture" data-url="{% url 'listeProduitRupture' %}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ----------------------------------- Modal équipement à rendre -------------------------------- -->
|
||||||
|
<div class="modal fade" id="aRendreModal" tabindex="-1" aria-labelledby="aRendreModalLabel">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="aRendreModalLabel">
|
||||||
|
Equipement à rendre
|
||||||
|
</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="tableau-equipement-rendre" data-url="{% url 'equipement-a-rendre' %}"></div>
|
||||||
|
</div>
|
||||||
|
{% if user|has_group:"Logisticien" %}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="equipementARendreEnregistrer" data-url="{% url 'update-equipement-a-rendre' %}">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block "js" %}
|
||||||
|
<script src="{% static 'js\index.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
68
SGL/urls.py
Normal file
68
SGL/urls.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for SGL project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.2/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'))
|
||||||
|
|
||||||
|
""" #noqa : D212
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", views.index, name="index"),
|
||||||
|
path("gestion-produit/", include("gestion_produit.urls")),
|
||||||
|
path("gestion-fournisseur/", include("gestion_fournisseur.urls")),
|
||||||
|
path("gestion-vehicule/", include("gestion_vehicule.urls")),
|
||||||
|
path("authentification/", include("authentification.urls")),
|
||||||
|
path(
|
||||||
|
"liste-commande-en-cours/",
|
||||||
|
views.liste_produit_commander,
|
||||||
|
name="commande_en_cours",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"liste-demande-en-cours/",
|
||||||
|
views.liste_demandes,
|
||||||
|
name="demande_en_cours"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"enregistrer-livraison-produit/",
|
||||||
|
views.enregistrer_livraison_produit,
|
||||||
|
name="enregistrer_livraison_produit",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"admin/",
|
||||||
|
admin.site.urls
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"dashboard-produit/",
|
||||||
|
views.liste_produit_rupture,
|
||||||
|
name="listeProduitRupture"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"equipement-a-rendre/",
|
||||||
|
views.produit_a_rendre,
|
||||||
|
name="equipement-a-rendre"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"update-equipement-a-rendre/",
|
||||||
|
views.produit_a_rendre_update,
|
||||||
|
name="update-equipement-a-rendre",
|
||||||
|
),
|
||||||
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
334
SGL/views.py
Normal file
334
SGL/views.py
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
"""Fichier views.py de gestion de la page dashboard de l'application SGL.
|
||||||
|
|
||||||
|
Principales fonctionnalités couvertes :
|
||||||
|
1- Gestion des stocks des produits (rupture, pré-rupture, normal).
|
||||||
|
2- Affichage des commandes en cours et validations des dites commandes
|
||||||
|
3- Affichage des demandes en cours et validations des dites demandes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from gestion_produit.models import Commander, Demander, Produit
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from gestion_produit.views import calcul_stock_produit
|
||||||
|
|
||||||
|
|
||||||
|
def dashboard_quantite_produit():
|
||||||
|
"""Calculez les quantités des produits en rupture, pré-rupture et normal.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
Dictionnaire contenant les nombres et les listes des produits par
|
||||||
|
catégorie.
|
||||||
|
|
||||||
|
"""
|
||||||
|
tous_les_produits = Produit.objects.all()
|
||||||
|
|
||||||
|
nombre_rupture = 0
|
||||||
|
nombre_pre_rupture = 0
|
||||||
|
nombre_normal = 0
|
||||||
|
|
||||||
|
produit_rupture = []
|
||||||
|
produit_pre_rupture = []
|
||||||
|
produit_normal = []
|
||||||
|
|
||||||
|
for produit in tous_les_produits:
|
||||||
|
stock_actuel = calcul_stock_produit(produit)
|
||||||
|
stock_actuel.pop("image")
|
||||||
|
if stock_actuel["stockActuel"] == 0:
|
||||||
|
nombre_rupture += 1
|
||||||
|
produit_rupture.append(stock_actuel)
|
||||||
|
elif stock_actuel["stockActuel"] < produit.seuil_pre_rupture:
|
||||||
|
nombre_pre_rupture += 1
|
||||||
|
produit_pre_rupture.append(stock_actuel)
|
||||||
|
else:
|
||||||
|
nombre_normal += 1
|
||||||
|
produit_normal.append(stock_actuel)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"nombre_rupture": nombre_rupture,
|
||||||
|
"nombre_pre_rupture": nombre_pre_rupture,
|
||||||
|
"nombre_normal": nombre_normal,
|
||||||
|
"produit_rupture": produit_rupture,
|
||||||
|
"produit_pre_rupture": produit_pre_rupture,
|
||||||
|
"produit_normale": produit_normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="authentification:login")
|
||||||
|
def index(request):
|
||||||
|
"""Vue pour la page d'accueil du dashboard.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
HttpResponse
|
||||||
|
La réponse HTTP contenant la page d'accueil du dashboard.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if request.user.groups.filter(name="Chauffeur").exists():
|
||||||
|
return redirect("gestion_vehicule:index")
|
||||||
|
|
||||||
|
if not request.user.groups.filter(name="Administration").exists():
|
||||||
|
return redirect("gestion_produit:index")
|
||||||
|
|
||||||
|
commandes_en_cours = Commander.objects.filter(livrer=False)
|
||||||
|
demandes_en_cours = Demander.objects.filter(
|
||||||
|
valider_par_chef=True, valider_par_logisticien=None
|
||||||
|
)
|
||||||
|
|
||||||
|
gestion_quantite_produit = dashboard_quantite_produit()
|
||||||
|
|
||||||
|
nombre_rupture = gestion_quantite_produit["nombre_rupture"]
|
||||||
|
nombre_pre_rupture = gestion_quantite_produit["nombre_pre_rupture"]
|
||||||
|
nombre_normal = gestion_quantite_produit["nombre_normal"]
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"index.html",
|
||||||
|
{
|
||||||
|
"nb_commande": len(commandes_en_cours),
|
||||||
|
"nb_demande": len(demandes_en_cours),
|
||||||
|
"nombre_rupture": nombre_rupture,
|
||||||
|
"nombre_pre_rupture": nombre_pre_rupture,
|
||||||
|
"nombre_normal": nombre_normal,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def produit_a_rendre(request): #noqa: ARG001
|
||||||
|
"""Vue pour obtenir la liste des équipements à rendre.
|
||||||
|
|
||||||
|
Cette vue est appelée via une requête AJAX et renvoie une liste
|
||||||
|
d'équipements qui doivent être rendus.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
Une réponse JSON contenant la liste des équipements à rendre.
|
||||||
|
|
||||||
|
"""
|
||||||
|
equipement_a_rendre = Demander.objects.filter(a_rendre=True, rendu=False)
|
||||||
|
liste_equipement = [
|
||||||
|
{
|
||||||
|
"id_demande": equipement.id,
|
||||||
|
"produit": equipement.produit.libelle,
|
||||||
|
"quantite": equipement.quantite,
|
||||||
|
"detenteur": (
|
||||||
|
f"{equipement.user.user.first_name} "
|
||||||
|
f"{equipement.user.user.last_name}"
|
||||||
|
),
|
||||||
|
"action": equipement.rendu,
|
||||||
|
}
|
||||||
|
for equipement in equipement_a_rendre
|
||||||
|
]
|
||||||
|
|
||||||
|
return JsonResponse(liste_equipement, safe=False)
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
@csrf_exempt
|
||||||
|
def produit_a_rendre_update(request):
|
||||||
|
"""Vue pour mettre à jour le statut des équipements à rendre.
|
||||||
|
|
||||||
|
Lorsqu'une requête POST est reçue, cette vue met à jour le statut
|
||||||
|
des équipements à rendre en fonction des données fournies dans la
|
||||||
|
requête.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
request.id_demande : int
|
||||||
|
L'identifiant de la demande d'équipement à mettre à jour.
|
||||||
|
request.action : bool
|
||||||
|
Le nouveau statut de rendu de l'équipement.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
Une réponse JSON indiquant le succès de l'opération.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if request.method == "POST":
|
||||||
|
data = json.loads(request.body)
|
||||||
|
for d in data:
|
||||||
|
demande = Demander.objects.get(id=d["id_demande"])
|
||||||
|
demande.rendu = d["action"]
|
||||||
|
demande.save()
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{"status": "success", "message": "Produits mis à jour"}
|
||||||
|
)
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def liste_produit_rupture(request):
|
||||||
|
"""Vue pour obtenir la liste des produits en fonction de leur type.
|
||||||
|
|
||||||
|
Cette vue est appelée via une requête AJAX à chaque fois que l'on clique
|
||||||
|
sur un cadran (rupture, pré-rupture, en stock normal) et renvoie une liste
|
||||||
|
de produits en fonction du type spécifié dans les paramètres de la
|
||||||
|
requête.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
request.type : str
|
||||||
|
Le statut de produits à récupérer qui sont en "rupture",
|
||||||
|
"pre_rupture" ou "normal".
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
Une réponse JSON contenant la liste des produits correspondant
|
||||||
|
au type spécifié.
|
||||||
|
|
||||||
|
"""
|
||||||
|
type_encadrer = request.GET["type"]
|
||||||
|
dashboard_gestion = dashboard_quantite_produit()
|
||||||
|
|
||||||
|
donnees_charger = []
|
||||||
|
|
||||||
|
if type_encadrer == "rupture":
|
||||||
|
donnees_charger = dashboard_gestion["produit_rupture"]
|
||||||
|
elif type_encadrer == "pre_rupture":
|
||||||
|
donnees_charger = dashboard_gestion["produit_pre_rupture"]
|
||||||
|
elif type_encadrer == "normal":
|
||||||
|
donnees_charger = dashboard_gestion["produit_normale"]
|
||||||
|
|
||||||
|
return JsonResponse(donnees_charger, safe=False)
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def liste_produit_commander(request):
|
||||||
|
"""Listez des produits commandés non encore livrés.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
Une réponse JSON contenant la liste des produits commandés
|
||||||
|
non encore livrés.
|
||||||
|
|
||||||
|
"""
|
||||||
|
commandes_en_cours = Commander.objects.filter(livrer=False)
|
||||||
|
ma_liste_commandes = [
|
||||||
|
{
|
||||||
|
"id_commande": commande.id,
|
||||||
|
"produit": commande.produit.libelle,
|
||||||
|
"quantite": commande.quantite_commander,
|
||||||
|
"date_commande": commande.date_commande,
|
||||||
|
"date_livraison": commande.date_livraison,
|
||||||
|
"fournisseur": commande.fournisseur.nom_entreprise,
|
||||||
|
"is_logisticien": (
|
||||||
|
request.user.profile.departement.
|
||||||
|
nom_departement == "Logistique"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for commande in commandes_en_cours
|
||||||
|
]
|
||||||
|
|
||||||
|
return JsonResponse(ma_liste_commandes, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
@csrf_exempt
|
||||||
|
def enregistrer_livraison_produit(request):
|
||||||
|
"""Enregistrez la livraison d'un produit commandé.
|
||||||
|
|
||||||
|
Lorsqu'une requête POST est reçue, cette vue met à jour le statut
|
||||||
|
de livraison du produit commandé en fonction de l'identifiant de la
|
||||||
|
commande fourni dans la requête.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
request.id_commande : int
|
||||||
|
L'identifiant de la commande dont la livraison doit
|
||||||
|
être enregistrée.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
Une réponse JSON indiquant le succès ou l'échec de l'opération.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if request.method == "POST":
|
||||||
|
id_commande = request.POST.get("id_commande")
|
||||||
|
try:
|
||||||
|
commande = Commander.objects.get(id=id_commande)
|
||||||
|
commande.livrer = True
|
||||||
|
commande.save()
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Livraison enregistrée avec succès."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Commander.DoesNotExist:
|
||||||
|
return JsonResponse(
|
||||||
|
{"status": "error", "message": "Commande non trouvée."}
|
||||||
|
)
|
||||||
|
return JsonResponse(
|
||||||
|
{"status": "error", "message": "Méthode non autorisée."}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def liste_demandes(request):
|
||||||
|
"""Listez des demandes en cours de validation.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
Une réponse JSON contenant la liste des demandes en cours
|
||||||
|
de validation.
|
||||||
|
|
||||||
|
"""
|
||||||
|
demandes_en_cours = Demander.objects.filter(
|
||||||
|
valider_par_chef=True, valider_par_logisticien=None
|
||||||
|
)
|
||||||
|
is_logisticien = (
|
||||||
|
request.user.profile.departement.nom_departement == "Logistique"
|
||||||
|
)
|
||||||
|
ma_liste_demandes = [
|
||||||
|
{
|
||||||
|
"id_demande": demande.id,
|
||||||
|
"demandeur": (
|
||||||
|
f"{demande.user.user.first_name} "
|
||||||
|
f"{demande.user.user.last_name}"
|
||||||
|
),
|
||||||
|
"produit": demande.produit.libelle,
|
||||||
|
"quantite": demande.quantite,
|
||||||
|
"motifs": demande.motifs,
|
||||||
|
"date_de_mise_a_dispo_souhaite": demande.date_de_mise_a_dispo_souhaite.strftime("%Y-%m-%d"),
|
||||||
|
"a_rendre": demande.a_rendre,
|
||||||
|
"valider_par_chef": demande.valider_par_chef,
|
||||||
|
"valider_par_logisticien": demande.valider_par_logisticien,
|
||||||
|
"is_logisticien": is_logisticien,
|
||||||
|
}
|
||||||
|
for demande in demandes_en_cours
|
||||||
|
]
|
||||||
|
|
||||||
|
return JsonResponse(ma_liste_demandes, safe=False)
|
||||||
16
SGL/wsgi.py
Normal file
16
SGL/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for SGL 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.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "SGL.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
authentification/__init__.py
Normal file
0
authentification/__init__.py
Normal file
BIN
authentification/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
authentification/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/admin.cpython-310.pyc
Normal file
BIN
authentification/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/admin.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/apps.cpython-310.pyc
Normal file
BIN
authentification/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/apps.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/models.cpython-310.pyc
Normal file
BIN
authentification/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/models.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/tests.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/tests.cpython-313.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/urls.cpython-310.pyc
Normal file
BIN
authentification/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/urls.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/views.cpython-310.pyc
Normal file
BIN
authentification/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/__pycache__/views.cpython-313.pyc
Normal file
BIN
authentification/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
3
authentification/admin.py
Normal file
3
authentification/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin # noqa: F401
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
authentification/apps.py
Normal file
6
authentification/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AuthentificationConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "authentification"
|
||||||
33
authentification/migrations/0001_initial.py
Normal file
33
authentification/migrations/0001_initial.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-11 07:12
|
||||||
|
|
||||||
|
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='Departement',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nom_departement', models.TextField(verbose_name='Nom du département :')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Profile',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('chef_departement', models.BooleanField(verbose_name='Cet utilisateur est-il chef de ce département ?')),
|
||||||
|
('departement', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='authentification.departement')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
authentification/migrations/__init__.py
Normal file
0
authentification/migrations/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
BIN
authentification/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
authentification/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
authentification/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
authentification/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
39
authentification/models.py
Normal file
39
authentification/models.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""Modèles pour l'authentification et la gestion des profils utilisateurs."""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
class Departement(models.Model):
|
||||||
|
"""Modèle représentant un département."""
|
||||||
|
|
||||||
|
nom_departement = models.TextField(verbose_name="Nom du département :")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Retourne une représentation en chaîne de caractères du dép."""
|
||||||
|
return self.nom_departement
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(models.Model):
|
||||||
|
"""Modèle représentant le profil utilisateur.
|
||||||
|
|
||||||
|
Il étend le modèle User de Django avec des informations supplémentaires.
|
||||||
|
En y rajoutant notamment le département de l'utilisateur et s'il en est le
|
||||||
|
chef.
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
departement = models.ForeignKey(
|
||||||
|
Departement, on_delete=models.SET_NULL, null=True, blank=True
|
||||||
|
)
|
||||||
|
chef_departement = models.BooleanField(
|
||||||
|
verbose_name="Cet utilisateur est-il chef de ce département ?"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Retourne une représentation en chaîne de caractères du profil."""
|
||||||
|
return (
|
||||||
|
f"{self.user.first_name} {self.user.last_name} "
|
||||||
|
f"({self.departement.nom_departement})"
|
||||||
|
)
|
||||||
40
authentification/templates/authentification/login.html
Normal file
40
authentification/templates/authentification/login.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
|
||||||
|
<title>Connexion: SGL</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row d-flex justify-content-center align-items-center vh-100 mt-4">
|
||||||
|
<div class="col-11 col-lg-5 border rounded p-2">
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-danger">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div style="height: 30px;">
|
||||||
|
<h4 class="text-center">Connexion</h4>
|
||||||
|
</div>
|
||||||
|
<form action="{% url 'authentification:login' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group mt-2">
|
||||||
|
<label for="username">Nom d'utilisateur :</label>
|
||||||
|
<input type="text" class="form-control" name="username">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-2">
|
||||||
|
<label for="password">Mot de passe :</label>
|
||||||
|
<input type="password" class="form-control" name="password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-3 d-block m-auto">Connexion</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
authentification/templatetags/__init__.py
Normal file
0
authentification/templatetags/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
authentification/templatetags/__pycache__/sorel.cpython-313.pyc
Normal file
BIN
authentification/templatetags/__pycache__/sorel.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
authentification/templatetags/tags_personnaliser.py
Normal file
29
authentification/templatetags/tags_personnaliser.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from django import template
|
||||||
|
from authentification.models import Profile
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def has_group(user, group_name):
|
||||||
|
return user.groups.filter(name=group_name).exists()
|
||||||
|
|
||||||
|
|
||||||
|
# On recupère l'utilisateur et le produit
|
||||||
|
## Si l'utilisateur n'est pas le chef d'un département, afficher simplement ses demandes
|
||||||
|
## Si l'utilisateur est un chef de département
|
||||||
|
### Afficher la liste de ses demandes
|
||||||
|
### Verifier pour chaque demande que l'utilisateur appartient à son departement
|
||||||
|
#### Si oui, afficher le produit
|
||||||
|
#### Si non, ne pas afficher le produit
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def affichage_demande(user, demande):
|
||||||
|
if user.profile.chef_departement:
|
||||||
|
chef_profile = Profile.objects.get(id=user.profile)
|
||||||
|
departement = chef_profile.departement
|
||||||
|
ids_profils_departement = Profile.objects.filter(departement=departement)["id"]
|
||||||
|
|
||||||
|
return demande.user_id in ids_profils_departement
|
||||||
|
return user.profile.id == demande.user_id
|
||||||
3
authentification/tests.py
Normal file
3
authentification/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
11
authentification/urls.py
Normal file
11
authentification/urls.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"""Routes pour l'authentification des utilisateurs."""
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "authentification"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("connexion", views.connexion, name="login"),
|
||||||
|
path("deconnexion", views.deconnexion, name="deconnexion"),
|
||||||
|
]
|
||||||
34
authentification/views.py
Normal file
34
authentification/views.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"""Vues pour l'authentification des utilisateurs."""
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
from SGL.views import index
|
||||||
|
|
||||||
|
|
||||||
|
def connexion(request):
|
||||||
|
"""Gérez la connexion des utilisateurs."""
|
||||||
|
authentification_echoue = False
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.POST["username"]
|
||||||
|
password = request.POST["password"]
|
||||||
|
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
login(request, user)
|
||||||
|
return redirect(index)
|
||||||
|
messages.error(request, "Nom d'utilisateur ou mot de passe incorrect.")
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"authentification/login.html",
|
||||||
|
{"authentification_echoue": authentification_echoue},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def deconnexion(request):
|
||||||
|
"""Gérez la déconnexion des utilisateurs."""
|
||||||
|
logout(request)
|
||||||
|
return redirect("authentification:login")
|
||||||
0
gestion_fournisseur/__init__.py
Normal file
0
gestion_fournisseur/__init__.py
Normal file
BIN
gestion_fournisseur/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/admin.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/admin.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/apps.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/apps.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/forms.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/forms.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/forms.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/models.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/models.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/tests.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/tests.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/urls.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/urls.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/views.cpython-310.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_fournisseur/__pycache__/views.cpython-313.pyc
Normal file
BIN
gestion_fournisseur/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
3
gestion_fournisseur/admin.py
Normal file
3
gestion_fournisseur/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin # noqa: F401
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
gestion_fournisseur/apps.py
Normal file
6
gestion_fournisseur/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GestionFournisseurConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "gestion_fournisseur"
|
||||||
34
gestion_fournisseur/forms.py
Normal file
34
gestion_fournisseur/forms.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"""Gérez les formulaires pour la gestion des fournisseurs."""
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from gestion_produit.models import Fournisseur
|
||||||
|
|
||||||
|
|
||||||
|
class FournisseurForm(forms.ModelForm):
|
||||||
|
"""Formulaire pour le modèle Fournisseur."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Métadonnées pour le formulaire FournisseurForm."""
|
||||||
|
|
||||||
|
model = Fournisseur
|
||||||
|
fields = (
|
||||||
|
"nom_entreprise",
|
||||||
|
"nom_prenom_representant",
|
||||||
|
"fonction",
|
||||||
|
"telephone",
|
||||||
|
"email",
|
||||||
|
"categorie",
|
||||||
|
"rccm",
|
||||||
|
"nifp",
|
||||||
|
)
|
||||||
|
widgets = {
|
||||||
|
"nom_entreprise": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
"nom_prenom_representant": forms.TextInput(
|
||||||
|
attrs={"class": "form-control"}
|
||||||
|
),
|
||||||
|
"fonction": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
"telephone": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||||
|
# "note": forms.NumberInput(attrs={"class": "form-control"}),
|
||||||
|
"categorie": forms.SelectMultiple(attrs={"class": "form-select"}),
|
||||||
|
}
|
||||||
0
gestion_fournisseur/migrations/__init__.py
Normal file
0
gestion_fournisseur/migrations/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7
gestion_fournisseur/models.py
Normal file
7
gestion_fournisseur/models.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""Gérez les modèles pour la gestion des fournisseurs.
|
||||||
|
|
||||||
|
Les modèles définis ici ont été déplacés vers gestion_produit.models.py
|
||||||
|
pour éviter les dépendances circulaires.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
|
||||||
|
{% block page_titre %} SGL - Gestion des fournisseurs {% endblock %}
|
||||||
|
|
||||||
|
{% block "contenu" %}
|
||||||
|
|
||||||
|
<h3 class="text-center my-3">Détails du fournisseur</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8 border rounded p-3 mx-auto">
|
||||||
|
<form action="{% url 'gestion_fournisseur:mise-a-jour-fournisseur' id_fournisseur %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">{{fournisseurForm.nom_entreprise.label}} :</label>
|
||||||
|
{{ fournisseurForm.nom_entreprise }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">{{fournisseurForm.nom_prenom_representant.label}} :</label>
|
||||||
|
{{ fournisseurForm.nom_prenom_representant }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">{{fournisseurForm.fonction.label}} :</label>
|
||||||
|
{{ fournisseurForm.fonction }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">{{fournisseurForm.telephone.label}} :</label>
|
||||||
|
{{ fournisseurForm.telephone }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">{{fournisseurForm.email.label}} :</label>
|
||||||
|
{{ fournisseurForm.email }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">{{fournisseurForm.categorie.label}} :</label>
|
||||||
|
{{ fournisseurForm.categorie }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">RCCM :</label>
|
||||||
|
<div>
|
||||||
|
Actuellement :
|
||||||
|
{% if fournisseur.rccm %}
|
||||||
|
<a href="{{ fournisseur.rccm.url }}" target="_blank">{{ fournisseur.rccm.name }}</a>
|
||||||
|
{% else %}
|
||||||
|
Aucun fichier téléchargé.
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<input type="file" name="rccm" id="rccm" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rccm" class="form-label d-block">NIFp :</label>
|
||||||
|
<div>
|
||||||
|
Actuellement :
|
||||||
|
{% if fournisseur.rccm %}
|
||||||
|
<a href="{{ fournisseur.nifp.url }}" target="_blank">{{ fournisseur.nifp.name }}</a>
|
||||||
|
{% else %}
|
||||||
|
Aucun fichier téléchargé.
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<input type="file" name="nifp" id="nifp" class="form-control">
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Mettre à jour" class="btn btn-primary mx-auto d-block">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-2 d-flex align-items-center">
|
||||||
|
<div class="col"><h4 class="my-3">Livraison du fournisseur</h4></div>
|
||||||
|
<div class="col">
|
||||||
|
<!-- <label for="">Filtrer par date :</label> -->
|
||||||
|
<input type="month" name="" id="filtreMois" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="liste-livraison" data-url = "{% url 'gestion_fournisseur:liste-livraison' id_fournisseur %}" data-idfournisseur="{{id_fournisseur}}"></div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block "js" %}
|
||||||
|
<script src="{% static 'js/details_fournisseur.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
64
gestion_fournisseur/templates/gestion_fournisseur/index.html
Normal file
64
gestion_fournisseur/templates/gestion_fournisseur/index.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
|
||||||
|
{% block page_titre %} SGL - Gestion des fournisseurs {% endblock %}
|
||||||
|
|
||||||
|
{% block "contenu" %}
|
||||||
|
|
||||||
|
<div class="row p-2">
|
||||||
|
<div class="col d-flex justify-content-between">
|
||||||
|
<h3>LISTE DES FOURNISSEURS </h3>
|
||||||
|
{% if user|has_group:"Logisticien" %}
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#enregistrerFournisseur">
|
||||||
|
<i class="bi bi-person-plus-fill"></i>
|
||||||
|
Ajouter un fournisseur
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row p-2">
|
||||||
|
<div class="col">
|
||||||
|
<input type="text" class="form-control" placeholder="Rechercher par nom du fournisseur" id="searchFournisseur">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<select name="" id="filterCategorie" class="form-select">
|
||||||
|
<option value="">Selectionner la catégorie du produit</option>
|
||||||
|
{% for categorie in listeCategorie %}
|
||||||
|
<option value="{{ categorie.id }}">{{ categorie.categorie }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="liste_fournisseur" data-url="{% url 'gestion_fournisseur:liste_fournisseur' %}"></div>
|
||||||
|
|
||||||
|
<!-- Modal ajout d'un nouveau fournisseur -->
|
||||||
|
<div class="modal fade" id="enregistrerFournisseur" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="informationFournisseurModalLabel">Information sur le fournisseur</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="{% url 'gestion_fournisseur:index' %}" method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block "js" %}
|
||||||
|
<script src="{% static 'js/gestion_fournisseur_index.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
3
gestion_fournisseur/tests.py
Normal file
3
gestion_fournisseur/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase # noqa: F401
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
33
gestion_fournisseur/urls.py
Normal file
33
gestion_fournisseur/urls.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""Gérez les URL pour la gestion des fournisseurs."""
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "gestion_fournisseur"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", views.index, name="index"),
|
||||||
|
path(
|
||||||
|
"liste-fournisseur", views.liste_fournisseurs, name="liste_fournisseur"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"liste-fournisseur-categorie",
|
||||||
|
views.liste_fournisseur_categorie,
|
||||||
|
name="liste_fournisseur_categorie",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"liste-livraison/<int:id_fournisseur>",
|
||||||
|
views.liste_livraison_fournisseur,
|
||||||
|
name="liste-livraison",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"details-fournisseur/<int:id_fournisseur>",
|
||||||
|
views.details_fournisseur,
|
||||||
|
name="details_fournisseur",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"mise-a-jour-fournisseur/<int:id_fournisseur>",
|
||||||
|
views.mise_a_jour_fournisseur,
|
||||||
|
name="mise-a-jour-fournisseur",
|
||||||
|
),
|
||||||
|
]
|
||||||
195
gestion_fournisseur/views.py
Normal file
195
gestion_fournisseur/views.py
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
"""Vuews pour la gestion des fournisseurs."""
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from gestion_produit.models import Fournisseur, Categorie, Commander
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
from .forms import FournisseurForm
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def index(request):
|
||||||
|
"""Gérez la vue index de la gestion des fournisseurs.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
HttpResponse
|
||||||
|
La réponse HTTP avec le formulaire de fournisseur.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if request.method == "POST":
|
||||||
|
form = FournisseurForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
|
||||||
|
form = FournisseurForm()
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"gestion_fournisseur/index.html",
|
||||||
|
{"form": form, "listeCategorie": Categorie.objects.all()},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def liste_fournisseurs(request): # noqa: ARG001
|
||||||
|
"""Renvoyez la liste des fournisseurs au format JSON.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
La réponse JSON contenant la liste des fournisseurs.
|
||||||
|
|
||||||
|
"""
|
||||||
|
fournisseurs = Fournisseur.objects.all().order_by("-id")
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
"id": f.id,
|
||||||
|
"nom_entreprise": f.nom_entreprise,
|
||||||
|
"representant": f.nom_prenom_representant,
|
||||||
|
"fonction": f.fonction,
|
||||||
|
"telephone": f.telephone,
|
||||||
|
"email": f.email,
|
||||||
|
"note": f.note,
|
||||||
|
}
|
||||||
|
for f in fournisseurs
|
||||||
|
]
|
||||||
|
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def liste_fournisseur_categorie(request):
|
||||||
|
"""Renvoyez la liste des fournisseurs par catégorie au format JSON.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
La réponse JSON contenant la liste des fournisseurs filtrés par
|
||||||
|
catégorie.
|
||||||
|
|
||||||
|
"""
|
||||||
|
categorie = Categorie.objects.get(id=request.GET["c"])
|
||||||
|
fournisseur_categorie = Fournisseur.objects.filter(categorie=categorie)
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
"id": f.id,
|
||||||
|
"nom_entreprise": f.nom_entreprise,
|
||||||
|
"representant": f.nom_prenom_representant,
|
||||||
|
"fonction": f.fonction,
|
||||||
|
"telephone": f.telephone,
|
||||||
|
"email": f.email,
|
||||||
|
"note": f.note,
|
||||||
|
}
|
||||||
|
for f in fournisseur_categorie
|
||||||
|
]
|
||||||
|
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="login")
|
||||||
|
def details_fournisseur(request, id_fournisseur):
|
||||||
|
"""Affichez les détails d'un fournisseur spécifique.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
id_fournisseur : int
|
||||||
|
L'identifiant du fournisseur.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
HttpResponse
|
||||||
|
La réponse HTTP avec les détails du fournisseur.
|
||||||
|
|
||||||
|
"""
|
||||||
|
fournisseur = Fournisseur.objects.get(id=id_fournisseur)
|
||||||
|
fournisseur_form = FournisseurForm(instance=fournisseur)
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"gestion_fournisseur/details_fournisseur.html",
|
||||||
|
{
|
||||||
|
"fournisseur": fournisseur,
|
||||||
|
"fournisseurForm": fournisseur_form,
|
||||||
|
"id_fournisseur": id_fournisseur,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mise_a_jour_fournisseur(request, id_fournisseur):
|
||||||
|
"""Mettez à jour les informations d'un fournisseur spécifique.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
id_fournisseur : int
|
||||||
|
L'identifiant du fournisseur.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
HttpResponse
|
||||||
|
La réponse HTTP redirigeant vers les détails du fournisseur.
|
||||||
|
|
||||||
|
"""
|
||||||
|
fournisseur = Fournisseur.objects.get(id=id_fournisseur)
|
||||||
|
if request.method == "POST":
|
||||||
|
form = FournisseurForm(
|
||||||
|
request.POST,
|
||||||
|
request.FILES,
|
||||||
|
instance=fournisseur
|
||||||
|
)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect(
|
||||||
|
"gestion_fournisseur:details_fournisseur",
|
||||||
|
id_fournisseur=id_fournisseur
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def liste_livraison_fournisseur(request, id_fournisseur): # noqa: ARG001
|
||||||
|
"""Renvoyez la liste des livraisons d'un fournisseur au format JSON.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
request : HttpRequest
|
||||||
|
La requête HTTP entrante.
|
||||||
|
id_fournisseur : int
|
||||||
|
L'identifiant du fournisseur.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
JsonResponse
|
||||||
|
La réponse JSON contenant la liste des livraisons du fournisseur.
|
||||||
|
|
||||||
|
"""
|
||||||
|
commande_fournisseur = [
|
||||||
|
{
|
||||||
|
"date": c.date_livraison,
|
||||||
|
"produit": c.produit.libelle,
|
||||||
|
"prix_achat": c.prix_achat,
|
||||||
|
"quantite": c.quantite_commander,
|
||||||
|
"montant_total": c.quantite_commander * c.prix_achat,
|
||||||
|
"date_livraison": c.date_livraison,
|
||||||
|
"statut": c.livrer,
|
||||||
|
}
|
||||||
|
for c in Commander.objects.filter(fournisseur__id=id_fournisseur)
|
||||||
|
]
|
||||||
|
return JsonResponse(commande_fournisseur, safe=False)
|
||||||
0
gestion_produit/__init__.py
Normal file
0
gestion_produit/__init__.py
Normal file
BIN
gestion_produit/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
gestion_produit/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gestion_produit/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/admin.cpython-310.pyc
Normal file
BIN
gestion_produit/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/admin.cpython-313.pyc
Normal file
BIN
gestion_produit/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/apps.cpython-310.pyc
Normal file
BIN
gestion_produit/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/apps.cpython-313.pyc
Normal file
BIN
gestion_produit/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/forms.cpython-310.pyc
Normal file
BIN
gestion_produit/__pycache__/forms.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/forms.cpython-313.pyc
Normal file
BIN
gestion_produit/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/models.cpython-310.pyc
Normal file
BIN
gestion_produit/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_produit/__pycache__/models.cpython-313.pyc
Normal file
BIN
gestion_produit/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user