diff --git a/Backend/src/ShoeStore.Api/Controllers/UserVoucherController.cs b/Backend/src/ShoeStore.Api/Controllers/UserVoucherController.cs
new file mode 100644
index 00000000..6a7398cc
--- /dev/null
+++ b/Backend/src/ShoeStore.Api/Controllers/UserVoucherController.cs
@@ -0,0 +1,57 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ShoeStore.Application.DTOs;
+using ShoeStore.Application.DTOs.VoucherDtos;
+using ShoeStore.Application.Interface.VoucherInterface;
+
+namespace ShoeStore.Api.Controllers;
+
+///
+/// Controller for managing user-specific voucher operations.
+/// Provides endpoints for users to retrieve their own vouchers.
+///
+/// Service for handling user-voucher relationship operations.
+[ApiController]
+[Route("api/user/vouchers")]
+[Authorize(Roles = "User")]
+public class UserVoucherController(IUserVoucherService userVoucherService) : ControllerBase
+{
+ ///
+ /// Retrieves all active vouchers associated with a specific user.
+ ///
+ ///
+ /// Requires User role authorization.
+ /// Returns a list of vouchers that are currently valid and assigned to the user.
+ ///
+ /// The unique identifier (GUID) of the user whose vouchers are being retrieved.
+ /// Cancellation token for the request.
+ /// Vouchers retrieved successfully.
+ /// Unauthorized; user must be authenticated with User role.
+ /// Not found; no vouchers found for the specified user.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result containing a paginated list of user vouchers on success, or an error response.
+ [ProducesResponseType(typeof(PageResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpGet("user/{userGuid}")]
+ public async Task GetVouchersForUser(Guid userGuid, CancellationToken token)
+ {
+ var result = await userVoucherService.GetAllVoucherForUserAsync(userGuid, token);
+ return result.Match(
+ vouchers => Ok(vouchers),
+ errors => errors[0].Code switch
+ {
+ "NO_VOUCHERS_FOUND" => NotFound(new
+ {
+ message = "No vouchers found for this user",
+ detail = errors[0].Description
+ }),
+ _ => BadRequest(new
+ {
+ message = "Failed to retrieve vouchers for user",
+ detail = errors[0].Description
+ })
+ });
+ }
+}
diff --git a/Backend/src/ShoeStore.Api/Controllers/VoucherController.cs b/Backend/src/ShoeStore.Api/Controllers/VoucherController.cs
new file mode 100644
index 00000000..54d840fb
--- /dev/null
+++ b/Backend/src/ShoeStore.Api/Controllers/VoucherController.cs
@@ -0,0 +1,243 @@
+using ErrorOr;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ShoeStore.Application.DTOs;
+using ShoeStore.Application.DTOs.VoucherDtos;
+using ShoeStore.Application.Interface.VoucherInterface;
+
+namespace ShoeStore.Api.Controllers;
+
+///
+/// Controller for managing vouchers in the system.
+/// Provides endpoints for voucher creation, retrieval, update, and deletion (Admin only).
+///
+/// Service for handling voucher logic operations.
+[ApiController]
+[Route("api/admin/vouchers")]
+[Authorize(Roles = "Admin")]
+public class VoucherController(IVoucherService voucherService) : ControllerBase
+{
+ ///
+ /// Creates a new voucher for the store.
+ ///
+ ///
+ /// Requires Admin role authorization.
+ /// The request body should include:
+ /// - VoucherName: Name of the voucher
+ /// - Discount: Value of the discount
+ /// - DiscountType: Type of discount (Percentage/FixedAmount)
+ /// - TotalQuantity: Number of vouchers available
+ /// - ValidFrom/ValidTo: Expiration dates
+ ///
+ /// Data transfer object containing voucher creation details.
+ /// Cancellation token for the request.
+ /// Voucher created successfully.
+ /// Bad request; invalid voucher data provided.
+ /// Unauthorized; user must be authenticated with Admin role.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result with status 201 (Created) on success, or an error response.
+ [ProducesResponseType(typeof(object), StatusCodes.Status201Created)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpPost]
+ public async Task CreateVoucher([FromBody] CreateVoucherDto createVoucherDto, CancellationToken token)
+ {
+ var result = await voucherService.CreateVoucherAsync(createVoucherDto, token);
+
+ return result.Match(
+ _ => Created("", new { message = "Voucher created successfully" }),
+ errors => BadRequest(new
+ {
+ message = "Failed to create voucher",
+ detail = errors[0].Description
+ }));
+ }
+
+ ///
+ /// Updates an existing voucher's details.
+ ///
+ ///
+ /// Requires Admin role authorization.
+ /// Updates the specified voucher with the provided information.
+ ///
+ /// The unique identifier (GUID) of the voucher to update.
+ /// Data transfer object containing updated voucher details.
+ /// Cancellation token for the request.
+ /// Voucher updated successfully.
+ /// Bad request; invalid update data provided.
+ /// Unauthorized; user must be authenticated with Admin role.
+ /// Not found; the voucher with the specified ID does not exist.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result with status 200 (OK) on success, or an error response.
+ [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpPut("{voucherGuid}")]
+ public async Task UpdateVoucher(Guid voucherGuid, [FromBody] UpdateVoucherDto updateVoucherDto,
+ CancellationToken token)
+ {
+ var result = await voucherService.UpdateVoucherAsync(voucherGuid, updateVoucherDto, token);
+ return result.Match(
+ _ => Ok(new { message = "Voucher updated successfully" }),
+ errors => errors[0].Code switch
+ {
+ "VOUCHER_NOT_FOUND" => NotFound(new
+ {
+ message = "Voucher not found",
+ detail = errors[0].Description
+ }),
+ _ => BadRequest(new
+ {
+ message = "Failed to update voucher",
+ detail = errors[0].Description
+ })
+ });
+ }
+
+ ///
+ /// Deletes a specific voucher from the system (soft delete).
+ ///
+ ///
+ /// Requires Admin role authorization.
+ /// Performs a soft delete by marking the voucher as deleted.
+ ///
+ /// The unique identifier (GUID) of the voucher to delete.
+ /// Cancellation token for the request.
+ /// Voucher deleted successfully.
+ /// Unauthorized; user must be authenticated with Admin role.
+ /// Not found; the voucher with the specified ID does not exist.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result with status 200 (OK) on success, or an error response.
+ [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpDelete("{voucherGuid}")]
+ public async Task DeleteVoucher(Guid voucherGuid, CancellationToken token)
+ {
+ var result = await voucherService.DeleteVoucherByGuidAsync(voucherGuid, token);
+ return result.Match(
+ _ => Ok(new { message = "Voucher deleted successfully" }),
+ errors => errors[0].Code switch
+ {
+ "VOUCHER_NOT_FOUND" => NotFound(new
+ {
+ message = "Voucher not found",
+ detail = errors[0].Description
+ }),
+ _ => BadRequest(new
+ {
+ message = "Failed to delete voucher",
+ detail = errors[0].Description
+ })
+ });
+ }
+
+ ///
+ /// Deletes all expired vouchers from the system (soft delete).
+ ///
+ ///
+ /// Requires Admin role authorization.
+ /// Identifies and soft deletes all vouchers whose expiration date has passed.
+ ///
+ /// Cancellation token for the request.
+ /// Expired vouchers deleted successfully.
+ /// Bad request; failed to delete expired vouchers or no expired vouchers found.
+ /// Unauthorized; user must be authenticated with Admin role.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result with status 200 (OK) on success, or an error response.
+ [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpDelete("expire")]
+ public async Task DeleteExpiredVouchers(CancellationToken token)
+ {
+ var result = await voucherService.DeleteVoucherExpireAsync(token);
+ return result.Match(
+ _ => Ok(new { message = "Expired vouchers deleted successfully" }),
+ errors => BadRequest(new
+ {
+ message = "Failed to delete expired vouchers",
+ detail = errors[0].Description
+ }));
+ }
+
+ ///
+ /// Retrieves a paginated list of vouchers for administrative purposes.
+ ///
+ ///
+ /// Requires Admin role authorization.
+ /// Provides a list of vouchers with detailed information relevant for administrators.
+ ///
+ /// Cancellation token for the request.
+ /// Vouchers retrieved successfully.
+ /// Unauthorized; user must be authenticated with Admin role.
+ /// Not found; no vouchers found in the system.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result containing a paginated list of vouchers on success, or an error response.
+ [ProducesResponseType(typeof(PageResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpGet]
+ public async Task GetVouchersForAdmin(CancellationToken token)
+ {
+ var result = await voucherService.GetVoucherForAdminAsync(token);
+ return result.Match(
+ vouchers => Ok(vouchers),
+ errors => errors[0].Code switch
+ {
+ "NO_VOUCHERS_FOUND" => NotFound(new
+ {
+ message = "No vouchers found",
+ detail = errors[0].Description
+ }),
+ _ => BadRequest(new
+ {
+ message = "Failed to retrieve vouchers",
+ detail = errors[0].Description
+ })
+ });
+ }
+
+ ///
+ /// Retrieves all active and non-deleted vouchers in the system.
+ ///
+ ///
+ /// Requires Admin role authorization.
+ ///
+ /// Cancellation token for the request.
+ /// All vouchers retrieved successfully.
+ /// Unauthorized; user must be authenticated with Admin role.
+ /// Not found; no vouchers found in the system.
+ /// Internal server error; an unexpected error occurred.
+ /// An action result containing a list of vouchers on success, or an error response.
+ [ProducesResponseType(typeof(PageResult), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
+ [HttpGet("all")]
+ public async Task GetAllVouchers(CancellationToken token)
+ {
+ var result = await voucherService.GetAllVouchersAsync(token);
+ return result.Match(
+ vouchers => Ok(vouchers),
+ errors => errors[0].Code switch
+ {
+ "NO_VOUCHERS_FOUND" => NotFound(new
+ {
+ message = "No vouchers found",
+ detail = errors[0].Description
+ }),
+ _ => BadRequest(new
+ {
+ message = "Failed to retrieve all vouchers",
+ detail = errors[0].Description
+ })
+ });
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs
new file mode 100644
index 00000000..d18c7d7b
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs
@@ -0,0 +1,29 @@
+
+namespace ShoeStore.Application.DTOs.VoucherDtos
+{
+ public class CreateVoucherDto
+ {
+ public string? VoucherName { get; set; }
+ public string? VoucherDescription { get; set; }
+ public decimal? Discount { get; set; }
+ public int VoucherScope { get; set; } = 1; // Default to AllProducts
+ public int DiscountType { get; set; } = 1; // Default to Percentage
+ public decimal MaxPriceDiscount { get; set; }
+ private DateTime? _validFrom;
+ public DateTime? ValidFrom
+ {
+ get => _validFrom;
+ set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+
+ private DateTime? _validTo;
+ public DateTime? ValidTo
+ {
+ get => _validTo;
+ set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+ public int? MaxUsagePerUser { get; set; }
+ public int? TotalQuantity { get; set; }
+ public decimal? MinOrderPrice { get; set; }
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/DeleteVoucherDto.cs b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/DeleteVoucherDto.cs
new file mode 100644
index 00000000..5a1f142e
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/DeleteVoucherDto.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ShoeStore.Application.DTOs.VoucherDtos
+{
+ public class DeleteVoucherDto
+ {
+ public bool IsDeleted { get; set; } = true;
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherAdminDto.cs b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherAdminDto.cs
new file mode 100644
index 00000000..04b00391
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherAdminDto.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ShoeStore.Application.DTOs.VoucherDtos
+{
+ public class ResponseVoucherAdminDto
+ {
+ public string? VoucherName { get; set; }
+ public decimal Discount { get; set; } = 0;
+ public int? VoucherScope { get; set; }
+ public int? DiscountType { get; set; }
+ public decimal? MaxPriceDiscount { get; set; }
+ public decimal? MinOrderPrice { get; set; }
+ private DateTime? _validFrom;
+ public DateTime? ValidFrom
+ {
+ get => _validFrom;
+ set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+
+ private DateTime? _validTo;
+ public DateTime? ValidTo
+ {
+ get => _validTo;
+ set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherUserDto.cs b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherUserDto.cs
new file mode 100644
index 00000000..5e6bbf4a
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherUserDto.cs
@@ -0,0 +1,23 @@
+
+namespace ShoeStore.Application.DTOs.VoucherDtos
+{
+ public class ResponseVoucherUserDto
+ {
+ public string VoucherName { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public decimal Discount { get; set; } = 0;
+ private DateTime? _validFrom;
+ public DateTime? ValidFrom
+ {
+ get => _validFrom;
+ set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+
+ private DateTime? _validTo;
+ public DateTime? ValidTo
+ {
+ get => _validTo;
+ set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs
new file mode 100644
index 00000000..1da41e96
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs
@@ -0,0 +1,27 @@
+namespace ShoeStore.Application.DTOs.VoucherDtos
+{
+ public class UpdateVoucherDto
+ {
+ public string? VoucherDescription { get; set; }
+ public decimal? Discount { get; set; }
+ public int VoucherScope { get; set; } = 1; // Default to AllProducts
+ public int DiscountType { get; set; } = 1; // Default to Percentage
+ public decimal MaxPriceDiscount { get; set; }
+ private DateTime? _validFrom;
+ public DateTime? ValidFrom
+ {
+ get => _validFrom;
+ set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+
+ private DateTime? _validTo;
+ public DateTime? ValidTo
+ {
+ get => _validTo;
+ set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ }
+ public int? MaxUsagePerUser { get; set; }
+ public int? TotalQuantity { get; set; }
+ public decimal? MinOrderPrice { get; set; }
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IUserVoucherRepository.cs b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IUserVoucherRepository.cs
new file mode 100644
index 00000000..b32bc5be
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IUserVoucherRepository.cs
@@ -0,0 +1,10 @@
+using ShoeStore.Domain.Entities;
+
+namespace ShoeStore.Application.Interface.VoucherInterface
+{
+ public interface IUserVoucherRepository
+ {
+ IQueryable GetAllVouchers();
+ IQueryable GetVouchersByUserGuid(Guid userGuid);
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IUserVoucherService.cs b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IUserVoucherService.cs
new file mode 100644
index 00000000..fbc58797
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IUserVoucherService.cs
@@ -0,0 +1,10 @@
+using ShoeStore.Application.DTOs.VoucherDtos;
+using ShoeStore.Application.DTOs;
+using ErrorOr;
+namespace ShoeStore.Application.Interface.VoucherInterface
+{
+ public interface IUserVoucherService : IVoucherService
+ {
+ Task>> GetAllVoucherForUserAsync(Guid UserGuid, CancellationToken token);
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs
new file mode 100644
index 00000000..117cf165
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs
@@ -0,0 +1,11 @@
+using ShoeStore.Application.Interface.Common;
+using ShoeStore.Domain.Entities;
+
+namespace ShoeStore.Application.Interface.VoucherInterface
+{
+ public interface IVoucherRepository : IGenericRepository
+ {
+ IQueryable GetAllVouchers();
+ IQueryable GetVoucherByGuid(Guid voucherGuid);
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs
new file mode 100644
index 00000000..d364bbab
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs
@@ -0,0 +1,19 @@
+using ShoeStore.Application.DTOs.VoucherDtos;
+using ErrorOr;
+using ShoeStore.Application.DTOs;
+namespace ShoeStore.Application.Interface.VoucherInterface
+{
+ public interface IVoucherService
+ {
+ // CREATE
+ Task> CreateVoucherAsync(CreateVoucherDto voucherCreateDto, CancellationToken token);
+ // GET
+ Task>> GetVoucherForAdminAsync(CancellationToken token);
+ Task>> GetAllVouchersAsync(CancellationToken token);
+ // UPDATE
+ Task> UpdateVoucherAsync(Guid voucherGuid, UpdateVoucherDto voucherUpdateDto, CancellationToken token);
+ // DELETE
+ Task> DeleteVoucherByGuidAsync(Guid voucherGuid, CancellationToken token );
+ Task> DeleteVoucherExpireAsync(CancellationToken token);
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/Services/VoucherService.cs b/Backend/src/ShoeStore.Application/Services/VoucherService.cs
new file mode 100644
index 00000000..903fdc80
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/Services/VoucherService.cs
@@ -0,0 +1,230 @@
+using ShoeStore.Application.Interface.VoucherInterface;
+using ShoeStore.Application.DTOs.VoucherDtos;
+using ErrorOr;
+using ShoeStore.Application.Interface.Common;
+using ShoeStore.Domain.Entities;
+using ShoeStore.Domain.Enum;
+using Microsoft.EntityFrameworkCore;
+using ShoeStore.Application.DTOs;
+
+namespace ShoeStore.Application.Services
+{
+ public class VoucherService : IVoucherService, IUserVoucherService
+ {
+ private readonly IVoucherRepository voucherRepository;
+ private readonly IUnitOfWork uow;
+ private readonly IUserVoucherRepository userVoucherRepository;
+
+ public VoucherService(IVoucherRepository voucherRepository, IUnitOfWork uow, IUserVoucherRepository userVoucherRepository)
+ {
+ this.voucherRepository = voucherRepository;
+ this.uow = uow;
+ this.userVoucherRepository = userVoucherRepository;
+ }
+ public async Task> CreateVoucherAsync(CreateVoucherDto voucherCreateDto, CancellationToken token)
+ {
+
+ var voucher = new Voucher
+ {
+ VoucherName = voucherCreateDto.VoucherName ?? string.Empty,
+ VoucherDescription = voucherCreateDto.VoucherDescription,
+ Discount = voucherCreateDto.Discount ?? 0,
+ VoucherScope = (VoucherScope)voucherCreateDto.VoucherScope,
+ DiscountType = (DiscountType)voucherCreateDto.DiscountType,
+ MaxPriceDiscount = voucherCreateDto.MaxPriceDiscount,
+ ValidFrom = voucherCreateDto.ValidFrom,
+ ValidTo = voucherCreateDto.ValidTo,
+ MaxUsagePerUser = voucherCreateDto.MaxUsagePerUser,
+ TotalQuantity = voucherCreateDto.TotalQuantity ?? 0,
+ MinOrderPrice = voucherCreateDto.MinOrderPrice ?? 0,
+ IsDeleted = false
+ };
+
+ voucherRepository.Add(voucher);
+ await uow.SaveChangesAsync(token);
+ return Result.Created;
+
+ }
+
+ public async Task> DeleteVoucherByGuidAsync(Guid voucherGuid, CancellationToken token)
+ {
+ var voucher = await voucherRepository
+ .GetVoucherByGuid(voucherGuid)
+ .Where(v => !v.IsDeleted)
+ .FirstOrDefaultAsync();
+ if (voucher == null)
+ {
+ return Error.NotFound(
+ "VOUCHER_NOT_FOUND",
+ "The voucher with the specified GUID does not exist."
+ );
+ }
+ // Soft delete logic
+ voucher.IsDeleted = true;
+ voucher.UpdatedAt = DateTime.UtcNow;
+ voucherRepository.Update(voucher);
+ await uow.SaveChangesAsync(token);
+ return Result.Deleted;
+ }
+
+ public async Task> DeleteVoucherExpireAsync(CancellationToken token)
+ {
+ var vouchersToDelete = voucherRepository
+ .GetAllVouchers()
+ .Where(v => v.ValidTo < DateTime.UtcNow && !v.IsDeleted)
+ .ToList();
+ if(!vouchersToDelete.Any())
+ {
+ return Error.NotFound(
+ "NO_EXPIRED_VOUCHERS",
+ "There are no expired vouchers to delete."
+ );
+ }
+ foreach (var voucher in vouchersToDelete)
+ {
+ voucher.IsDeleted = true;
+ voucher.UpdatedAt = DateTime.UtcNow;
+ voucherRepository.Update(voucher);
+ }
+ await uow.SaveChangesAsync(token);
+ return Result.Deleted;
+ }
+
+ public async Task>> GetAllVoucherForUserAsync(Guid UserGuid, CancellationToken token)
+ {
+ var vouchers = await userVoucherRepository
+ .GetVouchersByUserGuid(UserGuid)
+ .Where(v => !v.Voucher.IsDeleted && v.Voucher.ValidTo > DateTime.UtcNow)
+ .Select(v => new ResponseVoucherUserDto
+ {
+ VoucherName = v.Voucher.VoucherName ?? string.Empty,
+ Description = v.Voucher.VoucherDescription ?? string.Empty,
+ Discount = v.Voucher.Discount,
+ ValidFrom = v.Voucher.ValidFrom,
+ ValidTo = v.Voucher.ValidTo
+ })
+ .ToListAsync(token);
+
+ if(vouchers == null || !vouchers.Any())
+ {
+ return Error.NotFound(
+ "NO_VOUCHERS_FOUND",
+ "No vouchers were found for the user."
+ );
+ }
+
+ var result = new PageResult
+ {
+ Items = vouchers,
+ TotalCount = vouchers.Count
+ };
+ return result;
+ }
+
+ public async Task>> GetAllVouchersAsync(CancellationToken token)
+ {
+ var vouchers = await voucherRepository
+ .GetAllVouchers()
+ .Where(v => !v.IsDeleted)
+ .Select(v => new ResponseVoucherAdminDto
+ {
+ VoucherName = v.VoucherName,
+ Discount = v.Discount,
+ VoucherScope = (int)v.VoucherScope,
+ DiscountType = (int)v.DiscountType,
+ MaxPriceDiscount = v.MaxPriceDiscount,
+ ValidFrom = v.ValidFrom,
+ ValidTo = v.ValidTo,
+ MinOrderPrice = v.MinOrderPrice
+ })
+ .ToListAsync(token);
+ if(vouchers == null || !vouchers.Any())
+ {
+ return Error.NotFound(
+ "NO_VOUCHERS_FOUND",
+ "Dont have voucher created"
+ );
+ }
+
+ var pageResult = new PageResult
+ {
+ Items = vouchers,
+ TotalCount = vouchers.Count
+ };
+ return pageResult;
+
+ }
+
+ public async Task>> GetVoucherForAdminAsync(CancellationToken token)
+ {
+ var vouchers = await voucherRepository
+ .GetAllVouchers()
+ .Where(v => !v.IsDeleted)
+ .Select(v => new ResponseVoucherAdminDto
+ {
+ VoucherName = v.VoucherName,
+ Discount = v.Discount,
+ VoucherScope = (int)v.VoucherScope,
+ DiscountType = (int)v.DiscountType,
+ MaxPriceDiscount = v.MaxPriceDiscount,
+ ValidFrom = v.ValidFrom,
+ ValidTo = v.ValidTo,
+ MinOrderPrice = v.MinOrderPrice
+ })
+ .ToListAsync();
+ if(vouchers == null || !vouchers.Any())
+ {
+ return Error.NotFound(
+ "NO_VOUCHERS_FOUND",
+ "No vouchers were found in the system."
+ );
+ }
+
+ var pageResult = new PageResult
+ {
+ Items = vouchers,
+ TotalCount = vouchers.Count
+ };
+ return pageResult;
+ }
+
+ public async Task> UpdateVoucherAsync(Guid voucherGuid, UpdateVoucherDto voucherUpdateDto, CancellationToken token)
+ {
+ var voucher = await voucherRepository
+ .GetVoucherByGuid(voucherGuid)
+ .FirstOrDefaultAsync();
+
+ if (voucher == null)
+ {
+ return Error.NotFound(
+ "VOUCHER_NOT_FOUND",
+ "The voucher with the specified GUID does not exist."
+ );
+ }
+
+ // Update logic
+ voucher.VoucherDescription = voucherUpdateDto.VoucherDescription ?? voucher.VoucherDescription;
+
+ voucher.VoucherScope = (VoucherScope)voucherUpdateDto.VoucherScope;
+ voucher.DiscountType = (DiscountType)voucherUpdateDto.DiscountType;
+
+ voucher.MaxPriceDiscount = voucherUpdateDto.MaxPriceDiscount;
+
+ voucher.ValidFrom = voucherUpdateDto.ValidFrom ?? voucher.ValidFrom;
+ voucher.ValidTo = voucherUpdateDto.ValidTo ?? voucher.ValidTo;
+
+ voucher.MaxUsagePerUser = voucherUpdateDto.MaxUsagePerUser ?? voucher.MaxUsagePerUser;
+ voucher.TotalQuantity = voucherUpdateDto.TotalQuantity ?? voucher.TotalQuantity;
+ voucher.MinOrderPrice = voucherUpdateDto.MinOrderPrice ?? voucher.MinOrderPrice;
+
+ voucher.UpdatedAt = DateTime.UtcNow;
+
+ voucherRepository.Update(voucher);
+ await uow.SaveChangesAsync(token);
+
+ return Result.Updated;
+
+ }
+
+ }
+}
diff --git a/Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs b/Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs
new file mode 100644
index 00000000..63e52f2d
--- /dev/null
+++ b/Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs
@@ -0,0 +1,26 @@
+using FluentValidation;
+using ShoeStore.Application.DTOs.VoucherDtos;
+
+namespace ShoeStore.Application.Validations.VoucherValidation;
+
+public class CreateVoucherDtoValidation : AbstractValidator
+{
+ public CreateVoucherDtoValidation()
+ {
+ RuleFor(x => x.VoucherName)
+ .NotEmpty().WithMessage("Voucher name is required")
+ .MaximumLength(100).WithMessage("Voucher name must not exceed 100 characters");
+
+ RuleFor(x => x.Discount)
+ .NotNull().WithMessage("Discount is required")
+ .GreaterThanOrEqualTo(0).WithMessage("Discount must be greater than or equal to 0");
+
+ RuleFor(x => x.TotalQuantity)
+ .GreaterThan(0).WithMessage("Total quantity must be greater than 0");
+
+ RuleFor(x => x.ValidFrom)
+ .LessThanOrEqualTo(x => x.ValidTo)
+ .When(x => x.ValidFrom.HasValue && x.ValidTo.HasValue)
+ .WithMessage("Valid from date must be less than or equal to valid to date");
+ }
+}
diff --git a/Backend/src/ShoeStore.Infrastructure/DependencyInjection/DependencyInjection.cs b/Backend/src/ShoeStore.Infrastructure/DependencyInjection/DependencyInjection.cs
index 7e142081..898da647 100644
--- a/Backend/src/ShoeStore.Infrastructure/DependencyInjection/DependencyInjection.cs
+++ b/Backend/src/ShoeStore.Infrastructure/DependencyInjection/DependencyInjection.cs
@@ -20,6 +20,7 @@
using ShoeStore.Infrastructure.Notification;
using ShoeStore.Infrastructure.Repositories;
using ShoeStore.Infrastructure.RestorePassService;
+using ShoeStore.Application.Interface.VoucherInterface;
namespace ShoeStore.Infrastructure.DependencyInjection;
@@ -62,6 +63,10 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
return services;
}
}
\ No newline at end of file
diff --git a/Backend/src/ShoeStore.Infrastructure/Repositories/UserVoucherRepository.cs b/Backend/src/ShoeStore.Infrastructure/Repositories/UserVoucherRepository.cs
new file mode 100644
index 00000000..e8c83c4b
--- /dev/null
+++ b/Backend/src/ShoeStore.Infrastructure/Repositories/UserVoucherRepository.cs
@@ -0,0 +1,20 @@
+using ShoeStore.Application.Interface.VoucherInterface;
+using ShoeStore.Domain.Entities;
+using ShoeStore.Infrastructure.Data;
+
+namespace ShoeStore.Infrastructure.Repositories
+{
+ public class UserVoucherRepository(AppDbContext context) : IUserVoucherRepository
+ {
+ public IQueryable GetAllVouchers()
+ {
+ return context.UserVouchers;
+ }
+
+ public IQueryable GetVouchersByUserGuid(Guid userGuid)
+ {
+ return context.UserVouchers
+ .Where(uv => uv.User != null && uv.User.PublicId == userGuid);
+ }
+ }
+}
diff --git a/Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs b/Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs
new file mode 100644
index 00000000..ee912222
--- /dev/null
+++ b/Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using ShoeStore.Application.Interface.VoucherInterface;
+using ShoeStore.Domain.Entities;
+using ShoeStore.Infrastructure.Data;
+
+namespace ShoeStore.Infrastructure.Repositories
+{
+ public class VoucherRepository(AppDbContext context) : GenericRepository(context), IVoucherRepository
+ {
+ public IQueryable GetAllVouchers()
+ {
+ return context.Vouchers.AsNoTracking();
+ }
+
+ public IQueryable GetVoucherByGuid(Guid voucherGuid)
+ {
+ return context.Vouchers.Where(v => v.PublicId == voucherGuid).AsNoTracking();
+ }
+ }
+}