Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f54f5e02dc | |||
| d17f2fb23e | |||
| 697e1431d9 | |||
| de6a1c5385 | |||
| 29759cf896 | |||
| f0ad9b536a | |||
| fb97729c71 | |||
| 1bd92a8732 | |||
| 8c38255ed9 | |||
| 639631a63b |
@@ -5,7 +5,6 @@ public class GetUserDto
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Password { get; set; }
|
||||
public string? Salt { get; set; }
|
||||
public string? Fonction { get; set; }
|
||||
public string? Email { get; set; }
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class UpdateDelivererEndpoint(DeliverersRepository deliverersRepository,
|
||||
public override void Configure()
|
||||
{
|
||||
Put("/deliverers/{@Id}", x => new { x.Id });
|
||||
Roles("Admin");
|
||||
Roles("Admin", "Employe");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(UpdateDelivererDto req, CancellationToken ct)
|
||||
|
||||
@@ -3,18 +3,23 @@ using FastEndpoints;
|
||||
using PyroFetes.DTO.DeliveryNote.Request;
|
||||
using PyroFetes.Models;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
using PyroFetes.Services.Pdf;
|
||||
using PyroFetes.Specifications.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[]>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/deliveryNotes/{@Id}/pdf", x => new { x.Id });
|
||||
Roles("Admin","Employe");
|
||||
Roles("Admin", "Employe");
|
||||
Description(b => b.Produces<byte[]>(200, MediaTypeNames.Application.Pdf));
|
||||
}
|
||||
|
||||
@@ -31,7 +36,7 @@ public class GetDeliveryNotePdfEndpoint(DeliveryNotesRepository deliveryNotesRep
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = deliveryNotePdfService.Generate(deliveryNote, deliveryNote.ProductDeliveries!, setting!);
|
||||
byte[] bytes = await deliveryNotePdfService.Generate(deliveryNote, setting!, storageService);
|
||||
|
||||
await Send.BytesAsync(
|
||||
bytes: bytes,
|
||||
|
||||
@@ -3,6 +3,7 @@ using FastEndpoints;
|
||||
using PyroFetes.DTO.PurchaseOrder.Request;
|
||||
using PyroFetes.Models;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
using PyroFetes.Services.Pdf;
|
||||
using PyroFetes.Specifications.PurchaseOrders;
|
||||
|
||||
@@ -10,8 +11,9 @@ namespace PyroFetes.Endpoints.PurchaseOrders;
|
||||
|
||||
public class GetPurchaseOrderPdfEndpoint(
|
||||
PurchaseOrdersRepository purchaseOrdersRepository,
|
||||
IPurchaseOrderPdfService purchaseOrderPdfService,
|
||||
SettingsRepository settingsRepository)
|
||||
PurchaseOrderPdfService purchaseOrderPdfService,
|
||||
SettingsRepository settingsRepository,
|
||||
StorageService storageService)
|
||||
: Endpoint<GetPurchaseOrderPdfDto, byte[]>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -33,7 +35,7 @@ public class GetPurchaseOrderPdfEndpoint(
|
||||
|
||||
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(
|
||||
bytes: bytes,
|
||||
|
||||
@@ -16,7 +16,7 @@ public class DeleteProductFromQuotationEndpoint(QuotationProductsRepository quot
|
||||
public override void Configure()
|
||||
{
|
||||
Delete("/quotations/{@ProductId}/{@QuotationId}", x => new { x.ProductId, x.QuotationId });
|
||||
Roles("Admin");
|
||||
Roles("Admin", "Employe");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(DeleteQuotationProductRequest req, CancellationToken ct)
|
||||
|
||||
@@ -3,6 +3,7 @@ using FastEndpoints;
|
||||
using PyroFetes.DTO.Quotation.Request;
|
||||
using PyroFetes.Models;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
using PyroFetes.Services.Pdf;
|
||||
using PyroFetes.Specifications.Quotations;
|
||||
|
||||
@@ -10,8 +11,9 @@ namespace PyroFetes.Endpoints.Quotations;
|
||||
|
||||
public class GetQuotationPdfEndpoint(
|
||||
QuotationsRepository quotationRepository,
|
||||
IQuotationPdfService quotationPdfService,
|
||||
SettingsRepository settingsRepository)
|
||||
QuotationPdfService quotationPdfService,
|
||||
SettingsRepository settingsRepository,
|
||||
StorageService storageService)
|
||||
: Endpoint<GetQuotationPdfDto, byte[]>
|
||||
{
|
||||
public override void Configure()
|
||||
@@ -33,7 +35,7 @@ public class GetQuotationPdfEndpoint(
|
||||
|
||||
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(
|
||||
bytes: bytes,
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
using PyroFetes.DTO.SettingDTO.Response;
|
||||
using PyroFetes.Models;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
|
||||
namespace PyroFetes.Endpoints.Settings;
|
||||
|
||||
public class GetSettingEndpoint(SettingsRepository settingsRepository, AutoMapper.IMapper mapper) : EndpointWithoutRequest<GetSettingDto>
|
||||
public class GetSettingEndpoint(SettingsRepository settingsRepository, StorageService storageService) : EndpointWithoutRequest<GetSettingDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
@@ -22,7 +23,14 @@ public class GetSettingEndpoint(SettingsRepository settingsRepository, AutoMappe
|
||||
await Send.NotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
GetSettingDto settingDto = new()
|
||||
{
|
||||
Id = setting.Id,
|
||||
ElectronicSignature = storageService.GetUrl(setting.ElectronicSignature!),
|
||||
Logo = storageService.GetUrl(setting.Logo!)
|
||||
};
|
||||
|
||||
await Send.OkAsync(mapper.Map<GetSettingDto>(setting), ct);
|
||||
await Send.OkAsync(settingDto, ct);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,11 @@
|
||||
using PyroFetes.DTO.SettingDTO.Request;
|
||||
using PyroFetes.Models;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
|
||||
namespace PyroFetes.Endpoints.Settings;
|
||||
|
||||
public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settingsRepository) : Endpoint<PatchSettingElectronicSignatureDto>
|
||||
public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settingsRepository, StorageService storageService) : Endpoint<PatchSettingElectronicSignatureDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
@@ -24,14 +25,10 @@ public class PatchSettingElectronicSignatureEndpoint(SettingsRepository settings
|
||||
await Send.NotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// Encodage en base64
|
||||
using MemoryStream memoryStream = new();
|
||||
if (req.ElectronicSignature != null) await req.ElectronicSignature.CopyToAsync(memoryStream, ct);
|
||||
byte[] signatureBytes = memoryStream.ToArray();
|
||||
|
||||
setting.ElectronicSignature = Convert.ToBase64String(signatureBytes);
|
||||
|
||||
|
||||
string key = await storageService.UploadFile(req.ElectronicSignature!, "electronicSignature", ct);
|
||||
setting.ElectronicSignature = key;
|
||||
|
||||
await settingsRepository.SaveChangesAsync(ct);
|
||||
await Send.NoContentAsync(ct);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
using PyroFetes.DTO.SettingDTO.Request;
|
||||
using PyroFetes.Models;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
|
||||
namespace PyroFetes.Endpoints.Settings;
|
||||
|
||||
public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository) : Endpoint<PatchSettingLogoDto>
|
||||
public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository, StorageService storageService) : Endpoint<PatchSettingLogoDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
@@ -24,12 +25,9 @@ public class PatchSettingLogoEndpoint(SettingsRepository settingsRepository) : E
|
||||
return;
|
||||
}
|
||||
|
||||
// Encodage en base64
|
||||
using MemoryStream memoryStream = new();
|
||||
if (req.Logo != null) await req.Logo.CopyToAsync(memoryStream, ct);
|
||||
byte[] logoBytes = memoryStream.ToArray();
|
||||
string key = await storageService.UploadFile(req.Logo!, "logo", ct);
|
||||
|
||||
setting.Logo = Convert.ToBase64String(logoBytes);
|
||||
setting.Logo = key;
|
||||
|
||||
await settingsRepository.SaveChangesAsync(ct);
|
||||
await Send.NoContentAsync(ct);
|
||||
|
||||
@@ -6,22 +6,18 @@ using PyroFetes.Specifications.Users;
|
||||
|
||||
namespace PyroFetes.Endpoints.Users;
|
||||
|
||||
public class GetUserRequest
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
public class GetUserEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : Endpoint<GetUserRequest, GetUserDto>
|
||||
public class GetUserEndpoint(UsersRepository usersRepository, AutoMapper.IMapper mapper) : EndpointWithoutRequest<GetUserDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/users/{@Id}", x => new { x.Id });
|
||||
Get("/user/");
|
||||
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)
|
||||
{
|
||||
|
||||
+32
-3
@@ -1,3 +1,5 @@
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using AutoMapper;
|
||||
using AutoMapper.EquivalencyExpression;
|
||||
using PyroFetes;
|
||||
@@ -7,6 +9,7 @@ using FastEndpoints.Security;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using PyroFetes.MappingProfiles;
|
||||
using PyroFetes.Repositories;
|
||||
using PyroFetes.Services;
|
||||
using PyroFetes.Services.Pdf;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -53,9 +56,10 @@ builder.Services.AddScoped<WareHouseRepository>();
|
||||
builder.Services.AddScoped<CustomersRepository>();
|
||||
|
||||
// Ajout des services
|
||||
builder.Services.AddScoped<IDeliveryNotePdfService, DeliveryNotePdfService>();
|
||||
builder.Services.AddScoped<IPurchaseOrderPdfService, PurchaseOrderPdfService>();
|
||||
builder.Services.AddScoped<IQuotationPdfService, QuotationPdfService>();
|
||||
builder.Services.AddScoped<DeliveryNotePdfService>();
|
||||
builder.Services.AddScoped<PurchaseOrderPdfService>();
|
||||
builder.Services.AddScoped<QuotationPdfService>();
|
||||
builder.Services.AddScoped<StorageService>();
|
||||
|
||||
MapperConfiguration mappingConfig = new(mc =>
|
||||
{
|
||||
@@ -68,6 +72,31 @@ MapperConfiguration mappingConfig = new(mc =>
|
||||
AutoMapper.IMapper mapper = mappingConfig.CreateMapper();
|
||||
builder.Services.AddSingleton(mapper);
|
||||
|
||||
// RUSTFS
|
||||
IConfigurationSection config = builder.Configuration.GetSection("RustFS");
|
||||
|
||||
AmazonS3Client s3Client = new(
|
||||
config["AccessKey"],
|
||||
config["SecretKey"],
|
||||
new AmazonS3Config
|
||||
{
|
||||
ServiceURL = config["ServiceUrl"],
|
||||
ForcePathStyle = true
|
||||
}
|
||||
);
|
||||
|
||||
ListBucketsResponse? buckets = await s3Client.ListBucketsAsync();
|
||||
bool exist = buckets?.Buckets?.Any(x => x.BucketName == config["BucketName"]) == true;
|
||||
if (!exist)
|
||||
{
|
||||
await s3Client.PutBucketAsync(new PutBucketRequest
|
||||
{
|
||||
BucketName = config["BucketName"]
|
||||
});
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<IAmazonS3>(s3Client);
|
||||
|
||||
// On construit l'application en lui donnant vie
|
||||
WebApplication app = builder.Build();
|
||||
app.UseAuthentication()
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
|
||||
<PackageReference Include="AutoMapper" Version="15.0.1" />
|
||||
<PackageReference Include="AutoMapper.Collection" Version="11.0.0" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="4.0.24" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="FastEndpoints" Version="7.0.1" />
|
||||
<PackageReference Include="FastEndpoints.Security" Version="7.0.1" />
|
||||
@@ -33,7 +34,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -64,7 +64,7 @@ public class PyroFetesDbContext : DbContext
|
||||
{
|
||||
string connectionString =
|
||||
"Server=romaric-thibault.fr;" +
|
||||
"Database=PyroFetes-Sujet2;" +
|
||||
"Database=PyroFetes-Sujet2-Cristiano;" +
|
||||
"User Id=pyrofetes;" +
|
||||
"Password=Crablike8-Fringe-Swimmable;" +
|
||||
"TrustServerCertificate=true;";
|
||||
|
||||
@@ -5,17 +5,14 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
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 byte[] Generate(DeliveryNote deliveryNote, List<ProductDelivery> lignes, Setting setting)
|
||||
public async Task<byte[]> Generate(DeliveryNote deliveryNote, Setting setting, StorageService storageService)
|
||||
{
|
||||
byte[] logo = Convert.FromBase64String(setting.Logo!);
|
||||
byte[] signature = Convert.FromBase64String(setting.ElectronicSignature!);
|
||||
byte[] logoBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.Logo!));
|
||||
byte[] signatureBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.ElectronicSignature!));
|
||||
decimal total = 0;
|
||||
int totalQuantity = 0;
|
||||
Document document = Document.Create(container =>
|
||||
@@ -48,7 +45,7 @@ public class DeliveryNotePdfService : IDeliveryNotePdfService
|
||||
// Logo + société à droite
|
||||
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().AlignRight().Text("Pyro-Fêtes").SemiBold();
|
||||
col.Item().Height(5);
|
||||
@@ -93,7 +90,7 @@ public class DeliveryNotePdfService : IDeliveryNotePdfService
|
||||
header.Cell().Element(CellHeader).AlignRight().Text("Total");
|
||||
});
|
||||
|
||||
foreach (ProductDelivery l in lignes)
|
||||
foreach (ProductDelivery l in deliveryNote.ProductDeliveries!)
|
||||
{
|
||||
decimal price = l.Product!.Prices!
|
||||
.FirstOrDefault(x => x.SupplierId == l.DeliveryNote!.SupplierId && x.ProductId == l.ProductId)
|
||||
@@ -125,7 +122,7 @@ public class DeliveryNotePdfService : IDeliveryNotePdfService
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
||||
public interface IPurchaseOrderPdfService
|
||||
public class PurchaseOrderPdfService
|
||||
{
|
||||
byte[] Generate(PurchaseOrder purchaseOrder, List<PurchaseProduct> lignes, Setting setting);
|
||||
}
|
||||
|
||||
public class PurchaseOrderPdfService : IPurchaseOrderPdfService
|
||||
{
|
||||
public byte[] Generate(PurchaseOrder purchaseOrder, List<PurchaseProduct> lignes, Setting setting)
|
||||
private static readonly HttpClient HttpClient = new();
|
||||
|
||||
public async Task<byte[]> Generate(PurchaseOrder purchaseOrder, Setting setting, StorageService storageService)
|
||||
{
|
||||
byte[] logo = Convert.FromBase64String(setting.Logo!);
|
||||
byte[] signature = Convert.FromBase64String(setting.ElectronicSignature!);
|
||||
byte[] logoBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.Logo!));
|
||||
byte[] signatureBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.ElectronicSignature!));
|
||||
int totalQuantity = 0;
|
||||
decimal total = 0;
|
||||
Document document = Document.Create(container =>
|
||||
@@ -45,7 +42,7 @@ public class PurchaseOrderPdfService : IPurchaseOrderPdfService
|
||||
// Logo + société à droite
|
||||
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().AlignRight().Text("Pyro-Fêtes").SemiBold();
|
||||
col.Item().Height(5);
|
||||
@@ -93,7 +90,7 @@ public class PurchaseOrderPdfService : IPurchaseOrderPdfService
|
||||
header.Cell().Element(CellHeader).AlignRight().Text("Total");
|
||||
});
|
||||
|
||||
foreach (PurchaseProduct l in lignes)
|
||||
foreach (PurchaseProduct l in purchaseOrder.PurchaseProducts!)
|
||||
{
|
||||
decimal price = l.Product!.Prices!
|
||||
.FirstOrDefault(x => x.SupplierId == l.PurchaseOrder!.SupplierId && x.ProductId == l.ProductId)
|
||||
@@ -141,7 +138,7 @@ public class PurchaseOrderPdfService : IPurchaseOrderPdfService
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
||||
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 byte[] Generate(Quotation quotation, List<QuotationProduct> lignes, Setting setting)
|
||||
public async Task<byte[]> Generate(Quotation quotation, Setting setting, StorageService storageService)
|
||||
{
|
||||
byte[] logo = Convert.FromBase64String(setting.Logo!);
|
||||
byte[] signature = Convert.FromBase64String(setting.ElectronicSignature!);
|
||||
byte[] logoBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.Logo!));
|
||||
byte[] signatureBytes = await HttpClient.GetByteArrayAsync(storageService.GetUrl(setting.ElectronicSignature!));
|
||||
decimal total = 0;
|
||||
Document document = Document.Create(container =>
|
||||
{
|
||||
@@ -42,7 +39,7 @@ public class QuotationPdfService : IQuotationPdfService
|
||||
// Logo + société à droite
|
||||
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().AlignRight().Text("Pyro-Fêtes").SemiBold();
|
||||
col.Item().Height(5);
|
||||
@@ -90,7 +87,7 @@ public class QuotationPdfService : IQuotationPdfService
|
||||
header.Cell().Element(CellHeader).AlignRight().Text("Total");
|
||||
});
|
||||
|
||||
foreach (QuotationProduct l in lignes)
|
||||
foreach (QuotationProduct l in quotation.QuotationProducts!)
|
||||
{
|
||||
decimal price = l.Product!.Prices!
|
||||
.FirstOrDefault(x => x.SupplierId == l.Quotation!.SupplierId && x.ProductId == l.ProductId)
|
||||
@@ -134,7 +131,7 @@ public class QuotationPdfService : IQuotationPdfService
|
||||
});
|
||||
|
||||
// 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); });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,4 +140,4 @@ public class QuotationPdfService : IQuotationPdfService
|
||||
|
||||
return document.GeneratePdf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)(?=.*?[#?_!@$%^&*-])");
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,11 @@
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"RustFS": {
|
||||
"ServiceUrl": "https://stockage.sanchezvende.fr",
|
||||
"AccessKey": "Admin_Beready_Exam2026",
|
||||
"SecretKey": "4dm1n-Pr0j_2026-B3r34d7",
|
||||
"BucketName": "images"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 d’approvisionnement**.
|
||||
Elle est conçue pour simplifier le travail des entreprises en offrant une vue en temps réel sur les produits, leurs fournisseurs et l’état des livraisons.
|
||||
> Application web de gestion des stocks, fournisseurs, devis, bons de commande et bons de livraison pour l'entreprise **PyroFêtes**.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Fonctionnalités principales
|
||||
## Sommaire
|
||||
|
||||
### 1️⃣ Suivi et réapprovisionnement des stocks
|
||||
- Définissez un **niveau minimal de stock** pour chaque produit.
|
||||
- Surveillez les **niveaux en temps réel** grâce à une interface claire.
|
||||
- Lorsqu’un produit atteint ou descend sous son seuil minimal, le système **génère automatiquement un bon de commande** pour le réapprovisionner.
|
||||
- [Contexte](#contexte)
|
||||
- [Fonctionnalités](#fonctionnalités)
|
||||
- [Stack technique](#stack-technique)
|
||||
- [Équipe](#équipe)
|
||||
|
||||
### 2️⃣ Gestion des fournisseurs
|
||||
- Enregistrez les informations complètes des fournisseurs : nom, adresse, coordonnées, produits fournis, délais de livraison.
|
||||
- **Associez un ou plusieurs fournisseurs** à chaque produit.
|
||||
- Lorsqu’un bon de commande est créé, le système **propose automatiquement les fournisseurs appropriés**.
|
||||
|
||||
### 3️⃣ Devis et bons de commande
|
||||
- Créez des **devis personnalisés** : sélection des produits, quantités, prix, ajout d’un 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 d’achat) et exportation en PDF.
|
||||
## Contexte
|
||||
|
||||
### 4️⃣ Suivi des livraisons
|
||||
- **Transformez un bon de commande en bon de livraison** dès l’expédition des produits par le fournisseur.
|
||||
- Enregistrez toutes les informations importantes : date d’expédition, transporteur, numéro de suivi, date prévue et date effective de livraison.
|
||||
- Recevez des **alertes en cas de retard**.
|
||||
- Gérez la **réception des produits** et vérifiez leur conformité.
|
||||
PyroFêtes cherchait à remplacer ses processus manuels de gestion des stocks et de génération de documents commerciaux par un outil centralisé. Les objectifs principaux sont :
|
||||
|
||||
---
|
||||
- Automatiser le réapprovisionnement
|
||||
- Gérer les fournisseurs et leurs conditions
|
||||
- Éditer et exporter les documents commerciaux (devis, bons de commande, bons de livraison)
|
||||
- Assurer un suivi fiable des livraisons et des réceptions
|
||||
|
||||
## 🗂️ Livrables prévus
|
||||
- **Modèle de données** : diagramme de classes commun à tous les groupes.
|
||||
- **Interface utilisateur** : maquettes ou prototypes interactifs.
|
||||
- **Code source commenté** pour une meilleure compréhension.
|
||||
- **Documentation technique** : description des fonctionnalités, architecture de l’application et API.
|
||||
|
||||
---
|
||||
## Fonctionnalités
|
||||
|
||||
## 👥 Équipe
|
||||
- **Mathys**
|
||||
- **Enzo**
|
||||
- **Cristiano**
|
||||
- **Arsène**
|
||||
### Gestion des stocks
|
||||
- Définition de seuils minimaux par produit
|
||||
- Visualisation en temps réel du stock courant
|
||||
- Alertes automatiques en cas de stock sous le seuil
|
||||
- Génération automatique de bons de commande
|
||||
|
||||
---
|
||||
### Gestion des fournisseurs
|
||||
- Enregistrement des fournisseurs (nom, adresse, coordonnées, conditions)
|
||||
- Gestion des délais de livraison par produit
|
||||
- Association de plusieurs fournisseurs à un produit (prix + délai)
|
||||
- Suggestion automatique du fournisseur le plus pertinent
|
||||
|
||||
## 🚀 Objectif
|
||||
Fournir un outil complet pour automatiser la gestion des stocks et des commandes, réduisant les erreurs humaines, améliorant le suivi des livraisons et facilitant la communication avec les fournisseurs.
|
||||
### Devis & Bons de commande
|
||||
- Création de devis et bons de commande (produits, quantités, prix, remises)
|
||||
- Personnalisation des documents (logo, message, conditions)
|
||||
- Export au format **PDF**
|
||||
|
||||
### Bons de livraison & Réceptions
|
||||
- Transformation d'un bon de commande validé en bon de livraison
|
||||
- Enregistrement des informations de livraison (transporteur, numéro de suivi, dates)
|
||||
- Alertes en cas de retard de livraison
|
||||
- Gestion des réceptions avec mise à jour automatique des stocks
|
||||
|
||||
|
||||
## Stack technique
|
||||
|
||||
| Couche | Technologie |
|
||||
|---|---|
|
||||
| **Front-end** | Angular + NG-ZORRO + Tailwind CSS |
|
||||
| **Back-end** | C# / .NET |
|
||||
| **API** | REST (C#) |
|
||||
| **Base de données** | SQL Server |
|
||||
| **Gestion des tâches** | YouTrack |
|
||||
| **Versioning** | Gitea |
|
||||
| **Communication** | Discord + Présentiel |
|
||||
|
||||
|
||||
## Équipe
|
||||
|
||||
| Membre | Rôle |
|
||||
|---|---|
|
||||
| Mathys Sanchez-Vendé | Développeur |
|
||||
| Enzo Norguet | Développeur |
|
||||
| Cristiano Henrique Gaspar | Développeur |
|
||||
| Arsène | Développeur |
|
||||
|
||||
**Clients :** Mr Thibault Ferrand, Mr Douguet
|
||||
|
||||
|
||||
## Sécurité
|
||||
|
||||
- Authentification sécurisée
|
||||
- Gestion des accès et des permissions par rôle (Commercial / Administrateur)
|
||||
- Protection des données utilisateurs
|
||||
|
||||
Reference in New Issue
Block a user