From c1961ebea277df303d4a5ec3b5bbc5fbfbfd52ef Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 20 Aug 2026 13:07:47 +0530 Subject: [PATCH] Add store KYC document management --- .../Store/Kyc/IStoreKycDocumentRepository.cs | 7 ++ .../Store/Kyc/StoreKycDocumentController.cs | 26 +++++++ .../Store/Kyc/StoreKycDocumentDTOs.cs | 5 ++ .../Store/Kyc/StoreKycDocumentRepository.cs | 13 ++++ .../Store/Kyc/StoreKycDocumentService.cs | 71 +++++++++++++++++++ Distribution/Program.cs | 3 + 6 files changed, 125 insertions(+) create mode 100644 Distribution/Features/Store/Kyc/IStoreKycDocumentRepository.cs create mode 100644 Distribution/Features/Store/Kyc/StoreKycDocumentController.cs create mode 100644 Distribution/Features/Store/Kyc/StoreKycDocumentDTOs.cs create mode 100644 Distribution/Features/Store/Kyc/StoreKycDocumentRepository.cs create mode 100644 Distribution/Features/Store/Kyc/StoreKycDocumentService.cs diff --git a/Distribution/Features/Store/Kyc/IStoreKycDocumentRepository.cs b/Distribution/Features/Store/Kyc/IStoreKycDocumentRepository.cs new file mode 100644 index 0000000..b7169ec --- /dev/null +++ b/Distribution/Features/Store/Kyc/IStoreKycDocumentRepository.cs @@ -0,0 +1,7 @@ +namespace Distribution.Features.Store.Kyc; + +public interface IStoreKycDocumentRepository +{ + Task> GetByStoreIdAsync(Guid storeId); + Task AddAsync(Models.StoreKycDocument entity); +} diff --git a/Distribution/Features/Store/Kyc/StoreKycDocumentController.cs b/Distribution/Features/Store/Kyc/StoreKycDocumentController.cs new file mode 100644 index 0000000..7175c82 --- /dev/null +++ b/Distribution/Features/Store/Kyc/StoreKycDocumentController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Distribution.Features.Store.Kyc; + +[ApiController] +[Route("api/stores/{storeId:guid}/kyc-documents")] +[Authorize] +public class StoreKycDocumentController(StoreKycDocumentService service) : ControllerBase +{ + [HttpGet] + [Authorize(Policy = "stores.view")] + public async Task GetByStoreId(Guid storeId) + { + var result = await service.GetByStoreIdAsync(storeId); + return result is null ? NotFound() : Ok(result); + } + + [HttpPost] + [Authorize(Policy = "stores.update")] + public async Task Upload(Guid storeId, [FromForm] string documentType, [FromForm] IFormFile file, CancellationToken ct) + { + var result = await service.UploadAsync(storeId, new UploadKycDocumentRequest(documentType, file), ct); + return result is null ? NotFound() : Ok(result); + } +} diff --git a/Distribution/Features/Store/Kyc/StoreKycDocumentDTOs.cs b/Distribution/Features/Store/Kyc/StoreKycDocumentDTOs.cs new file mode 100644 index 0000000..06ada8c --- /dev/null +++ b/Distribution/Features/Store/Kyc/StoreKycDocumentDTOs.cs @@ -0,0 +1,5 @@ +namespace Distribution.Features.Store.Kyc; + +public record UploadKycDocumentRequest(string DocumentType, IFormFile File); + +public record StoreKycDocumentResponse(Guid Id, Guid StoreId, string DocumentType, string FileReference, DateTime UploadedAt); diff --git a/Distribution/Features/Store/Kyc/StoreKycDocumentRepository.cs b/Distribution/Features/Store/Kyc/StoreKycDocumentRepository.cs new file mode 100644 index 0000000..8a4ae13 --- /dev/null +++ b/Distribution/Features/Store/Kyc/StoreKycDocumentRepository.cs @@ -0,0 +1,13 @@ +using Distribution.Data; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Features.Store.Kyc; + +public class StoreKycDocumentRepository(AppDBContext context) : IStoreKycDocumentRepository +{ + public async Task> GetByStoreIdAsync(Guid storeId) => + await context.StoreKycDocuments.Where(d => d.FK_StoreId == storeId && d.IsActive).ToListAsync(); + + public async Task AddAsync(Models.StoreKycDocument entity) => + await context.StoreKycDocuments.AddAsync(entity); +} diff --git a/Distribution/Features/Store/Kyc/StoreKycDocumentService.cs b/Distribution/Features/Store/Kyc/StoreKycDocumentService.cs new file mode 100644 index 0000000..1470cd9 --- /dev/null +++ b/Distribution/Features/Store/Kyc/StoreKycDocumentService.cs @@ -0,0 +1,71 @@ +using Distribution.Infra.Authorization; +using Distribution.Infra.UoW; + +namespace Distribution.Features.Store.Kyc; + +public class StoreKycDocumentService( + IStoreKycDocumentRepository repo, + IStoreRepository storeRepo, + IUnitOfWork uow, + ICurrentUserService currentUser, + IScopeAccessService scopeAccess, + IWebHostEnvironment env) +{ + private const int GlobalScopeRoleLevel = 50; + + private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel; + + private async Task HasStoreScopeAsync(Guid storeId) + { + if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope; + return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId); + } + + private static StoreKycDocumentResponse ToResponse(Models.StoreKycDocument d) => + new(d.Id, d.FK_StoreId, d.DocumentType, d.FileReference, d.UploadedAt); + + public async Task?> GetByStoreIdAsync(Guid storeId) + { + if (!await HasStoreScopeAsync(storeId)) return null; + var docs = await repo.GetByStoreIdAsync(storeId); + return docs.Select(ToResponse).ToList(); + } + + public async Task UploadAsync(Guid storeId, UploadKycDocumentRequest request, CancellationToken ct = default) + { + var store = await storeRepo.GetByIdAsync(storeId); + if (store is null || !store.IsActive) return null; + if (!await HasStoreScopeAsync(storeId)) return null; + + var uploadsRoot = Path.Combine(env.ContentRootPath, "wwwroot", "uploads", "kyc", storeId.ToString()); + Directory.CreateDirectory(uploadsRoot); + var fileName = $"{Guid.NewGuid()}{Path.GetExtension(request.File.FileName)}"; + var filePath = Path.Combine(uploadsRoot, fileName); + await using (var stream = new FileStream(filePath, FileMode.Create)) + { + await request.File.CopyToAsync(stream, ct); + } + var fileReference = $"uploads/kyc/{storeId}/{fileName}"; + + var doc = new Models.StoreKycDocument + { + FK_StoreId = storeId, + DocumentType = request.DocumentType, + FileReference = fileReference, + UploadedAt = DateTime.Now + }; + + await uow.BeginAsync(ct); + try + { + await repo.AddAsync(doc); + await uow.CommitAsync(ct); + } + catch + { + await uow.RollbackAsync(ct); + throw; + } + return ToResponse(doc); + } +} diff --git a/Distribution/Program.cs b/Distribution/Program.cs index 0461582..3569321 100644 --- a/Distribution/Program.cs +++ b/Distribution/Program.cs @@ -9,6 +9,7 @@ using Distribution.Features.Region; using Distribution.Features.Role; using Distribution.Features.Route; using Distribution.Features.Store; +using Distribution.Features.Store.Kyc; using Distribution.Features.Sync; using Distribution.Features.Uom; using Distribution.Features.User; @@ -46,6 +47,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -62,6 +64,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped();