29 Commits

Author SHA1 Message Date
sanchezvem 09ce53203c Merge branch 'develop' 2026-06-08 12:22:09 +01:00
sanchezvem d17f2fb23e Implemented rustfs in app 2026-06-05 11:52:49 +01:00
sanchezvem 3065bba7fb Merge branch 'develop' 2026-06-01 11:13:22 +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
sanchezvem 29759cf896 Fixed wwwroot error 2026-05-30 13:44:21 +01:00
sanchezvem da1407579d Actualiser README.md 2026-05-30 14:23:44 +02:00
sanchezvem 602b91006e Merge remote-tracking branch 'origin/develop' 2026-05-30 13:45:04 +01:00
sanchezvem f0ad9b536a Changed values of userDto 2026-05-28 15:50:57 +01:00
sanchezvem fb97729c71 Changed values of userDto 2026-05-28 15:41:07 +01:00
sanchezvem 1bd92a8732 Fix route error 2026-05-28 15:39:55 +01:00
sanchezvem 8c38255ed9 Merge remote-tracking branch 'origin/feature/refactor-backend' into feature/refactor-backend
# Conflicts:
#	PyroFetes/Endpoints/Users/GetUserEndpoint.cs
2026-05-28 15:39:04 +01:00
sanchezvem 639631a63b Changed Id value 2026-05-28 15:36:58 +01:00
Cristiano 5869ae18c4 Put roles into endpoints 2026-05-28 15:36:33 +02:00
sanchezvem fc9da89ebe Added refresh token endpoint 2026-05-28 14:05:47 +01:00
sanchezvem 76239b41bd Adapt endpoint to UX 2026-05-27 18:02:03 +01:00
sanchezvem 88882f9db8 Deleted date of delivery note dto 2026-05-27 17:48:30 +01:00
sanchezvem 6339fbdb8c Added spec for see all documents order by desc 2026-05-27 17:23:02 +01:00
sanchezvem 6f2c60e6c0 Fixed error with inclusion in pdf of quotation 2026-05-27 13:12:57 +01:00
sanchezvem cac880e35f Add endpoint to display all customers, and updated dto to create quotation and purchase order 2026-05-27 12:32:27 +01:00
sanchezvem 897b036fc5 fix error 2026-05-26 18:55:54 +01:00
sanchezvem 19c63ef317 Fixed error with update of quantity in stock page 2026-05-26 12:06:15 +01:00
sanchezvem fdaead91ff Changed display of delivery note 2026-05-26 12:01:09 +01:00
sanchezvem aa40ae2e7a Added missing mappings profiles 2026-05-26 11:43:41 +01:00
sanchezvem ed59efe4f8 Added new endpoint to manage warehouse 2026-05-26 11:16:16 +01:00
sanchezvem b13b8ebfb6 Added new endpoint to manage deliveries 2026-05-26 10:53:10 +01:00
sanchezvem 57646a1417 Fix error with spec of product under limit 2026-05-26 10:25:05 +01:00
Cristiano 7f3ffde3ff Merge branch 'develop' 2025-11-27 14:56:14 +01:00
sanchezvem 6be43958fa updated length of password to 60 for bcrypt length 2025-11-27 13:28:05 +01:00
92 changed files with 564 additions and 148 deletions
@@ -0,0 +1,7 @@
namespace PyroFetes.DTO.Customer.Response;
public class GetCustomerDto
{
public int Id { get; set; }
public string? Note { get; set; }
}
@@ -3,8 +3,6 @@ namespace PyroFetes.DTO.DeliveryNote.Request;
public class CreateDeliveryNoteDto public class CreateDeliveryNoteDto
{ {
public string? TrackingNumber { get; set; } public string? TrackingNumber { get; set; }
public DateOnly EstimateDeliveryDate { get; set; }
public DateOnly ExpeditionDate { get; set; }
public int DelivererId { get; set; } public int DelivererId { get; set; }
public int SupplierId { get; set; } public int SupplierId { get; set; }
@@ -5,5 +5,6 @@ namespace PyroFetes.DTO.PurchaseOrder.Request;
public class CreatePurchaseOrderDto public class CreatePurchaseOrderDto
{ {
public string? PurchaseConditions { get; set; } public string? PurchaseConditions { get; set; }
public int SupplierId { get; set; }
public List<CreatePurchaseOrderProductDto>? Products { get; set; } public List<CreatePurchaseOrderProductDto>? Products { get; set; }
} }
@@ -6,5 +6,7 @@ public class CreateQuotationDto
{ {
public string? Message { get; set; } public string? Message { get; set; }
public string? ConditionsSale { get; set; } public string? ConditionsSale { get; set; }
public int CustomerId { get; set; }
public int SupplierId { get; set; }
public List<CreateProductQuotationDto>? Products { get; set; } public List<CreateProductQuotationDto>? Products { get; set; }
} }
@@ -0,0 +1,6 @@
namespace PyroFetes.DTO.Refresh.Request;
public class RefreshTokenDto
{
public string? Token { get; set; }
}
@@ -0,0 +1,6 @@
namespace PyroFetes.DTO.Refresh.Response;
public class GetRefreshTokenDto
{
public string? Token { get; set; }
}
@@ -5,7 +5,6 @@ public class GetUserDto
public int Id { get; set; } public int Id { get; set; }
public string? Name { get; set; } public string? Name { get; set; }
public string? Password { get; set; } public string? Password { get; set; }
public string? Salt { get; set; }
public string? Fonction { get; set; } public string? Fonction { get; set; }
public string? Email { get; set; } public string? Email { get; set; }
} }
@@ -0,0 +1,7 @@
namespace PyroFetes.DTO.WareHouse.Response;
public class GetWareHouseDto
{
public int Id { get; set; }
public string? Name { get; set; }
}
@@ -0,0 +1,19 @@
using FastEndpoints;
using PyroFetes.DTO.Customer.Response;
using PyroFetes.Repositories;
namespace PyroFetes.Endpoints.Customers;
public class GetAllCustomersEndpoint(CustomersRepository customersRepository, AutoMapper.IMapper mapper) : EndpointWithoutRequest<List<GetCustomerDto>>
{
public override void Configure()
{
Get("/customers");
Roles("Admin","Employe");
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await customersRepository.ProjectToListAsync<GetCustomerDto>(ct), ct);
}
}
@@ -11,7 +11,7 @@ public class CreateDelivererEndpoint(DeliverersRepository deliverersRepository)
public override void Configure() public override void Configure()
{ {
Post("/deliverers"); Post("/deliverers");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreateDelivererDto req, CancellationToken ct) public override async Task HandleAsync(CreateDelivererDto req, CancellationToken ct)
@@ -15,7 +15,7 @@ public class DeleteDelivererEndpoint(DeliverersRepository deliverersRepository)
public override void Configure() public override void Configure()
{ {
Delete("/deliverers/{@Id}", x => new { x.DelivererId }); Delete("/deliverers/{@Id}", x => new { x.DelivererId });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeleteDelivererRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteDelivererRequest req, CancellationToken ct)
@@ -9,7 +9,7 @@ public class GetAllDelivererEndpoint(DeliverersRepository deliverersRepository)
public override void Configure() public override void Configure()
{ {
Get("/deliverers"); Get("/deliverers");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
@@ -16,7 +16,7 @@ public class GetDelivererEndpoint(DeliverersRepository deliverersRepository, Aut
public override void Configure() public override void Configure()
{ {
Get("/deliverers/{@Id}", x => new { x.DelivererId }); Get("/deliverers/{@Id}", x => new { x.DelivererId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetDelivererRequest req, CancellationToken ct) public override async Task HandleAsync(GetDelivererRequest req, CancellationToken ct)
@@ -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 });
AllowAnonymous(); Roles("Admin", "Employe");
} }
public override async Task HandleAsync(UpdateDelivererDto req, CancellationToken ct) public override async Task HandleAsync(UpdateDelivererDto req, CancellationToken ct)
@@ -18,7 +18,7 @@ public class CreateDeliveryNoteEndpoint(
public override void Configure() public override void Configure()
{ {
Post("/deliveryNotes"); Post("/deliveryNotes");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreateDeliveryNoteDto req, CancellationToken ct) public override async Task HandleAsync(CreateDeliveryNoteDto req, CancellationToken ct)
@@ -35,8 +35,8 @@ public class CreateDeliveryNoteEndpoint(
DeliveryNote newDeliveryNote = new() DeliveryNote newDeliveryNote = new()
{ {
TrackingNumber = req.TrackingNumber, TrackingNumber = req.TrackingNumber,
EstimateDeliveryDate = req.EstimateDeliveryDate, EstimateDeliveryDate = DateOnly.FromDateTime(DateTime.Today).AddMonths(2),
ExpeditionDate = req.ExpeditionDate, ExpeditionDate = DateOnly.FromDateTime(DateTime.Today),
DelivererId = deliverer.Id, DelivererId = deliverer.Id,
Deliverer = deliverer, Deliverer = deliverer,
SupplierId = req.SupplierId, SupplierId = req.SupplierId,
@@ -16,7 +16,7 @@ public class DeleteDeliveryNoteEndpoint(
public override void Configure() public override void Configure()
{ {
Delete("/deliveryNotes/{@Id}", x => new { x.Id }); Delete("/deliveryNotes/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeleteDeliveryNoteRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteDeliveryNoteRequest req, CancellationToken ct)
@@ -10,7 +10,7 @@ public class GetAllDeliveryNoteEndpoint(DeliveryNotesRepository deliveryNotesRep
public override void Configure() public override void Configure()
{ {
Get("/deliveryNotes"); Get("/deliveryNotes");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
@@ -0,0 +1,20 @@
using FastEndpoints;
using PyroFetes.DTO.DeliveryNote.Response;
using PyroFetes.Repositories;
using PyroFetes.Specifications.DeliveryNotes;
namespace PyroFetes.Endpoints.DeliveryNotes;
public class GetAllDeliveryNotesNotArrivedEndpoint(DeliveryNotesRepository deliveryNotesRepository) : EndpointWithoutRequest<List<GetDeliveryNoteDto>>
{
public override void Configure()
{
Get("/deliveryNotes/validation");
Roles("Admin","Employe");
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await deliveryNotesRepository.ProjectToListAsync<GetDeliveryNoteDto>(new GetAllDeliveryNotesByRealDateSpec(), ct), ct);
}
}
@@ -18,7 +18,7 @@ public class GetDeliveryNoteEndpoint(
public override void Configure() public override void Configure()
{ {
Get("/deliveryNotes/{@Id}", x => new { x.DeliveryNoteId }); Get("/deliveryNotes/{@Id}", x => new { x.DeliveryNoteId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetDeliveryNoteRequest req, CancellationToken ct) public override async Task HandleAsync(GetDeliveryNoteRequest req, CancellationToken ct)
@@ -14,7 +14,7 @@ public class GetDeliveryNotePdfEndpoint(DeliveryNotesRepository deliveryNotesRep
public override void Configure() public override void Configure()
{ {
Get("/deliveryNotes/{@Id}/pdf", x => new { x.Id }); Get("/deliveryNotes/{@Id}/pdf", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf)); Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf));
} }
@@ -13,7 +13,7 @@ public class PatchRealDeliveryDateEndpoint(
public override void Configure() public override void Configure()
{ {
Patch("/deliveryNotes/{@Id}", x => new { x.Id }); Patch("/deliveryNotes/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchDeliveryNoteRealDeliveryDateDto req, CancellationToken ct) public override async Task HandleAsync(PatchDeliveryNoteRealDeliveryDateDto req, CancellationToken ct)
@@ -11,7 +11,7 @@ public class UpdateDeliveryNoteEndpoint(DeliveryNotesRepository deliveryNotesRep
public override void Configure() public override void Configure()
{ {
Put("/deliveryNotes/{@Id}", x => new { x.Id }); Put("/deliveryNotes/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(UpdateDeliveryNoteDto req, CancellationToken ct) public override async Task HandleAsync(UpdateDeliveryNoteDto req, CancellationToken ct)
@@ -15,7 +15,8 @@ public class DeleteProductEndpoint(ProductsRepository productsRepository) : Endp
public override void Configure() public override void Configure()
{ {
Delete("/products/{@Id}", x => new { x.ProductId }); Delete("/products/{@Id}", x => new { x.ProductId });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeleteProductsRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteProductsRequest req, CancellationToken ct)
@@ -9,7 +9,7 @@ public class GetAllProductsEndpoint(ProductsRepository productsRepository) : End
public override void Configure() public override void Configure()
{ {
Get("/products"); Get("/products");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
@@ -10,7 +10,7 @@ public class GetAllProductsUnderLimitEndpoint(ProductsRepository productsReposit
public override void Configure() public override void Configure()
{ {
Get("/products/underLimit"); Get("/products/underLimit");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
@@ -18,7 +18,8 @@ public class GetProductEndpoint(
public override void Configure() public override void Configure()
{ {
Get("/products/{@Id}", x => new { x.Id }); Get("/products/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetProductRequest req, CancellationToken ct) public override async Task HandleAsync(GetProductRequest req, CancellationToken ct)
@@ -11,7 +11,7 @@ public class PatchProductMinimalStockEndpoint(ProductsRepository productsReposit
public override void Configure() public override void Configure()
{ {
Patch("/products/{@Id}/MinimalStock", x => new { x.Id }); Patch("/products/{@Id}/MinimalStock", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchProductMinimalStockDto req, CancellationToken ct) public override async Task HandleAsync(PatchProductMinimalStockDto req, CancellationToken ct)
@@ -11,7 +11,7 @@ public class UpdateProductEndpoint(ProductsRepository productsRepository, AutoMa
public override void Configure() public override void Configure()
{ {
Put("/products/{@Id}", x => new { x.Id }); Put("/products/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(UpdateProductDto req, CancellationToken ct) public override async Task HandleAsync(UpdateProductDto req, CancellationToken ct)
@@ -11,7 +11,7 @@ public class AddProductFromPurchaseOrderEndpoint(PurchaseProductsRepository purc
public override void Configure() public override void Configure()
{ {
Post("/purchaseOrders/{@PurchaseOrderId}/{@ProductId}", x => new { x.PurchaseOrderId, x.ProductId }); Post("/purchaseOrders/{@PurchaseOrderId}/{@ProductId}", x => new { x.PurchaseOrderId, x.ProductId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreatePurchaseProductDto req, CancellationToken ct) public override async Task HandleAsync(CreatePurchaseProductDto req, CancellationToken ct)
@@ -1,5 +1,6 @@
using FastEndpoints; using FastEndpoints;
using PyroFetes.DTO.PurchaseOrder.Request; using PyroFetes.DTO.PurchaseOrder.Request;
using PyroFetes.DTO.PurchaseOrder.Response;
using PyroFetes.DTO.PurchaseProduct.Request; using PyroFetes.DTO.PurchaseProduct.Request;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
@@ -12,12 +13,12 @@ public class CreatePurchaseOrder(
PurchaseOrdersRepository purchaseOrdersRepository, PurchaseOrdersRepository purchaseOrdersRepository,
ProductsRepository productsRepository, ProductsRepository productsRepository,
PurchaseProductsRepository purchaseProductsRepository, PurchaseProductsRepository purchaseProductsRepository,
AutoMapper.IMapper mapper) : Endpoint<CreatePurchaseOrderDto> AutoMapper.IMapper mapper) : Endpoint<CreatePurchaseOrderDto, GetPurchaseOrderDto>
{ {
public override void Configure() public override void Configure()
{ {
Post("/purchaseOrders"); Post("/purchaseOrders");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreatePurchaseOrderDto req, CancellationToken ct) public override async Task HandleAsync(CreatePurchaseOrderDto req, CancellationToken ct)
@@ -42,6 +43,7 @@ public class CreatePurchaseOrder(
if (purchaseProduct is not null) if (purchaseProduct is not null)
{ {
await Send.StringAsync("Le produit est déjà dans le bon de commande", 400, cancellation: ct); await Send.StringAsync("Le produit est déjà dans le bon de commande", 400, cancellation: ct);
return;
} }
PurchaseProduct? productOnPurchase = mapper.Map<PurchaseProduct>(line); PurchaseProduct? productOnPurchase = mapper.Map<PurchaseProduct>(line);
@@ -50,6 +52,6 @@ public class CreatePurchaseOrder(
await purchaseProductsRepository.AddAsync(productOnPurchase, ct); await purchaseProductsRepository.AddAsync(productOnPurchase, ct);
} }
} }
await Send.NoContentAsync(ct); await Send.OkAsync(mapper.Map<GetPurchaseOrderDto>(purchaseOrder), ct);
} }
} }
@@ -16,7 +16,7 @@ public class DeleteProductFromPurchaseOrderEndpoint(PurchaseProductsRepository p
public override void Configure() public override void Configure()
{ {
Delete("/purchaseOrders/{@ProductId}/{@PurchaseOrderId}", x => new { x.ProductId, x.PurchaseOrderId }); Delete("/purchaseOrders/{@ProductId}/{@PurchaseOrderId}", x => new { x.ProductId, x.PurchaseOrderId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(DeletePurchaseProductRequest req, CancellationToken ct) public override async Task HandleAsync(DeletePurchaseProductRequest req, CancellationToken ct)
@@ -15,7 +15,8 @@ public class DeletePurchaseOrderEndpoint(PurchaseOrdersRepository purchaseOrders
public override void Configure() public override void Configure()
{ {
Delete("/purchaseOrders/{@Id}", x => new { x.Id }); Delete("/purchaseOrders/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeletePurchaseOrderRequest req, CancellationToken ct) public override async Task HandleAsync(DeletePurchaseOrderRequest req, CancellationToken ct)
@@ -1,6 +1,7 @@
using FastEndpoints; using FastEndpoints;
using PyroFetes.DTO.PurchaseOrder.Response; using PyroFetes.DTO.PurchaseOrder.Response;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Specifications.PurchaseOrders;
namespace PyroFetes.Endpoints.PurchaseOrders; namespace PyroFetes.Endpoints.PurchaseOrders;
@@ -9,11 +10,11 @@ public class GetAllPurchaseOrderEndpoint(PurchaseOrdersRepository purchaseOrders
public override void Configure() public override void Configure()
{ {
Get("/purchaseOrders"); Get("/purchaseOrders");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
{ {
await Send.OkAsync(await purchaseOrdersRepository.ProjectToListAsync<GetPurchaseOrderDto>(ct), ct); await Send.OkAsync(await purchaseOrdersRepository.ProjectToListAsync<GetPurchaseOrderDto>(new GetAllPurchaseOrderSpec(), ct), ct);
} }
} }
@@ -16,7 +16,7 @@ public class GetPurchaseOrderEndpoint(PurchaseOrdersRepository purchaseOrdersRep
public override void Configure() public override void Configure()
{ {
Get("/purchaseOrders/{@Id}", x => new { x.Id }); Get("/purchaseOrders/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetPurchaseOrderRequest req, CancellationToken ct) public override async Task HandleAsync(GetPurchaseOrderRequest req, CancellationToken ct)
@@ -17,7 +17,7 @@ public class GetPurchaseOrderPdfEndpoint(
public override void Configure() public override void Configure()
{ {
Get("/purchaseOrders/{@Id}/pdf", x => new { x.Id }); Get("/purchaseOrders/{@Id}/pdf", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf)); Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf));
} }
@@ -13,7 +13,7 @@ public class PatchPurchaseOrderPurchaseConditionsEndpoint(PurchaseOrdersReposito
public override void Configure() public override void Configure()
{ {
Patch("/purchaseOrders/{@Id}/PurchaseConditions", x => new { x.Id }); Patch("/purchaseOrders/{@Id}/PurchaseConditions", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchPurchaseOrderPurchaseConditionsDto req, CancellationToken ct) public override async Task HandleAsync(PatchPurchaseOrderPurchaseConditionsDto req, CancellationToken ct)
@@ -13,7 +13,7 @@ public class PatchPurchaseProductQuantityEndpoint(PurchaseProductsRepository pur
public override void Configure() public override void Configure()
{ {
Patch("/purchaseOrders/{@ProductId}/{@PurchaseOrderId}/Quantity", x => new { x.ProductId, x.PurchaseOrderId }); Patch("/purchaseOrders/{@ProductId}/{@PurchaseOrderId}/Quantity", x => new { x.ProductId, x.PurchaseOrderId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchPurchaseProductQuantityDto req, CancellationToken ct) public override async Task HandleAsync(PatchPurchaseProductQuantityDto req, CancellationToken ct)
@@ -14,7 +14,7 @@ public class AddProductoToQuotationEndpoint(
public override void Configure() public override void Configure()
{ {
Post("/quotations/{@Id}/products", x => new { x.ProductId, x.QuotationId }); Post("/quotations/{@Id}/products", x => new { x.ProductId, x.QuotationId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(AddQuotationProductDto req, CancellationToken ct) public override async Task HandleAsync(AddQuotationProductDto req, CancellationToken ct)
@@ -17,13 +17,12 @@ public class CreateQuotationEndpoint(
public override void Configure() public override void Configure()
{ {
Post("/quotations"); Post("/quotations");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreateQuotationDto req, CancellationToken ct) public override async Task HandleAsync(CreateQuotationDto req, CancellationToken ct)
{ {
Quotation quotation = mapper.Map<Quotation>(req); Quotation quotation = mapper.Map<Quotation>(req);
quotation.CustomerId = 1; // TODO: A changer
await quotationsRepository.AddAsync(quotation, ct); await quotationsRepository.AddAsync(quotation, ct);
if (req.Products != null) if (req.Products != null)
@@ -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 });
AllowAnonymous(); Roles("Admin", "Employe");
} }
public override async Task HandleAsync(DeleteQuotationProductRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteQuotationProductRequest req, CancellationToken ct)
@@ -15,7 +15,8 @@ public class DeleteQuotationEndpoint(QuotationsRepository quotationsRepository)
public override void Configure() public override void Configure()
{ {
Delete("/quotations/{@Id}", x => new { x.Id }); Delete("/quotations/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeleteQuotationRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteQuotationRequest req, CancellationToken ct)
@@ -1,6 +1,7 @@
using FastEndpoints; using FastEndpoints;
using PyroFetes.DTO.Quotation.Response; using PyroFetes.DTO.Quotation.Response;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Specifications.Quotations;
namespace PyroFetes.Endpoints.Quotations; namespace PyroFetes.Endpoints.Quotations;
@@ -9,11 +10,11 @@ public class GetAllQuotationEndpoint(QuotationsRepository quotationsRepository)
public override void Configure() public override void Configure()
{ {
Get("/quotations"); Get("/quotations");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
{ {
await Send.OkAsync(await quotationsRepository.ProjectToListAsync<GetQuotationDto>(ct), ct); await Send.OkAsync(await quotationsRepository.ProjectToListAsync<GetQuotationDto>(new GetAllQuotationSpec(), ct), ct);
} }
} }
@@ -18,7 +18,7 @@ public class GetQuotationEndpoint(
public override void Configure() public override void Configure()
{ {
Get("/quotations/{@Id}", x => new { x.Id }); Get("/quotations/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetQuotationRequest req, CancellationToken ct) public override async Task HandleAsync(GetQuotationRequest req, CancellationToken ct)
@@ -17,7 +17,7 @@ public class GetQuotationPdfEndpoint(
public override void Configure() public override void Configure()
{ {
Get("/quotations/{@Id}/pdf", x => new { x.Id }); Get("/quotations/{@Id}/pdf", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf)); Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf));
} }
@@ -14,7 +14,7 @@ public class PatchQuotationConditionsSaleEndpoint(
public override void Configure() public override void Configure()
{ {
Patch("/quotations/{@Id}/saleConditions", x => new { x.Id }); Patch("/quotations/{@Id}/saleConditions", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchQuotationConditionsSaleDto req, CancellationToken ct) public override async Task HandleAsync(PatchQuotationConditionsSaleDto req, CancellationToken ct)
@@ -14,7 +14,7 @@ public class PatchQuotationMessageEndpoint(
public override void Configure() public override void Configure()
{ {
Patch("/quotations/{@Id}/message", x => new { x.Id }); Patch("/quotations/{@Id}/message", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchQuotationMessageDto req, CancellationToken ct) public override async Task HandleAsync(PatchQuotationMessageDto req, CancellationToken ct)
@@ -14,7 +14,7 @@ public class PatchQuotationProductQuantityEndpoint(
public override void Configure() public override void Configure()
{ {
Patch("/quotations/{@ProductId}/{@QuotationId}/Quantity", x => new { x.ProductId, x.QuotationId }); Patch("/quotations/{@ProductId}/{@QuotationId}/Quantity", x => new { x.ProductId, x.QuotationId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchQuotationProductQuantityDto req, CancellationToken ct) public override async Task HandleAsync(PatchQuotationProductQuantityDto req, CancellationToken ct)
@@ -13,7 +13,8 @@ public class UpdateQuotationEndpoint(
public override void Configure() public override void Configure()
{ {
Put("/quotations/{@Id}", x => new { x.Id }); Put("/quotations/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(UpdateQuotationDto req, CancellationToken ct) public override async Task HandleAsync(UpdateQuotationDto req, CancellationToken ct)
@@ -0,0 +1,62 @@
using System.IdentityModel.Tokens.Jwt;
using FastEndpoints;
using FastEndpoints.Security;
using PyroFetes.DTO.Refresh.Request;
using PyroFetes.DTO.Refresh.Response;
using PyroFetes.Models;
using PyroFetes.Repositories;
using PyroFetes.Specifications.Users;
namespace PyroFetes.Endpoints.Refresh;
public class RefreshTokenEndpoint(UsersRepository usersRepository) : Endpoint<RefreshTokenDto, GetRefreshTokenDto>
{
public override void Configure()
{
Post("/refresh");
AllowAnonymous();
}
public override async Task HandleAsync(RefreshTokenDto req, CancellationToken ct)
{
try
{
JwtSecurityTokenHandler handler = new();
JwtSecurityToken? token = handler.ReadJwtToken(req.Token);
string? username = token.Claims.FirstOrDefault(c => c.Type == "Name")?.Value;
if (string.IsNullOrWhiteSpace(username))
{
await Send.UnauthorizedAsync(ct);
return;
}
User? login = await usersRepository.SingleOrDefaultAsync(new GetUserByNameSpec(username), ct);
if (login == null)
{
await Send.UnauthorizedAsync(ct);
return;
}
string jwtToken = JwtBearer.CreateToken(o =>
{
o.SigningKey = "v9!Qx7#Lk2@pZ8$wR6!tN5%uF3&cD9^mH1*eY4";
o.ExpireAt = DateTime.UtcNow.AddMinutes(15);
if (login.Fonction is not null) o.User.Roles.Add(login.Fonction);
o.User.Claims.Add(("Name", login.Name)!);
o.User.Claims.Add(("Id", login.Id.ToString())!);
});
GetRefreshTokenDto responseDto = new()
{
Token = jwtToken
};
await Send.OkAsync(responseDto, ct);
}
catch
{
await Send.UnauthorizedAsync(ct);
}
}
}
@@ -2,15 +2,16 @@
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()
{ {
Get("/settings/"); Get("/settings/");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
@@ -22,7 +23,14 @@ public class GetSettingEndpoint(SettingsRepository settingsRepository, AutoMappe
await Send.NotFoundAsync(ct); await Send.NotFoundAsync(ct);
return; 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,16 +2,18 @@
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()
{ {
Patch("/settings/electronicSignature"); Patch("/settings/electronicSignature");
AllowFormData(); AllowFormData();
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(PatchSettingElectronicSignatureDto req, CancellationToken ct) public override async Task HandleAsync(PatchSettingElectronicSignatureDto req, CancellationToken ct)
@@ -23,14 +25,10 @@ public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settings
await Send.NotFoundAsync(ct); await Send.NotFoundAsync(ct);
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,16 +2,17 @@
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()
{ {
Patch("/settings/logo"); Patch("/settings/logo");
AllowFormData(); AllowFormData();
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(PatchSettingLogoDto req, CancellationToken ct) public override async Task HandleAsync(PatchSettingLogoDto req, CancellationToken ct)
@@ -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);
@@ -17,7 +17,7 @@ public class AddProductToSupplierEndpoint(
public override void Configure() public override void Configure()
{ {
Post("/suppliers/{@SupplierId}/{@ProductId}/", x => new { x.SupplierId, x.ProductId }); Post("/suppliers/{@SupplierId}/{@ProductId}/", x => new { x.SupplierId, x.ProductId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreatePriceDto req, CancellationToken ct) public override async Task HandleAsync(CreatePriceDto req, CancellationToken ct)
@@ -10,7 +10,7 @@ public class CreateSupplierEndpoint(SuppliersRepository suppliersRepository, Aut
public override void Configure() public override void Configure()
{ {
Post("/suppliers"); Post("/suppliers");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CreateSupplierDto req, CancellationToken ct) public override async Task HandleAsync(CreateSupplierDto req, CancellationToken ct)
@@ -16,7 +16,7 @@ public class DeleteProductToSupplierEndpoint(PricesRepository pricesRepository)
public override void Configure() public override void Configure()
{ {
Delete("/suppliers/{@SupplierId}/{@Product}", x => new { x.SupplierId, x.ProductId }); Delete("/suppliers/{@SupplierId}/{@Product}", x => new { x.SupplierId, x.ProductId });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeletePriceRequest req, CancellationToken ct) public override async Task HandleAsync(DeletePriceRequest req, CancellationToken ct)
@@ -15,7 +15,7 @@ public class DeleteSupplierEndpoint(SuppliersRepository suppliersRepository) : E
public override void Configure() public override void Configure()
{ {
Delete("/suppliers/{@Id}", x => new { x.Id }); Delete("/suppliers/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeleteSupplierRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteSupplierRequest req, CancellationToken ct)
@@ -9,7 +9,7 @@ public class GetAllSuppliersEndpoint(SuppliersRepository suppliersRepository) :
public override void Configure() public override void Configure()
{ {
Get("/suppliers"); Get("/suppliers");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
@@ -16,7 +16,7 @@ public class GetSupplierEndpoint(SuppliersRepository suppliersRepository, AutoMa
public override void Configure() public override void Configure()
{ {
Get("/suppliers/{@Id}", x => new { x.Id }); Get("/suppliers/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetSupplierRequest req, CancellationToken ct) public override async Task HandleAsync(GetSupplierRequest req, CancellationToken ct)
@@ -13,7 +13,7 @@ public class PatchPriceEndpoint(
public override void Configure() public override void Configure()
{ {
Patch("/prices/{@ProductId}/{@SupplierId}/SellingPrice", x => new { x.ProductId, x.SupplierId }); Patch("/prices/{@ProductId}/{@SupplierId}/SellingPrice", x => new { x.ProductId, x.SupplierId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchPriceSellingPriceDto req, CancellationToken ct) public override async Task HandleAsync(PatchPriceSellingPriceDto req, CancellationToken ct)
@@ -12,7 +12,8 @@ public class PatchSupplierDeliveryDelayEndpoint(SuppliersRepository suppliersRep
public override void Configure() public override void Configure()
{ {
Patch("/suppliers/{@Id}/deliveryDelay", x => new { x.Id }); Patch("/suppliers/{@Id}/deliveryDelay", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchSupplierDeliveryDelayDto req, CancellationToken ct) public override async Task HandleAsync(PatchSupplierDeliveryDelayDto req, CancellationToken ct)
@@ -11,7 +11,7 @@ public class UpdateSupplierEndpoint(SuppliersRepository suppliersRepository, Aut
public override void Configure() public override void Configure()
{ {
Put("/suppliers/{@Id}", x => new { x.Id }); Put("/suppliers/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(UpdateSupplierDto req, CancellationToken ct) public override async Task HandleAsync(UpdateSupplierDto req, CancellationToken ct)
@@ -31,7 +31,7 @@ public class ConnectUserEndpoint(UsersRepository usersRepository) : Endpoint<Con
{ {
string jwtToken = JwtBearer.CreateToken(o => string jwtToken = JwtBearer.CreateToken(o =>
{ {
o.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong"; o.SigningKey = "v9!Qx7#Lk2@pZ8$wR6!tN5%uF3&cD9^mH1*eY4";
o.ExpireAt = DateTime.UtcNow.AddMinutes(15); o.ExpireAt = DateTime.UtcNow.AddMinutes(15);
if (user.Fonction is not null) o.User.Roles.Add(user.Fonction); if (user.Fonction is not null) o.User.Roles.Add(user.Fonction);
o.User.Claims.Add(("Name", user.Name)!); o.User.Claims.Add(("Name", user.Name)!);
@@ -13,7 +13,7 @@ public class CreateUserEndpoint(UsersRepository usersRepository) : Endpoint<Crea
public override void Configure() public override void Configure()
{ {
Post("/users"); Post("/users");
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(CreateUserDto req, CancellationToken ct) public override async Task HandleAsync(CreateUserDto req, CancellationToken ct)
@@ -15,7 +15,7 @@ public class DeleteUserEndpoint(UsersRepository usersRepository) : Endpoint<Dele
public override void Configure() public override void Configure()
{ {
Delete("/users/{@Id}", x => new { x.Id }); Delete("/users/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(DeleteUserRequest req, CancellationToken ct) public override async Task HandleAsync(DeleteUserRequest req, CancellationToken ct)
@@ -9,7 +9,7 @@ public class GetAllUsersEndpoint(UsersRepository usersRepository) : EndpointWith
public override void Configure() public override void Configure()
{ {
Get("/users"); Get("/users");
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
+6 -10
View File
@@ -6,22 +6,18 @@ using PyroFetes.Specifications.Users;
namespace PyroFetes.Endpoints.Users; namespace PyroFetes.Endpoints.Users;
public class GetUserRequest public class GetUserEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : EndpointWithoutRequest<GetUserDto>
{
public int Id { get; set; }
}
public class GetUserEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : Endpoint<GetUserRequest, GetUserDto>
{ {
public override void Configure() public override void Configure()
{ {
Get("/users/{@Id}", x => new { x.Id }); Get("/user/");
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetUserRequest req, CancellationToken ct) public override async Task HandleAsync(CancellationToken ct)
{ {
User? user = await usersRepository.SingleOrDefaultAsync(new GetUserByIdSpec(req.Id), ct); int userId = int.Parse(User.FindFirst("Id")!.Value);
User? user = await usersRepository.SingleOrDefaultAsync(new GetUserByIdSpec(userId), ct);
if (user is null) if (user is null)
{ {
@@ -12,7 +12,7 @@ public class PatchUserPasswordEndpoint(UsersRepository usersRepository, AutoMapp
public override void Configure() public override void Configure()
{ {
Patch("/users/{@Id}/password", x => new { x.Id }); Patch("/users/{@Id}/password", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(PatchUserPasswordDto req, CancellationToken ct) public override async Task HandleAsync(PatchUserPasswordDto req, CancellationToken ct)
@@ -13,7 +13,7 @@ public class UpdateUserEndpoint(UsersRepository usersRepository) : Endpoint<Upda
public override void Configure() public override void Configure()
{ {
Put("/users/{@Id}", x => new { x.Id }); Put("/users/{@Id}", x => new { x.Id });
AllowAnonymous(); Roles("Admin");
} }
public override async Task HandleAsync(UpdateUserDto req, CancellationToken ct) public override async Task HandleAsync(UpdateUserDto req, CancellationToken ct)
@@ -0,0 +1,19 @@
using FastEndpoints;
using PyroFetes.DTO.WareHouse.Response;
using PyroFetes.Repositories;
namespace PyroFetes.Endpoints.WareHouse;
public class GetAllWarehouseEndpoint(WareHouseRepository wareHouseRepository, AutoMapper.IMapper mapper) : EndpointWithoutRequest<List<GetWareHouseDto>>
{
public override void Configure()
{
Get("/wareHouses/");
Roles("Admin","Employe");
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await wareHouseRepository.ProjectToListAsync<GetWareHouseDto>(ct), ct);
}
}
@@ -16,7 +16,7 @@ public class GetTotalQuantityEndpoint(
public override void Configure() public override void Configure()
{ {
Get("/wareHouseProducts/{@ProductId}", x => new { x.ProductId }); Get("/wareHouseProducts/{@ProductId}", x => new { x.ProductId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(GetTotalQuantityRequest req, CancellationToken ct) public override async Task HandleAsync(GetTotalQuantityRequest req, CancellationToken ct)
@@ -3,31 +3,37 @@ using PyroFetes.DTO.WareHouseProduct.Request;
using PyroFetes.DTO.WareHouseProduct.Response; using PyroFetes.DTO.WareHouseProduct.Response;
using PyroFetes.Models; using PyroFetes.Models;
using PyroFetes.Repositories; using PyroFetes.Repositories;
using PyroFetes.Specifications.WareHouse;
using PyroFetes.Specifications.WarehouseProducts; using PyroFetes.Specifications.WarehouseProducts;
namespace PyroFetes.Endpoints.WareHouseProducts; namespace PyroFetes.Endpoints.WareHouseProducts;
public class PatchWareHouseProductQuantityEndpoint(WarehouseProductsRepository warehouseProductsRepository) : Endpoint<PatchWareHouseProductQuantityDto, GetWareHouseProductDto> public class PatchWareHouseProductQuantityEndpoint(WarehouseProductsRepository warehouseProductsRepository, WareHouseRepository wareHouseRepository , AutoMapper.IMapper mapper) : Endpoint<PatchWareHouseProductQuantityDto, GetWareHouseProductDto>
{ {
public override void Configure() public override void Configure()
{ {
Patch("/wareHouseProducts/{@ProductId}/{@WareHouseId}/quantity", x => new { x.ProductId, x.WareHouseId }); Patch("/wareHouseProducts/{@ProductId}/{@WareHouseId}/quantity", x => new { x.ProductId, x.WareHouseId });
AllowAnonymous(); Roles("Admin","Employe");
} }
public override async Task HandleAsync(PatchWareHouseProductQuantityDto req, CancellationToken ct) public override async Task HandleAsync(PatchWareHouseProductQuantityDto req, CancellationToken ct)
{ {
WarehouseProduct? wareHouseProduct = await warehouseProductsRepository.FirstOrDefaultAsync(new GetWarehouseProductByProductIdSpec(req.ProductId, req.WareHouseId), ct); WarehouseProduct? wareHouseProduct = await warehouseProductsRepository.FirstOrDefaultAsync(new GetWarehouseProductByProductIdSpec(req.ProductId, req.WareHouseId), ct);
Warehouse? warehouse = await wareHouseRepository.SingleOrDefaultAsync(new GetWareHouseByIdSpec(req.WareHouseId), ct);
if (wareHouseProduct is null) if (warehouse is null)
{ {
await Send.NotFoundAsync(ct); await Send.NotFoundAsync(ct);
return; return;
} }
wareHouseProduct.Quantity = req.Quantity; if (wareHouseProduct is null) await warehouseProductsRepository.AddAsync(mapper.Map<WarehouseProduct>(req), ct);
await warehouseProductsRepository.SaveChangesAsync(ct); else
{
wareHouseProduct.Quantity += req.Quantity;
await warehouseProductsRepository.SaveChangesAsync(ct);
}
await Send.NoContentAsync(ct); await Send.NoContentAsync(ct);
} }
} }
@@ -81,8 +81,6 @@ public class DtoToEntityMappings : Profile
CreateMap<PatchUserPasswordDto, User>() CreateMap<PatchUserPasswordDto, User>()
.ForMember(dest => dest.Id, opt => opt.Ignore()); .ForMember(dest => dest.Id, opt => opt.Ignore());
CreateMap<PatchWareHouseProductQuantityDto, WarehouseProduct>() CreateMap<PatchWareHouseProductQuantityDto, WarehouseProduct>();
.ForMember(dest => dest.ProductId, opt => opt.Ignore())
.ForMember(dest => dest.WarehouseId, opt => opt.Ignore());
} }
} }
@@ -1,4 +1,5 @@
using AutoMapper; using AutoMapper;
using PyroFetes.DTO.Customer.Response;
using PyroFetes.DTO.Deliverer.Response; using PyroFetes.DTO.Deliverer.Response;
using PyroFetes.DTO.DeliveryNote.Response; using PyroFetes.DTO.DeliveryNote.Response;
using PyroFetes.DTO.Price.Response; using PyroFetes.DTO.Price.Response;
@@ -11,6 +12,7 @@ using PyroFetes.DTO.QuotationProduct.Response;
using PyroFetes.DTO.SettingDTO.Response; using PyroFetes.DTO.SettingDTO.Response;
using PyroFetes.DTO.Supplier.Response; using PyroFetes.DTO.Supplier.Response;
using PyroFetes.DTO.User.Response; using PyroFetes.DTO.User.Response;
using PyroFetes.DTO.WareHouse.Response;
using PyroFetes.DTO.WareHouseProduct.Response; using PyroFetes.DTO.WareHouseProduct.Response;
using PyroFetes.Models; using PyroFetes.Models;
@@ -56,5 +58,9 @@ public class EntityToDtoMappings : Profile
CreateMap<User, GetUserDto>(); CreateMap<User, GetUserDto>();
CreateMap<WarehouseProduct, GetWareHouseProductDto>(); CreateMap<WarehouseProduct, GetWareHouseProductDto>();
CreateMap<Warehouse, GetWareHouseDto>();
CreateMap<Customer, GetCustomerDto>();
} }
} }
+32 -1
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;
@@ -17,7 +20,7 @@ QuestPDF.Settings.License = LicenseType.Community;
// On ajoute ici FastEndpoints, un framework REPR et Swagger aux services disponibles dans le projet // On ajoute ici FastEndpoints, un framework REPR et Swagger aux services disponibles dans le projet
builder.Services builder.Services
.AddAuthenticationJwtBearer(s => s.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong") .AddAuthenticationJwtBearer(s => s.SigningKey = "v9!Qx7#Lk2@pZ8$wR6!tN5%uF3&cD9^mH1*eY4")
.AddAuthorization() .AddAuthorization()
.AddFastEndpoints() .AddFastEndpoints()
.SwaggerDocument(options => { options.ShortSchemaNames = true; }) .SwaggerDocument(options => { options.ShortSchemaNames = true; })
@@ -49,11 +52,14 @@ builder.Services.AddScoped<SuppliersRepository>();
builder.Services.AddScoped<SettingsRepository>(); builder.Services.AddScoped<SettingsRepository>();
builder.Services.AddScoped<UsersRepository>(); builder.Services.AddScoped<UsersRepository>();
builder.Services.AddScoped<WarehouseProductsRepository>(); builder.Services.AddScoped<WarehouseProductsRepository>();
builder.Services.AddScoped<WareHouseRepository>();
builder.Services.AddScoped<CustomersRepository>();
// Ajout des services // Ajout des services
builder.Services.AddScoped<IDeliveryNotePdfService, DeliveryNotePdfService>(); builder.Services.AddScoped<IDeliveryNotePdfService, DeliveryNotePdfService>();
builder.Services.AddScoped<IPurchaseOrderPdfService, PurchaseOrderPdfService>(); builder.Services.AddScoped<IPurchaseOrderPdfService, PurchaseOrderPdfService>();
builder.Services.AddScoped<IQuotationPdfService, QuotationPdfService>(); builder.Services.AddScoped<IQuotationPdfService, QuotationPdfService>();
builder.Services.AddScoped<StorageService>();
MapperConfiguration mappingConfig = new(mc => MapperConfiguration mappingConfig = new(mc =>
{ {
@@ -66,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 -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" />
@@ -33,7 +34,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Migrations\" /> <Folder Include="Migrations\" />
<Folder Include="wwwroot\" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -0,0 +1,5 @@
using PyroFetes.Models;
namespace PyroFetes.Repositories;
public class CustomersRepository(PyroFetesDbContext pyrofetesContext, AutoMapper.IMapper mapper) : PyrofetesRepository<Customer>(pyrofetesContext, mapper);
@@ -0,0 +1,5 @@
using PyroFetes.Models;
namespace PyroFetes.Repositories;
public class WareHouseRepository(PyroFetesDbContext pyrofetesContext, AutoMapper.IMapper mapper) : PyrofetesRepository<Warehouse>(pyrofetesContext, mapper);
@@ -35,7 +35,8 @@ public class QuotationPdfService : IQuotationPdfService
col.Item().Text(""); col.Item().Text("");
col.Item().Text(""); col.Item().Text("");
col.Item().Text("Client").SemiBold().FontSize(12); col.Item().Text("Client").SemiBold().FontSize(12);
col.Item().Text($"{quotation.Customer}"); col.Item().Text($"{quotation.Customer?.Note}");
col.Item().Text($"{quotation.Customer?.CustomerType?.Label}");
}); });
// Logo + société à droite // Logo + société à droite
+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}";
}
}
@@ -11,6 +11,7 @@ public class GetAllDeliveryNoteSpec : Specification<DeliveryNote>
.Include(x => x.Deliverer) .Include(x => x.Deliverer)
.Include(x => x.ProductDeliveries)! .Include(x => x.ProductDeliveries)!
.ThenInclude(x => x.Product) .ThenInclude(x => x.Product)
.Where(x => true); .OrderBy(x => x.RealDeliveryDate)
.ThenByDescending(x => x.ExpeditionDate);
} }
} }
@@ -0,0 +1,17 @@
using Ardalis.Specification;
using PyroFetes.Models;
namespace PyroFetes.Specifications.DeliveryNotes;
public class GetAllDeliveryNotesByRealDateSpec : Specification<DeliveryNote>
{
public GetAllDeliveryNotesByRealDateSpec()
{
Query
.Include(x => x.Deliverer)
.Include(x => x.ProductDeliveries)!
.ThenInclude(x => x.Product)
.Where(x => x.RealDeliveryDate == null)
.OrderByDescending(x => x.ExpeditionDate);
}
}
@@ -8,7 +8,8 @@ public sealed class GetProductsUnderLimitSpec : Specification<Product>
public GetProductsUnderLimitSpec() public GetProductsUnderLimitSpec()
{ {
Query Query
.Include(p => p.QuotationProducts) .Include(x => x.WarehouseProducts)
.Where(p => p.QuotationProducts != null && p.QuotationProducts.Any(q => q.Quantity < p.MinimalQuantity)); .Where(x => x.WarehouseProducts != null && x.WarehouseProducts.Sum(p => p.Quantity) < x.MinimalQuantity
&& !x.ProductDeliveries!.Any(d => d.DeliveryNote!.RealDeliveryDate == null));
} }
} }
@@ -0,0 +1,14 @@
using Ardalis.Specification;
using PyroFetes.Models;
namespace PyroFetes.Specifications.PurchaseOrders;
public class GetAllPurchaseOrderSpec : Specification<PurchaseOrder>
{
public GetAllPurchaseOrderSpec()
{
Query
.Where(x => true)
.OrderByDescending(x => x.Id);
}
}
@@ -0,0 +1,14 @@
using Ardalis.Specification;
using PyroFetes.Models;
namespace PyroFetes.Specifications.Quotations;
public class GetAllQuotationSpec : Specification<Quotation>
{
public GetAllQuotationSpec()
{
Query
.Where(x => true)
.OrderByDescending(x => x.Id);
}
}
@@ -11,6 +11,8 @@ public class GetQuotationByIdWithProductsSpec : SingleResultSpecification<Quotat
.Where(x => x.Id == quotationId) .Where(x => x.Id == quotationId)
.Include(x => x.QuotationProducts!) .Include(x => x.QuotationProducts!)
.ThenInclude(x => x.Product) .ThenInclude(x => x.Product)
.ThenInclude(x => x!.Prices); .ThenInclude(x => x!.Prices)
.Include(x => x.Customer)
.ThenInclude(x => x!.CustomerType);
} }
} }
@@ -0,0 +1,13 @@
using Ardalis.Specification;
using PyroFetes.Models;
namespace PyroFetes.Specifications.WareHouse;
public class GetWareHouseByIdSpec : SingleResultSpecification<Warehouse>
{
public GetWareHouseByIdSpec(int id)
{
Query
.Where(x => x.Id == id);
}
}
@@ -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"
} }
} }
View File
+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