4 Commits

Author SHA1 Message Date
Cristiano f54f5e02dc Last changes before exam 2026-06-09 20:11:33 +02:00
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
20 changed files with 284 additions and 104 deletions
@@ -12,7 +12,7 @@ public class UpdateDelivererEndpoint(DeliverersRepository deliverersRepository,
public override void Configure() public override void Configure()
{ {
Put("/deliverers/{@Id}", x => new { x.Id }); Put("/deliverers/{@Id}", x => new { x.Id });
Roles("Admin"); Roles("Admin", "Employe");
} }
public override async Task HandleAsync(UpdateDelivererDto req, CancellationToken ct) public override async Task HandleAsync(UpdateDelivererDto req, CancellationToken ct)
@@ -3,12 +3,17 @@ using FastEndpoints;
using PyroFetes.DTO.DeliveryNote.Request; using PyroFetes.DTO.DeliveryNote.Request;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
using PyroFetes.Services.Pdf; using PyroFetes.Services.Pdf;
using PyroFetes.Specifications.DeliveryNotes; using PyroFetes.Specifications.DeliveryNotes;
namespace PyroFetes.Endpoints.DeliveryNotes; namespace PyroFetes.Endpoints.DeliveryNotes;
public class GetDeliveryNotePdfEndpoint(DeliveryNotesRepository deliveryNotesRepository, IDeliveryNotePdfService deliveryNotePdfService, SettingsRepository settingsRepository) public class GetDeliveryNotePdfEndpoint(
DeliveryNotesRepository deliveryNotesRepository,
DeliveryNotePdfService deliveryNotePdfService,
SettingsRepository settingsRepository,
StorageService storageService)
: Endpoint<GetDeliveryNotePdfDto, byte[]> : Endpoint<GetDeliveryNotePdfDto, byte[]>
{ {
public override void Configure() public override void Configure()
@@ -31,7 +36,7 @@ public class GetDeliveryNotePdfEndpoint(DeliveryNotesRepository deliveryNotesRep
return; return;
} }
byte[] bytes = deliveryNotePdfService.Generate(deliveryNote, deliveryNote.ProductDeliveries!, setting!); byte[] bytes = await deliveryNotePdfService.Generate(deliveryNote, setting!, storageService);
await Send.BytesAsync( await Send.BytesAsync(
bytes: bytes, bytes: bytes,
@@ -3,6 +3,7 @@ using FastEndpoints;
using PyroFetes.DTO.PurchaseOrder.Request; using PyroFetes.DTO.PurchaseOrder.Request;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
using PyroFetes.Services.Pdf; using PyroFetes.Services.Pdf;
using PyroFetes.Specifications.PurchaseOrders; using PyroFetes.Specifications.PurchaseOrders;
@@ -10,8 +11,9 @@ namespace PyroFetes.Endpoints.PurchaseOrders;
public class GetPurchaseOrderPdfEndpoint( public class GetPurchaseOrderPdfEndpoint(
PurchaseOrdersRepository purchaseOrdersRepository, PurchaseOrdersRepository purchaseOrdersRepository,
IPurchaseOrderPdfService purchaseOrderPdfService, PurchaseOrderPdfService purchaseOrderPdfService,
SettingsRepository settingsRepository) SettingsRepository settingsRepository,
StorageService storageService)
: Endpoint<GetPurchaseOrderPdfDto, byte[]> : Endpoint<GetPurchaseOrderPdfDto, byte[]>
{ {
public override void Configure() public override void Configure()
@@ -33,7 +35,7 @@ public class GetPurchaseOrderPdfEndpoint(
Setting? setting = await settingsRepository.FirstOrDefaultAsync(ct); Setting? setting = await settingsRepository.FirstOrDefaultAsync(ct);
byte[] bytes = purchaseOrderPdfService.Generate(purchaseOrder, purchaseOrder.PurchaseProducts!, setting!); byte[] bytes = await purchaseOrderPdfService.Generate(purchaseOrder, setting!, storageService);
await Send.BytesAsync( await Send.BytesAsync(
bytes: bytes, bytes: bytes,
@@ -16,7 +16,7 @@ public class DeleteProductFromQuotationEndpoint(QuotationProductsRepository quot
public override void Configure() public override void Configure()
{ {
Delete("/quotations/{@ProductId}/{@QuotationId}", x => new { x.ProductId, x.QuotationId }); Delete("/quotations/{@ProductId}/{@QuotationId}", x => new { x.ProductId, x.QuotationId });
Roles("Admin"); Roles("Admin", "Employe");
} }
public override async Task HandleAsync(DeleteQuotationProductRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteQuotationProductRequest req, CancellationToken ct)
@@ -3,6 +3,7 @@ using FastEndpoints;
using PyroFetes.DTO.Quotation.Request; using PyroFetes.DTO.Quotation.Request;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
using PyroFetes.Services.Pdf; using PyroFetes.Services.Pdf;
using PyroFetes.Specifications.Quotations; using PyroFetes.Specifications.Quotations;
@@ -10,8 +11,9 @@ namespace PyroFetes.Endpoints.Quotations;
public class GetQuotationPdfEndpoint( public class GetQuotationPdfEndpoint(
QuotationsRepository quotationRepository, QuotationsRepository quotationRepository,
IQuotationPdfService quotationPdfService, QuotationPdfService quotationPdfService,
SettingsRepository settingsRepository) SettingsRepository settingsRepository,
StorageService storageService)
: Endpoint<GetQuotationPdfDto, byte[]> : Endpoint<GetQuotationPdfDto, byte[]>
{ {
public override void Configure() public override void Configure()
@@ -33,7 +35,7 @@ public class GetQuotationPdfEndpoint(
Setting? setting = await settingsRepository.FirstOrDefaultAsync(ct); Setting? setting = await settingsRepository.FirstOrDefaultAsync(ct);
byte[] bytes = quotationPdfService.Generate(quotation, quotation.QuotationProducts!, setting!); byte[] bytes = await quotationPdfService.Generate(quotation, setting!, storageService);
await Send.BytesAsync( await Send.BytesAsync(
bytes: bytes, bytes: bytes,
@@ -2,10 +2,11 @@
using PyroFetes.DTO.SettingDTO.Response; using PyroFetes.DTO.SettingDTO.Response;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
namespace PyroFetes.Endpoints.Settings; 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() public override void Configure()
{ {
@@ -23,6 +24,13 @@ public class GetSettingEndpoint(SettingsRepository settingsRepository, AutoMappe
return; return;
} }
await Send.OkAsync(mapper.Map<GetSettingDto>(setting), ct); GetSettingDto settingDto = new()
{
Id = setting.Id,
ElectronicSignature = storageService.GetUrl(setting.ElectronicSignature!),
Logo = storageService.GetUrl(setting.Logo!)
};
await Send.OkAsync(settingDto, ct);
} }
} }
@@ -2,10 +2,11 @@
using PyroFetes.DTO.SettingDTO.Request; using PyroFetes.DTO.SettingDTO.Request;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
namespace PyroFetes.Endpoints.Settings; namespace PyroFetes.Endpoints.Settings;
public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settingsRepository) : Endpoint<PatchSettingElectronicSignatureDto> public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settingsRepository, StorageService storageService) : Endpoint<PatchSettingElectronicSignatureDto>
{ {
public override void Configure() public override void Configure()
{ {
@@ -25,12 +26,8 @@ public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settings
return; return;
} }
// Encodage en base64 string key = await storageService.UploadFile(req.ElectronicSignature!, "electronicSignature", ct);
using MemoryStream memoryStream = new(); setting.ElectronicSignature = key;
if (req.ElectronicSignature != null) await req.ElectronicSignature.CopyToAsync(memoryStream, ct);
byte[] signatureBytes = memoryStream.ToArray();
setting.ElectronicSignature = Convert.ToBase64String(signatureBytes);
await settingsRepository.SaveChangesAsync(ct); await settingsRepository.SaveChangesAsync(ct);
await Send.NoContentAsync(ct); await Send.NoContentAsync(ct);
@@ -2,10 +2,11 @@
using PyroFetes.DTO.SettingDTO.Request; using PyroFetes.DTO.SettingDTO.Request;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
namespace PyroFetes.Endpoints.Settings; namespace PyroFetes.Endpoints.Settings;
public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository) : Endpoint<PatchSettingLogoDto> public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository, StorageService storageService) : Endpoint<PatchSettingLogoDto>
{ {
public override void Configure() public override void Configure()
{ {
@@ -24,12 +25,9 @@ public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository) : E
return; return;
} }
// Encodage en base64 string key = await storageService.UploadFile(req.Logo!, "logo", ct);
using MemoryStream memoryStream = new();
if (req.Logo != null) await req.Logo.CopyToAsync(memoryStream, ct);
byte[] logoBytes = memoryStream.ToArray();
setting.Logo = Convert.ToBase64String(logoBytes); setting.Logo = key;
await settingsRepository.SaveChangesAsync(ct); await settingsRepository.SaveChangesAsync(ct);
await Send.NoContentAsync(ct); await Send.NoContentAsync(ct);
+32 -3
View File
@@ -1,3 +1,5 @@
using Amazon.S3;
using Amazon.S3.Model;
using AutoMapper; using AutoMapper;
using AutoMapper.EquivalencyExpression; using AutoMapper.EquivalencyExpression;
using PyroFetes; using PyroFetes;
@@ -7,6 +9,7 @@ using FastEndpoints.Security;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using PyroFetes.MappingProfiles; using PyroFetes.MappingProfiles;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Services;
using PyroFetes.Services.Pdf; using PyroFetes.Services.Pdf;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -53,9 +56,10 @@ builder.Services.AddScoped<WareHouseRepository>();
builder.Services.AddScoped<CustomersRepository>(); builder.Services.AddScoped<CustomersRepository>();
// Ajout des services // Ajout des services
builder.Services.AddScoped<IDeliveryNotePdfService, DeliveryNotePdfService>(); builder.Services.AddScoped<DeliveryNotePdfService>();
builder.Services.AddScoped<IPurchaseOrderPdfService, PurchaseOrderPdfService>(); builder.Services.AddScoped<PurchaseOrderPdfService>();
builder.Services.AddScoped<IQuotationPdfService, QuotationPdfService>(); builder.Services.AddScoped<QuotationPdfService>();
builder.Services.AddScoped<StorageService>();
MapperConfiguration mappingConfig = new(mc => MapperConfiguration mappingConfig = new(mc =>
{ {
@@ -68,6 +72,31 @@ MapperConfiguration mappingConfig = new(mc =>
AutoMapper.IMapper mapper = mappingConfig.CreateMapper(); AutoMapper.IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper); 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 // On construit l'application en lui donnant vie
WebApplication app = builder.Build(); WebApplication app = builder.Build();
app.UseAuthentication() app.UseAuthentication()
+1
View File
@@ -10,6 +10,7 @@
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" /> <PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
<PackageReference Include="AutoMapper" Version="15.0.1" /> <PackageReference Include="AutoMapper" Version="15.0.1" />
<PackageReference Include="AutoMapper.Collection" Version="11.0.0" /> <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="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="FastEndpoints" Version="7.0.1" /> <PackageReference Include="FastEndpoints" Version="7.0.1" />
<PackageReference Include="FastEndpoints.Security" Version="7.0.1" /> <PackageReference Include="FastEndpoints.Security" Version="7.0.1" />
+1 -1
View File
@@ -64,7 +64,7 @@ public class PyroFetesDbContext : DbContext
{ {
string connectionString = string connectionString =
"Server=romaric-thibault.fr;" + "Server=romaric-thibault.fr;" +
"Database=PyroFetes-Sujet2;" + "Database=PyroFetes-Sujet2-Cristiano;" +
"User Id=pyrofetes;" + "User Id=pyrofetes;" +
"Password=Crablike8-Fringe-Swimmable;" + "Password=Crablike8-Fringe-Swimmable;" +
"TrustServerCertificate=true;"; "TrustServerCertificate=true;";
@@ -5,17 +5,14 @@ using QuestPDF.Infrastructure;
namespace PyroFetes.Services.Pdf; namespace PyroFetes.Services.Pdf;
public interface IDeliveryNotePdfService public class DeliveryNotePdfService
{ {
byte[] Generate(DeliveryNote deliveryNote, List<ProductDelivery> lignes, Setting setting); private static readonly HttpClient HttpClient = new();
}
public class DeliveryNotePdfService : IDeliveryNotePdfService public async Task<byte[]> Generate(DeliveryNote deliveryNote, Setting setting, StorageService storageService)
{ {
public byte[] Generate(DeliveryNote deliveryNote, List<ProductDelivery> lignes, Setting setting) byte[] logoBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.Logo!));
{ byte[] signatureBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.ElectronicSignature!));
byte[] logo = Convert.FromBase64String(setting.Logo!);
byte[] signature = Convert.FromBase64String(setting.ElectronicSignature!);
decimal total = 0; decimal total = 0;
int totalQuantity = 0; int totalQuantity = 0;
Document document = Document.Create(container => Document document = Document.Create(container =>
@@ -48,7 +45,7 @@ public class DeliveryNotePdfService : IDeliveryNotePdfService
// Logo + société à droite // Logo + société à droite
row.ConstantItem(200).Column(col => row.ConstantItem(200).Column(col =>
{ {
col.Item().AlignRight().Height(70).Image(logo, ImageScaling.FitArea); col.Item().AlignRight().Height(70).Image(logoBytes, ImageScaling.FitArea);
col.Item().Height(20); col.Item().Height(20);
col.Item().AlignRight().Text("Pyro-Fêtes").SemiBold(); col.Item().AlignRight().Text("Pyro-Fêtes").SemiBold();
col.Item().Height(5); col.Item().Height(5);
@@ -93,7 +90,7 @@ public class DeliveryNotePdfService : IDeliveryNotePdfService
header.Cell().Element(CellHeader).AlignRight().Text("Total"); header.Cell().Element(CellHeader).AlignRight().Text("Total");
}); });
foreach (ProductDelivery l in lignes) foreach (ProductDelivery l in deliveryNote.ProductDeliveries!)
{ {
decimal price = l.Product!.Prices! decimal price = l.Product!.Prices!
.FirstOrDefault(x => x.SupplierId == l.DeliveryNote!.SupplierId && x.ProductId == l.ProductId) .FirstOrDefault(x => x.SupplierId == l.DeliveryNote!.SupplierId && x.ProductId == l.ProductId)
@@ -125,7 +122,7 @@ public class DeliveryNotePdfService : IDeliveryNotePdfService
}); });
// Signature en bas à droite // Signature en bas à droite
page.Footer().AlignRight().Column(col => { col.Item().AlignRight().Height(100).Image(signature, ImageScaling.FitArea); }); page.Footer().AlignRight().Column(col => { col.Item().AlignRight().Height(100).Image(signatureBytes, ImageScaling.FitArea); });
}); });
}); });
@@ -5,17 +5,14 @@ using QuestPDF.Infrastructure;
namespace PyroFetes.Services.Pdf; namespace PyroFetes.Services.Pdf;
public interface IPurchaseOrderPdfService public class PurchaseOrderPdfService
{ {
byte[] Generate(PurchaseOrder purchaseOrder, List<PurchaseProduct> lignes, Setting setting); private static readonly HttpClient HttpClient = new();
}
public class PurchaseOrderPdfService : IPurchaseOrderPdfService public async Task<byte[]> Generate(PurchaseOrder purchaseOrder, Setting setting, StorageService storageService)
{ {
public byte[] Generate(PurchaseOrder purchaseOrder, List<PurchaseProduct> lignes, Setting setting) byte[] logoBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.Logo!));
{ byte[] signatureBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.ElectronicSignature!));
byte[] logo = Convert.FromBase64String(setting.Logo!);
byte[] signature = Convert.FromBase64String(setting.ElectronicSignature!);
int totalQuantity = 0; int totalQuantity = 0;
decimal total = 0; decimal total = 0;
Document document = Document.Create(container => Document document = Document.Create(container =>
@@ -45,7 +42,7 @@ public class PurchaseOrderPdfService : IPurchaseOrderPdfService
// Logo + société à droite // Logo + société à droite
row.ConstantItem(200).Column(col => row.ConstantItem(200).Column(col =>
{ {
col.Item().AlignRight().Height(70).Image(logo, ImageScaling.FitArea); col.Item().AlignRight().Height(70).Image(logoBytes, ImageScaling.FitArea);
col.Item().Height(20); col.Item().Height(20);
col.Item().AlignRight().Text("Pyro-Fêtes").SemiBold(); col.Item().AlignRight().Text("Pyro-Fêtes").SemiBold();
col.Item().Height(5); col.Item().Height(5);
@@ -93,7 +90,7 @@ public class PurchaseOrderPdfService : IPurchaseOrderPdfService
header.Cell().Element(CellHeader).AlignRight().Text("Total"); header.Cell().Element(CellHeader).AlignRight().Text("Total");
}); });
foreach (PurchaseProduct l in lignes) foreach (PurchaseProduct l in purchaseOrder.PurchaseProducts!)
{ {
decimal price = l.Product!.Prices! decimal price = l.Product!.Prices!
.FirstOrDefault(x => x.SupplierId == l.PurchaseOrder!.SupplierId && x.ProductId == l.ProductId) .FirstOrDefault(x => x.SupplierId == l.PurchaseOrder!.SupplierId && x.ProductId == l.ProductId)
@@ -141,7 +138,7 @@ public class PurchaseOrderPdfService : IPurchaseOrderPdfService
}); });
// Signature en bas à droite // Signature en bas à droite
page.Footer().AlignRight().Column(col => { col.Item().AlignRight().Height(100).Image(signature, ImageScaling.FitArea); }); page.Footer().AlignRight().Column(col => { col.Item().AlignRight().Height(100).Image(signatureBytes, ImageScaling.FitArea); });
}); });
}); });
+8 -11
View File
@@ -5,17 +5,14 @@ using QuestPDF.Infrastructure;
namespace PyroFetes.Services.Pdf; namespace PyroFetes.Services.Pdf;
public interface IQuotationPdfService public class QuotationPdfService
{ {
byte[] Generate(Quotation quotation, List<QuotationProduct> lignes, Setting setting); private static readonly HttpClient HttpClient = new();
}
public class QuotationPdfService : IQuotationPdfService public async Task<byte[]> Generate(Quotation quotation, Setting setting, StorageService storageService)
{ {
public byte[] Generate(Quotation quotation, List<QuotationProduct> lignes, Setting setting) byte[] logoBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.Logo!));
{ byte[] signatureBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.ElectronicSignature!));
byte[] logo = Convert.FromBase64String(setting.Logo!);
byte[] signature = Convert.FromBase64String(setting.ElectronicSignature!);
decimal total = 0; decimal total = 0;
Document document = Document.Create(container => Document document = Document.Create(container =>
{ {
@@ -42,7 +39,7 @@ public class QuotationPdfService : IQuotationPdfService
// Logo + société à droite // Logo + société à droite
row.ConstantItem(200).Column(col => row.ConstantItem(200).Column(col =>
{ {
col.Item().AlignRight().Height(70).Image(logo, ImageScaling.FitArea); col.Item().AlignRight().Height(70).Image(logoBytes, ImageScaling.FitArea);
col.Item().Height(20); col.Item().Height(20);
col.Item().AlignRight().Text("Pyro-Fêtes").SemiBold(); col.Item().AlignRight().Text("Pyro-Fêtes").SemiBold();
col.Item().Height(5); col.Item().Height(5);
@@ -90,7 +87,7 @@ public class QuotationPdfService : IQuotationPdfService
header.Cell().Element(CellHeader).AlignRight().Text("Total"); header.Cell().Element(CellHeader).AlignRight().Text("Total");
}); });
foreach (QuotationProduct l in lignes) foreach (QuotationProduct l in quotation.QuotationProducts!)
{ {
decimal price = l.Product!.Prices! decimal price = l.Product!.Prices!
.FirstOrDefault(x => x.SupplierId == l.Quotation!.SupplierId && x.ProductId == l.ProductId) .FirstOrDefault(x => x.SupplierId == l.Quotation!.SupplierId && x.ProductId == l.ProductId)
@@ -134,7 +131,7 @@ public class QuotationPdfService : IQuotationPdfService
}); });
// Signature en bas à droite // Signature en bas à droite
page.Footer().AlignRight().Column(col => { col.Item().AlignRight().Height(100).Image(signature, ImageScaling.FitArea); }); page.Footer().AlignRight().Column(col => { col.Item().AlignRight().Height(100).Image(signatureBytes, ImageScaling.FitArea); });
}); });
}); });
+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}/{Guid.NewGuid()}";
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", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "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**. > Application web de gestion des stocks, fournisseurs, devis, bons de commande et bons de livraison pour l'entreprise **PyroFêtes**.
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.
---
## ✨ Fonctionnalités principales ## Sommaire
### 1️⃣ Suivi et réapprovisionnement des stocks - [Contexte](#contexte)
- Définissez un **niveau minimal de stock** pour chaque produit. - [Fonctionnalités](#fonctionnalités)
- Surveillez les **niveaux en temps réel** grâce à une interface claire. - [Stack technique](#stack-technique)
- Lorsquun produit atteint ou descend sous son seuil minimal, le système **génère automatiquement un bon de commande** pour le réapprovisionner. - [É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 ## Contexte
- 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.
### 4️⃣ Suivi des livraisons 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 :
- **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é.
--- - 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 ### Gestion des stocks
- **Mathys** - Définition de seuils minimaux par produit
- **Enzo** - Visualisation en temps réel du stock courant
- **Cristiano** - Alertes automatiques en cas de stock sous le seuil
- **Arsène** - 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 ### Devis & Bons de commande
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. - 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