3 Commits

Author SHA1 Message Date
sanchezvem d17f2fb23e Implemented rustfs in app 2026-06-05 11:52:49 +01:00
sanchezvem 697e1431d9 Added validators to managed users 2026-06-01 10:55:32 +01:00
sanchezvem de6a1c5385 Actualiser README.md 2026-05-30 14:25:02 +02:00
13 changed files with 236 additions and 56 deletions
@@ -12,7 +12,7 @@ public class UpdateDelivererEndpoint(DeliverersRepository deliverersRepository,
public override void Configure()
{
Put("/deliverers/{@Id}", x => new { x.Id });
Roles("Admin");
Roles("Admin", "Employe");
}
public override async Task HandleAsync(UpdateDelivererDto req, CancellationToken ct)
@@ -16,7 +16,7 @@ public class DeleteProductFromQuotationEndpoint(QuotationProductsRepository quot
public override void Configure()
{
Delete("/quotations/{@ProductId}/{@QuotationId}", x => new { x.ProductId, x.QuotationId });
Roles("Admin");
Roles("Admin", "Employe");
}
public override async Task HandleAsync(DeleteQuotationProductRequest req, CancellationToken ct)
@@ -2,10 +2,11 @@
using PyroFetes.DTO.SettingDTO.Response;
using PyroFetes.Models;
using PyroFetes.Repositories;
using PyroFetes.Services;
namespace PyroFetes.Endpoints.Settings;
public class GetSettingEndpoint(SettingsRepository settingsRepository, AutoMapper.IMapper mapper) : EndpointWithoutRequest<GetSettingDto>
public class GetSettingEndpoint(SettingsRepository settingsRepository, StorageService storageService) : EndpointWithoutRequest<GetSettingDto>
{
public override void Configure()
{
@@ -22,7 +23,14 @@ public class GetSettingEndpoint(SettingsRepository settingsRepository, AutoMappe
await Send.NotFoundAsync(ct);
return;
}
GetSettingDto settingDto = new()
{
Id = setting.Id,
ElectronicSignature = storageService.GetUrl(setting.ElectronicSignature!),
Logo = storageService.GetUrl(setting.Logo!)
};
await Send.OkAsync(mapper.Map<GetSettingDto>(setting), ct);
await Send.OkAsync(settingDto, ct);
}
}
@@ -2,10 +2,11 @@
using PyroFetes.DTO.SettingDTO.Request;
using PyroFetes.Models;
using PyroFetes.Repositories;
using PyroFetes.Services;
namespace PyroFetes.Endpoints.Settings;
public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settingsRepository) : Endpoint<PatchSettingElectronicSignatureDto>
public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settingsRepository, StorageService storageService) : Endpoint<PatchSettingElectronicSignatureDto>
{
public override void Configure()
{
@@ -24,14 +25,10 @@ public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settings
await Send.NotFoundAsync(ct);
return;
}
// Encodage en base64
using MemoryStream memoryStream = new();
if (req.ElectronicSignature != null) await req.ElectronicSignature.CopyToAsync(memoryStream, ct);
byte[] signatureBytes = memoryStream.ToArray();
setting.ElectronicSignature = Convert.ToBase64String(signatureBytes);
string key = await storageService.UploadFile(req.ElectronicSignature!, "electronicSignature", ct);
setting.ElectronicSignature = key;
await settingsRepository.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
}
@@ -2,10 +2,11 @@
using PyroFetes.DTO.SettingDTO.Request;
using PyroFetes.Models;
using PyroFetes.Repositories;
using PyroFetes.Services;
namespace PyroFetes.Endpoints.Settings;
public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository) : Endpoint<PatchSettingLogoDto>
public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository, StorageService storageService) : Endpoint<PatchSettingLogoDto>
{
public override void Configure()
{
@@ -24,12 +25,9 @@ public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository) : E
return;
}
// Encodage en base64
using MemoryStream memoryStream = new();
if (req.Logo != null) await req.Logo.CopyToAsync(memoryStream, ct);
byte[] logoBytes = memoryStream.ToArray();
string key = await storageService.UploadFile(req.Logo!, "logo", ct);
setting.Logo = Convert.ToBase64String(logoBytes);
setting.Logo = key;
await settingsRepository.SaveChangesAsync(ct);
await Send.NoContentAsync(ct);
+29
View File
@@ -1,3 +1,5 @@
using Amazon.S3;
using Amazon.S3.Model;
using AutoMapper;
using AutoMapper.EquivalencyExpression;
using PyroFetes;
@@ -7,6 +9,7 @@ using FastEndpoints.Security;
using Microsoft.Net.Http.Headers;
using PyroFetes.MappingProfiles;
using PyroFetes.Repositories;
using PyroFetes.Services;
using PyroFetes.Services.Pdf;
using QuestPDF.Infrastructure;
@@ -56,6 +59,7 @@ builder.Services.AddScoped<CustomersRepository>();
builder.Services.AddScoped<IDeliveryNotePdfService, DeliveryNotePdfService>();
builder.Services.AddScoped<IPurchaseOrderPdfService, PurchaseOrderPdfService>();
builder.Services.AddScoped<IQuotationPdfService, QuotationPdfService>();
builder.Services.AddScoped<StorageService>();
MapperConfiguration mappingConfig = new(mc =>
{
@@ -68,6 +72,31 @@ MapperConfiguration mappingConfig = new(mc =>
AutoMapper.IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
// RUSTFS
IConfigurationSection config = builder.Configuration.GetSection("RustFS");
AmazonS3Client s3Client = new(
config["AccessKey"],
config["SecretKey"],
new AmazonS3Config
{
ServiceURL = config["ServiceUrl"],
ForcePathStyle = true
}
);
ListBucketsResponse? buckets = await s3Client.ListBucketsAsync();
bool exist = buckets?.Buckets?.Any(x => x.BucketName == config["BucketName"]) == true;
if (!exist)
{
await s3Client.PutBucketAsync(new PutBucketRequest
{
BucketName = config["BucketName"]
});
}
builder.Services.AddSingleton<IAmazonS3>(s3Client);
// On construit l'application en lui donnant vie
WebApplication app = builder.Build();
app.UseAuthentication()
+1
View File
@@ -10,6 +10,7 @@
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
<PackageReference Include="AutoMapper" Version="15.0.1" />
<PackageReference Include="AutoMapper.Collection" Version="11.0.0" />
<PackageReference Include="AWSSDK.S3" Version="4.0.24" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="FastEndpoints" Version="7.0.1" />
<PackageReference Include="FastEndpoints.Security" Version="7.0.1" />
+37
View File
@@ -0,0 +1,37 @@
using Amazon.S3;
using Amazon.S3.Model;
namespace PyroFetes.Services;
public class StorageService(IAmazonS3 amazonS3, IConfiguration config)
{
private readonly string _bucket = config["RustFs:BucketName"]!;
private readonly string _url = config["RustFs:ServiceUrl"]!;
public async Task<string> UploadFile(IFormFile file, string type, CancellationToken ct)
{
if (file.Length == 0) throw new Exception("Fichier vide");
string key = $"settings/{type}";
using MemoryStream memoryStream = new();
await file.CopyToAsync(memoryStream, ct);
memoryStream.Position = 0;
PutObjectRequest uploadRequest = new()
{
BucketName = _bucket,
ContentType = file.ContentType,
InputStream = memoryStream,
Key = key,
};
await amazonS3.PutObjectAsync(uploadRequest, ct);
return key;
}
public string? GetUrl(string key)
{
return string.IsNullOrEmpty(key) ? null : $"{_url}/{_bucket}/{key}";
}
}
@@ -0,0 +1,23 @@
using FastEndpoints;
using FluentValidation;
using PyroFetes.DTO.User.Request;
namespace PyroFetes.Validators.Users;
public class ConnectUserDtoValidator : Validator<ConnectUserDto>
{
public ConnectUserDtoValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("Username is required")
.MaximumLength(50)
.WithMessage("Username cannot exceed 50 characters")
.MinimumLength(2)
.WithMessage("Username must exceed 2 characters");
RuleFor(x => x.Password)
.NotEmpty()
.WithMessage("Password is required");
}
}
@@ -0,0 +1,26 @@
using FastEndpoints;
using FluentValidation;
using PyroFetes.DTO.User.Request;
namespace PyroFetes.Validators.Users;
public class CreateUpdateUserDtoValidator: Validator<UpdateUserDto>
{
public CreateUpdateUserDtoValidator()
{
RuleFor(x => x.Email)
.NotEmpty()
.WithMessage("L'email est requis")
.MaximumLength(100)
.WithMessage("L'email ne doit pas dépasser plus de 100 caractères")
.EmailAddress()
.WithMessage("Adresse email invalide");
RuleFor(x => x.Password)
.NotEmpty()
.WithMessage("Le mot de passe est requis")
.MinimumLength(12)
.WithMessage("Le mot de passe doit contenir au minimum 12 caractères")
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*?[#?_!@$%^&*-])");
}
}
@@ -0,0 +1,26 @@
using FastEndpoints;
using FluentValidation;
using PyroFetes.DTO.User.Request;
namespace PyroFetes.Validators.Users;
public class CreateUserDtoValidator : Validator<CreateUserDto>
{
public CreateUserDtoValidator()
{
RuleFor(x => x.Email)
.NotEmpty()
.WithMessage("L'email est requis")
.MaximumLength(100)
.WithMessage("L'email ne doit pas dépasser plus de 100 caractères")
.EmailAddress()
.WithMessage("Adresse email invalide");
RuleFor(x => x.Password)
.NotEmpty()
.WithMessage("Le mot de passe est requis")
.MinimumLength(12)
.WithMessage("Le mot de passe doit contenir au minimum 12 caractères")
.Matches(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*?[#?_!@$%^&*-])");
}
}
+6
View File
@@ -4,5 +4,11 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"RustFS": {
"ServiceUrl": "https://stockage.sanchezvende.fr",
"AccessKey": "Admin_Beready_Exam2026",
"SecretKey": "4dm1n-Pr0j_2026-B3r34d7",
"BucketName": "images"
}
}
+66 -37
View File
@@ -1,50 +1,79 @@
# Gestionnaire de Stocks et Commandes
# PyroFêtes — Système de gestion des stocks et des documents
Cette application web permet de **suivre les stocks**, **automatiser les commandes fournisseurs** et **gérer le cycle complet dapprovisionnement**.
Elle est conçue pour simplifier le travail des entreprises en offrant une vue en temps réel sur les produits, leurs fournisseurs et l’état des livraisons.
> Application web de gestion des stocks, fournisseurs, devis, bons de commande et bons de livraison pour l'entreprise **PyroFêtes**.
---
## ✨ Fonctionnalités principales
## Sommaire
### 1️⃣ Suivi et réapprovisionnement des stocks
- Définissez un **niveau minimal de stock** pour chaque produit.
- Surveillez les **niveaux en temps réel** grâce à une interface claire.
- Lorsquun produit atteint ou descend sous son seuil minimal, le système **génère automatiquement un bon de commande** pour le réapprovisionner.
- [Contexte](#contexte)
- [Fonctionnalités](#fonctionnalités)
- [Stack technique](#stack-technique)
- [Équipe](#équipe)
### 2️⃣ Gestion des fournisseurs
- Enregistrez les informations complètes des fournisseurs : nom, adresse, coordonnées, produits fournis, délais de livraison.
- **Associez un ou plusieurs fournisseurs** à chaque produit.
- Lorsquun bon de commande est créé, le système **propose automatiquement les fournisseurs appropriés**.
### 3️⃣ Devis et bons de commande
- Créez des **devis personnalisés** : sélection des produits, quantités, prix, ajout dun logo, message ou conditions de vente.
- **Imprimez ou exportez** vos devis au format PDF.
- Générez des **bons de commande** en quelques clics, avec personnalisation (logo, conditions dachat) et exportation en PDF.
## Contexte
### 4️⃣ Suivi des livraisons
- **Transformez un bon de commande en bon de livraison** dès lexpédition des produits par le fournisseur.
- Enregistrez toutes les informations importantes : date dexpédition, transporteur, numéro de suivi, date prévue et date effective de livraison.
- Recevez des **alertes en cas de retard**.
- Gérez la **réception des produits** et vérifiez leur conformité.
PyroFêtes cherchait à remplacer ses processus manuels de gestion des stocks et de génération de documents commerciaux par un outil centralisé. Les objectifs principaux sont :
---
- Automatiser le réapprovisionnement
- Gérer les fournisseurs et leurs conditions
- Éditer et exporter les documents commerciaux (devis, bons de commande, bons de livraison)
- Assurer un suivi fiable des livraisons et des réceptions
## 🗂️ Livrables prévus
- **Modèle de données** : diagramme de classes commun à tous les groupes.
- **Interface utilisateur** : maquettes ou prototypes interactifs.
- **Code source commenté** pour une meilleure compréhension.
- **Documentation technique** : description des fonctionnalités, architecture de lapplication et API.
---
## Fonctionnalités
## 👥 Équipe
- **Mathys**
- **Enzo**
- **Cristiano**
- **Arsène**
### Gestion des stocks
- Définition de seuils minimaux par produit
- Visualisation en temps réel du stock courant
- Alertes automatiques en cas de stock sous le seuil
- Génération automatique de bons de commande
---
### Gestion des fournisseurs
- Enregistrement des fournisseurs (nom, adresse, coordonnées, conditions)
- Gestion des délais de livraison par produit
- Association de plusieurs fournisseurs à un produit (prix + délai)
- Suggestion automatique du fournisseur le plus pertinent
## 🚀 Objectif
Fournir un outil complet pour automatiser la gestion des stocks et des commandes, réduisant les erreurs humaines, améliorant le suivi des livraisons et facilitant la communication avec les fournisseurs.
### Devis & Bons de commande
- Création de devis et bons de commande (produits, quantités, prix, remises)
- Personnalisation des documents (logo, message, conditions)
- Export au format **PDF**
### Bons de livraison & Réceptions
- Transformation d'un bon de commande validé en bon de livraison
- Enregistrement des informations de livraison (transporteur, numéro de suivi, dates)
- Alertes en cas de retard de livraison
- Gestion des réceptions avec mise à jour automatique des stocks
## Stack technique
| Couche | Technologie |
|---|---|
| **Front-end** | Angular + NG-ZORRO + Tailwind CSS |
| **Back-end** | C# / .NET |
| **API** | REST (C#) |
| **Base de données** | SQL Server |
| **Gestion des tâches** | YouTrack |
| **Versioning** | Gitea |
| **Communication** | Discord + Présentiel |
## Équipe
| Membre | Rôle |
|---|---|
| Mathys Sanchez-Vendé | Développeur |
| Enzo Norguet | Développeur |
| Cristiano Henrique Gaspar | Développeur |
| Arsène | Développeur |
**Clients :** Mr Thibault Ferrand, Mr Douguet
## Sécurité
- Authentification sécurisée
- Gestion des accès et des permissions par rôle (Commercial / Administrateur)
- Protection des données utilisateurs