Skip to content

Feature/voucher#200

Draft
Hoang44444 wants to merge 10 commits intomainfrom
feature/voucher
Draft

Feature/voucher#200
Hoang44444 wants to merge 10 commits intomainfrom
feature/voucher

Conversation

@Hoang44444
Copy link
Copy Markdown
Collaborator

@Hoang44444 Hoang44444 commented Apr 19, 2026

Summary by CodeRabbit

  • New Features
    • Added voucher management functionality with API endpoints to create and update vouchers.
    • Vouchers support configurable discount settings, validity date ranges, and usage constraints.
    • Implemented validation for voucher data including name requirements, discount amounts, and date range checks.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 58a419be-5afe-41c2-b937-d99973339f42

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a complete voucher management feature for the backend, including a REST API controller with create and update endpoints, DTOs, service layer implementation with business logic, repository layer for data access, and validation rules using FluentValidation. The feature spans from API controllers through the application and infrastructure layers with dependency injection setup.

Changes

Cohort / File(s) Summary
API Controller
Backend/src/ShoeStore.Api/Controllers/VoucherController.cs
New controller with two endpoints: CreateVoucher (POST) and UpdateVoucher (PUT) that delegate to the voucher service and return appropriate HTTP responses (201 Created, 200 OK, 404 NotFound, 400 BadRequest).
Data Transfer Objects
Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs, UpdateVoucherDto.cs, ResponseVoucherDto.cs
Three new DTOs for voucher operations. CreateVoucherDto and UpdateVoucherDto include nullable properties for voucher metadata, discount parameters, scope/usage constraints, and UTC-normalizing date properties. ResponseVoucherDto is an empty placeholder.
Service Layer
Backend/src/ShoeStore.Application/Services/VoucherService.cs
Implements IVoucherService with CreateVoucherAsync (maps DTO to entity with defaults) and UpdateVoucherAsync (fetches by GUID, updates mutable fields, persists changes).
Service & Repository Interfaces
Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs, IVoucherRepository.cs
Interface definitions for voucher service operations and repository queries. IVoucherRepository extends the generic repository with GetAllVouchers() and GetVoucherByGuid() methods.
Repository Implementation
Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs
Concrete repository implementing IVoucherRepository with two query methods returning untracked IQueryable<Voucher> for deferred execution.
Validation
Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs
FluentValidation rules for CreateVoucherDto validating voucher name (non-empty, max 100 chars), discount (non-null, >= 0), quantity (> 0), and date range (ValidFrom <= ValidTo).
Dependency Injection
Backend/src/ShoeStore.Infrastructure/DependencyInjection/DependencyInjection.cs
Registers IVoucherRepositoryVoucherRepository and IVoucherServiceVoucherService as scoped dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as VoucherController
    participant Service as VoucherService
    participant Repository as VoucherRepository
    participant Database as AppDbContext

    Client->>Controller: POST /api/admin/vouchers<br/>(CreateVoucherDto)
    Controller->>Service: CreateVoucherAsync(dto, token)
    Service->>Service: Map DTO to Voucher entity<br/>Apply defaults
    Service->>Repository: Add(voucher)
    Service->>Database: SaveChangesAsync(token)
    Database-->>Service: Persisted
    Service-->>Controller: ErrorOr<Created>
    Controller-->>Client: 201 Created / 400 BadRequest
Loading
sequenceDiagram
    participant Client
    participant Controller as VoucherController
    participant Service as VoucherService
    participant Repository as VoucherRepository
    participant Database as AppDbContext

    Client->>Controller: PUT /api/admin/vouchers/{guid}<br/>(UpdateVoucherDto)
    Controller->>Service: UpdateVoucherAsync(guid, dto, token)
    Service->>Repository: GetVoucherByGuid(guid)
    Repository->>Database: Query Vouchers by PublicId
    Database-->>Repository: Voucher entity (or null)
    Repository-->>Service: IQueryable<Voucher>
    Service->>Service: FirstOrDefaultAsync()
    alt Voucher not found
        Service-->>Controller: Error.NotFound
        Controller-->>Client: 404 NotFound
    else Voucher found
        Service->>Service: Update entity fields<br/>Set UpdatedAt = UtcNow
        Service->>Repository: Update(voucher)
        Service->>Database: SaveChangesAsync(token)
        Database-->>Service: Persisted
        Service-->>Controller: ErrorOr<Updated>
        Controller-->>Client: 200 OK / 400 BadRequest
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • PR #146: Main PR's additions for vouchers operate directly on the same Voucher domain model, repository/service abstractions, and dependency injection boundaries, making this PR complementary to voucher feature development.

Suggested labels

feature

Suggested reviewers

  • hoanghaoz
  • tqha1011

Poem

🐰 Hops with glee

A voucher system springs to life,
DTOs dance without strife,
Services and repos aligned,
Creation and updates refined,
Discounts hop freely at last! 🎟️

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/voucher' is vague and generic, using branch naming convention rather than describing the actual changes. It does not convey meaningful information about what the pull request implements. Use a descriptive title summarizing the main change, such as 'Add voucher management API endpoints' or 'Implement voucher creation and update endpoints'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/voucher

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs (1)

8-11: Remove commented-out interface members.

Keep the public service contract focused on implemented operations; track the future CRUD methods in an issue instead of commented stubs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs`
around lines 8 - 11, In IVoucherService remove the four commented-out interface
members (GetVoucherByGuidAsync, GetAllVouchersAsync, UpdateVoucherAsync,
DeleteVoucherAsync) so the public contract only contains implemented methods;
delete those commented lines from the IVoucherService interface file and, if you
want to track them for later, create an issue/ticket instead of leaving
commented stubs in the codebase.
Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs (1)

8-14: Keep partial-update fields nullable and audit timestamps server-owned.

MaxPriceDiscount = 0, IsDeleted = false, and client-settable UpdateAt make it easy to overwrite existing data when a field is simply omitted. Set audit values in the service layer and keep optional update fields nullable.

♻️ Suggested DTO adjustment
-        public decimal MaxPriceDiscount { get; set; }
-        public DateTime UpdateAt { get; set; } = DateTime.UtcNow;
+        public decimal? MaxPriceDiscount { get; set; }
         public DateTime? ValidFrom { get; set; }
         public DateTime? ValidTo { get; set; }
         public int? MaxUsagePerUser { get; set; }
         public int? TotalQuantity { get; set; }
-        public bool? IsDeleted { get; set; } = false;
+        public bool? IsDeleted { get; set; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs`
around lines 8 - 14, Update the UpdateVoucherDto so optional update fields are
nullable and audit timestamps are server-owned: change MaxPriceDiscount to
decimal? (nullable) and IsDeleted to bool? (nullable) and remove the
client-settable UpdateAt (either remove the property or make it internal/handled
only by the service), and remove any default values (e.g., = false or =
DateTime.UtcNow) from the DTO; then ensure the service layer (e.g., Voucher
update handler) sets UpdateAt = DateTime.UtcNow and applies partial updates only
when nullable DTO fields (MaxPriceDiscount, IsDeleted, ValidFrom, ValidTo,
MaxUsagePerUser, TotalQuantity) are non-null.
Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherDto.cs (1)

7-10: Avoid shipping an empty public response DTO.

If this DTO is returned later, clients will only receive {}. Either add the voucher response fields now or remove the DTO until an endpoint actually needs it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherDto.cs`
around lines 7 - 10, The ResponseVoucherDto class is empty; either populate it
with the actual response properties or remove it until needed. Fix by updating
the ResponseVoucherDto DTO to include the voucher response fields used by your
API (e.g., properties such as Id, Code, DiscountAmount or Percentage, ValidFrom,
ValidTo, IsActive or whatever the domain requires) so clients receive meaningful
JSON, or delete ResponseVoucherDto and refactor any controllers/services that
reference it to use the correct existing DTO/entity until you define the final
response shape.
Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs (1)

8-9: These methods are currently unused; if implemented, prefer async materialized results over IQueryable.

GetAllVouchers and GetVoucherByGuid are defined but not called anywhere in the codebase. When they're implemented, follow the established async pattern across other repositories: Task<IReadOnlyList<Voucher>> GetAllVouchersAsync(CancellationToken token) and Task<Voucher?> GetVoucherByGuidAsync(Guid voucherGuid, CancellationToken token). Returning IQueryable defers execution, cancellation, and DbContext lifetime handling to callers; materialized results provide cleaner encapsulation and align with ICategoryRepository, IColorRepository, ISizeRepository, and similar patterns in the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs`
around lines 8 - 9, The interface IVoucherRepository currently exposes
IQueryable methods GetAllVouchers and GetVoucherByGuid; change these to async,
materialized signatures to match the repository pattern used elsewhere by
replacing GetAllVouchers() with Task<IReadOnlyList<Voucher>>
GetAllVouchersAsync(CancellationToken token) and GetVoucherByGuid(Guid
voucherGuid) with Task<Voucher?> GetVoucherByGuidAsync(Guid voucherGuid,
CancellationToken token), and update implementations to materialize queries with
cancellation support (e.g., ToListAsync/FirstOrDefaultAsync) so callers don't
handle IQueryable/DbContext lifetime.
Backend/src/ShoeStore.Application/Services/VoucherService.cs (2)

44-62: Remove commented-out stubs.

These commented DeleteVoucherAsync / GetAllVouchersAsync / GetVoucherByGuidAsync / UpdateVoucherAsync blocks are dead code. Track unimplemented methods via an issue/TODO rather than committed commented code; source control preserves history.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs` around lines 44
- 62, Remove the commented-out stub methods in VoucherService.cs (the commented
DeleteVoucherAsync, GetAllVouchersAsync, GetVoucherByGuidAsync, and
UpdateVoucherAsync blocks); delete those commented blocks entirely and, if you
need to track the missing implementations, replace them with a single TODO
comment or create a ticket/issue and reference its ID in a short TODO line
(e.g., "// TODO: implement voucher operations - ISSUE-1234") so history is
preserved by source control while keeping the codebase clean.

22-36: Silent defaulting of nullable required fields masks validation failures.

VoucherName ?? string.Empty, Discount ?? 0, TotalQuantity ?? 0, MinOrderPrice ?? 0 will happily create a voucher with an empty name or zero discount/quantity if validation is ever bypassed (e.g., validator not wired, or a new code path). Since CreateVoucherDto models these as nullable, the service should either reject nulls explicitly or rely on strongly-typed (non-nullable) DTO properties. Recommended: make required fields non-nullable in the DTO so the compiler/validator enforces presence and this service doesn't need to coalesce.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs` around lines 22
- 36, The current object initializer in VoucherService that builds the Voucher
from voucherCreateDto silently coalesces nullable fields
(voucherCreateDto.VoucherName, Discount, TotalQuantity, MinOrderPrice) to
defaults; instead either make those properties non-nullable on CreateVoucherDto
so the compiler/validation enforces them, or add explicit null/invalid checks in
VoucherService (e.g., in the method that constructs the Voucher) and throw a
clear exception (ArgumentException/ValidationException) when VoucherName is
null/empty or numeric fields are null/invalid before creating the Voucher
instance; locate the initialization in the VoucherService method that references
voucherCreateDto and update it to validate and reject nulls rather than using ??
defaults.
Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs (1)

14-17: Consider returning a single entity for GUID lookup.

GetVoucherByGuid returns IQueryable<Voucher> for what is logically a single-entity lookup (PublicId should be unique). Callers have to call FirstOrDefaultAsync themselves, and the IQueryable shape leaks EF concerns into the application layer. Consider Task<Voucher?> GetVoucherByGuidAsync(Guid voucherGuid, CancellationToken token) instead. Also, style-wise AsNoTracking() is typically applied before Where(...) (functionally equivalent here but more conventional).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs`
around lines 14 - 17, Change the repository method GetVoucherByGuid to return a
single entity asynchronously: replace the IQueryable-returning GetVoucherByGuid
with Task<Voucher?> GetVoucherByGuidAsync(Guid voucherGuid, CancellationToken
cancellationToken) that queries context.Vouchers.AsNoTracking().Where(v =>
v.PublicId == voucherGuid).FirstOrDefaultAsync(cancellationToken); update any
callers to await the new async method and remove any .FirstOrDefaultAsync usage
there; keep AsNoTracking() applied before Where for style/convention and ensure
you include the CancellationToken in the EF Core call.
Backend/src/ShoeStore.Api/Controllers/VoucherController.cs (1)

47-53: Created response lacks a resource location and identifier.

Created("", new { message = ... }) emits a 201 with an empty Location header and does not return the newly created voucher's identifier (e.g., PublicId). Consumers cannot discover the created resource. Consider returning the new voucher's PublicId from CreateVoucherAsync (e.g., ErrorOr<Guid> or a small result DTO) and using CreatedAtAction(nameof(GetVoucherByGuid), new { id = ... }, ...) once a GET endpoint exists, or at minimum include the PublicId in the response body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Api/Controllers/VoucherController.cs` around lines 47 -
53, The Created response in VoucherController currently uses Created("", ...)
which sends an empty Location header and omits the created voucher identifier;
update the CreateVoucherAsync flow (or the method returning result) to return
the new voucher PublicId (e.g., change CreateVoucherAsync to return
ErrorOr<Guid> or a DTO containing PublicId), then replace Created("", ...) with
CreatedAtAction(nameof(GetVoucherByGuid), new { id = publicId }, new { message =
"Voucher created successfully", publicId }) or at minimum include publicId in
the response body so consumers can discover the resource.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs`:
- Around line 13-24: The setters for ValidFrom and ValidTo currently use
DateTime.SpecifyKind which only labels the value as UTC without converting the
instant; update the setters in CreateVoucherDto (properties ValidFrom/ValidTo,
backing fields _validFrom/_validTo) to convert incoming non-null DateTime values
to UTC using ToUniversalTime() (e.g., set _validFrom = value.HasValue ?
value.Value.ToUniversalTime() : null) so timestamps are actually normalized to
UTC while preserving null handling.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs`:
- Around line 27-28: The CreateVoucherDtoValidation is missing enum validation,
so unvalidated int values are being cast into enums in VoucherService when
assigning VoucherScope and DiscountType; update CreateVoucherDtoValidation to
add IsInEnum() rules for the VoucherScope and DiscountType properties (e.g.,
RuleFor(x => x.VoucherScope).IsInEnum().WithMessage(...); RuleFor(x =>
x.DiscountType).IsInEnum().WithMessage(...)) to ensure invalid ints are rejected
before VoucherService performs the casts.

In
`@Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs`:
- Around line 10-19: Add validations for enum and numeric constraints: ensure
VoucherScope and DiscountType use IsInEnum() (e.g., RuleFor(x =>
x.VoucherScope).IsInEnum().WithMessage(...), RuleFor(x =>
x.DiscountType).IsInEnum().WithMessage(...)); add a conditional rule that when
DiscountType == DiscountType.Percentage the Discount value is <= 100 (RuleFor(x
=> x.Discount).LessThanOrEqualTo(100).When(x => x.DiscountType ==
DiscountType.Percentage).WithMessage(...)); and enforce non-negative constraints
for MaxPriceDiscount, MinOrderPrice and MaxUsagePerUser (e.g., RuleFor(x =>
x.MaxPriceDiscount).GreaterThanOrEqualTo(0)..., RuleFor(x =>
x.MinOrderPrice).GreaterThanOrEqualTo(0)..., RuleFor(x =>
x.MaxUsagePerUser).GreaterThanOrEqualTo(0)...). Use the
CreateVoucherDtoValidation class and the property names VoucherScope,
DiscountType, Discount, MaxPriceDiscount, MinOrderPrice, and MaxUsagePerUser to
locate where to add these rules.

---

Nitpick comments:
In `@Backend/src/ShoeStore.Api/Controllers/VoucherController.cs`:
- Around line 47-53: The Created response in VoucherController currently uses
Created("", ...) which sends an empty Location header and omits the created
voucher identifier; update the CreateVoucherAsync flow (or the method returning
result) to return the new voucher PublicId (e.g., change CreateVoucherAsync to
return ErrorOr<Guid> or a DTO containing PublicId), then replace Created("",
...) with CreatedAtAction(nameof(GetVoucherByGuid), new { id = publicId }, new {
message = "Voucher created successfully", publicId }) or at minimum include
publicId in the response body so consumers can discover the resource.

In `@Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherDto.cs`:
- Around line 7-10: The ResponseVoucherDto class is empty; either populate it
with the actual response properties or remove it until needed. Fix by updating
the ResponseVoucherDto DTO to include the voucher response fields used by your
API (e.g., properties such as Id, Code, DiscountAmount or Percentage, ValidFrom,
ValidTo, IsActive or whatever the domain requires) so clients receive meaningful
JSON, or delete ResponseVoucherDto and refactor any controllers/services that
reference it to use the correct existing DTO/entity until you define the final
response shape.

In `@Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs`:
- Around line 8-14: Update the UpdateVoucherDto so optional update fields are
nullable and audit timestamps are server-owned: change MaxPriceDiscount to
decimal? (nullable) and IsDeleted to bool? (nullable) and remove the
client-settable UpdateAt (either remove the property or make it internal/handled
only by the service), and remove any default values (e.g., = false or =
DateTime.UtcNow) from the DTO; then ensure the service layer (e.g., Voucher
update handler) sets UpdateAt = DateTime.UtcNow and applies partial updates only
when nullable DTO fields (MaxPriceDiscount, IsDeleted, ValidFrom, ValidTo,
MaxUsagePerUser, TotalQuantity) are non-null.

In
`@Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs`:
- Around line 8-9: The interface IVoucherRepository currently exposes IQueryable
methods GetAllVouchers and GetVoucherByGuid; change these to async, materialized
signatures to match the repository pattern used elsewhere by replacing
GetAllVouchers() with Task<IReadOnlyList<Voucher>>
GetAllVouchersAsync(CancellationToken token) and GetVoucherByGuid(Guid
voucherGuid) with Task<Voucher?> GetVoucherByGuidAsync(Guid voucherGuid,
CancellationToken token), and update implementations to materialize queries with
cancellation support (e.g., ToListAsync/FirstOrDefaultAsync) so callers don't
handle IQueryable/DbContext lifetime.

In
`@Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs`:
- Around line 8-11: In IVoucherService remove the four commented-out interface
members (GetVoucherByGuidAsync, GetAllVouchersAsync, UpdateVoucherAsync,
DeleteVoucherAsync) so the public contract only contains implemented methods;
delete those commented lines from the IVoucherService interface file and, if you
want to track them for later, create an issue/ticket instead of leaving
commented stubs in the codebase.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs`:
- Around line 44-62: Remove the commented-out stub methods in VoucherService.cs
(the commented DeleteVoucherAsync, GetAllVouchersAsync, GetVoucherByGuidAsync,
and UpdateVoucherAsync blocks); delete those commented blocks entirely and, if
you need to track the missing implementations, replace them with a single TODO
comment or create a ticket/issue and reference its ID in a short TODO line
(e.g., "// TODO: implement voucher operations - ISSUE-1234") so history is
preserved by source control while keeping the codebase clean.
- Around line 22-36: The current object initializer in VoucherService that
builds the Voucher from voucherCreateDto silently coalesces nullable fields
(voucherCreateDto.VoucherName, Discount, TotalQuantity, MinOrderPrice) to
defaults; instead either make those properties non-nullable on CreateVoucherDto
so the compiler/validation enforces them, or add explicit null/invalid checks in
VoucherService (e.g., in the method that constructs the Voucher) and throw a
clear exception (ArgumentException/ValidationException) when VoucherName is
null/empty or numeric fields are null/invalid before creating the Voucher
instance; locate the initialization in the VoucherService method that references
voucherCreateDto and update it to validate and reject nulls rather than using ??
defaults.

In `@Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs`:
- Around line 14-17: Change the repository method GetVoucherByGuid to return a
single entity asynchronously: replace the IQueryable-returning GetVoucherByGuid
with Task<Voucher?> GetVoucherByGuidAsync(Guid voucherGuid, CancellationToken
cancellationToken) that queries context.Vouchers.AsNoTracking().Where(v =>
v.PublicId == voucherGuid).FirstOrDefaultAsync(cancellationToken); update any
callers to await the new async method and remove any .FirstOrDefaultAsync usage
there; keep AsNoTracking() applied before Where for style/convention and ensure
you include the CancellationToken in the EF Core call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fde2b7b4-ad97-42e0-9f47-77eb870f4d83

📥 Commits

Reviewing files that changed from the base of the PR and between 3029a25 and 3d26f35.

📒 Files selected for processing (11)
  • Backend/src/ShoeStore.Api/Controllers/VoucherController.cs
  • Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs
  • Backend/src/ShoeStore.Application/DTOs/VoucherDtos/ResponseVoucherDto.cs
  • Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs
  • Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherRepository.cs
  • Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs
  • Backend/src/ShoeStore.Application/Services/VoucherService.cs
  • Backend/src/ShoeStore.Application/ShoeStore.Application.csproj
  • Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs
  • Backend/src/ShoeStore.Infrastructure/DependencyInjection/DependencyInjection.cs
  • Backend/src/ShoeStore.Infrastructure/Repositories/VoucherRepository.cs

Comment on lines +13 to +24
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;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Microsoft .NET DateTime.SpecifyKind documentation: does SpecifyKind convert the DateTime value to UTC or only set the Kind property?

💡 Result:

DateTime.SpecifyKind does not convert the DateTime value to UTC. It only sets the Kind property while keeping the same number of ticks (time value) unchanged. The official documentation explicitly states: "The returned DateTime value does not represent the same instant in time as the value parameter, and SpecifyKind is not a time zone conversion method. Instead, it leaves the time specified by the value parameter unchanged, and sets the Kind property to kind." For time zone conversions, use methods like ToUniversalTime or ToLocalTime.

Citations:


🏁 Script executed:

# First, locate and inspect the CreateVoucherDto.cs file
find . -name "CreateVoucherDto.cs" -type f

Repository: tqha1011/ShoeStore

Length of output: 134


🏁 Script executed:

# Search for DateTime.SpecifyKind usage across the codebase
rg "SpecifyKind" -t cs -C 3

Repository: tqha1011/ShoeStore

Length of output: 5521


🏁 Script executed:

# Search for similar DateTime assignment patterns in DTOs
rg "DateTime\.SpecifyKind|DateTimeKind\.Utc" -t cs --glob "**/*Dto.cs"

Repository: tqha1011/ShoeStore

Length of output: 419


Use ToUniversalTime() to convert non-UTC times, not SpecifyKind().

Lines 16 and 23 use DateTime.SpecifyKind(), which only sets metadata without converting the timestamp. If clients send local or offset-based times, they get relabeled as UTC without any actual conversion, causing vouchers to expire at the wrong instant.

🛠️ Proper UTC normalization
+        private static DateTime? NormalizeUtc(DateTime? value)
+        {
+            if (!value.HasValue)
+            {
+                return null;
+            }
+
+            return value.Value.Kind == DateTimeKind.Unspecified
+                ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc)
+                : value.Value.ToUniversalTime();
+        }
+
         private DateTime? _validFrom;
         public DateTime? ValidFrom
         {
             get => _validFrom;
-            set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+            set => _validFrom = NormalizeUtc(value);
         }
 
         private DateTime? _validTo;
         public DateTime? ValidTo
         {
             get => _validTo;
-            set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+            set => _validTo = NormalizeUtc(value);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs`
around lines 13 - 24, The setters for ValidFrom and ValidTo currently use
DateTime.SpecifyKind which only labels the value as UTC without converting the
instant; update the setters in CreateVoucherDto (properties ValidFrom/ValidTo,
backing fields _validFrom/_validTo) to convert incoming non-null DateTime values
to UTC using ToUniversalTime() (e.g., set _validFrom = value.HasValue ?
value.Value.ToUniversalTime() : null) so timestamps are actually normalized to
UTC while preserving null handling.

Comment on lines +27 to +28
VoucherScope = (VoucherScope)voucherCreateDto.VoucherScope,
DiscountType = (DiscountType)voucherCreateDto.DiscountType,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd CreateVoucherDtoValidation.cs --exec cat {}
fd CreateVoucherDto.cs --exec cat {}

Repository: tqha1011/ShoeStore

Length of output: 2141


Unvalidated intenum casts persist invalid enum values.

Verified: CreateVoucherDtoValidation contains no validation for VoucherScope or DiscountType. Both fields lack IsInEnum() rules, allowing any int to silently cast to an enum on lines 27–28, corrupting voucher records.

Add to CreateVoucherDtoValidation:

RuleFor(x => x.VoucherScope)
    .IsInEnum().WithMessage("Invalid VoucherScope value.");

RuleFor(x => x.DiscountType)
    .IsInEnum().WithMessage("Invalid DiscountType value.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs` around lines 27
- 28, The CreateVoucherDtoValidation is missing enum validation, so unvalidated
int values are being cast into enums in VoucherService when assigning
VoucherScope and DiscountType; update CreateVoucherDtoValidation to add
IsInEnum() rules for the VoucherScope and DiscountType properties (e.g.,
RuleFor(x => x.VoucherScope).IsInEnum().WithMessage(...); RuleFor(x =>
x.DiscountType).IsInEnum().WithMessage(...)) to ensure invalid ints are rejected
before VoucherService performs the casts.

Comment on lines +10 to +19
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");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find the domain/entity definitions and usages for voucher scope/type/value fields.
# Expected: Use the discovered allowed values to replace the placeholder Must(...) checks above.

rg -n -C3 --type=cs '\b(VoucherScope|DiscountType|MaxPriceDiscount|MinOrderPrice|MaxUsagePerUser)\b'

Repository: tqha1011/ShoeStore

Length of output: 50374


🏁 Script executed:

fd -n "CreateVoucherDto" --type f

Repository: tqha1011/ShoeStore

Length of output: 292


🏁 Script executed:

fd "CreateVoucherDto" --type f

Repository: tqha1011/ShoeStore

Length of output: 226


🏁 Script executed:

cat -n "Backend/src/ShoeStore.Application/DTOs/VoucherDtos/CreateVoucherDto.cs"

Repository: tqha1011/ShoeStore

Length of output: 1334


🏁 Script executed:

fd "Voucher" --type f | grep -E "(Entity|Domain|Model)" | head -20

Repository: tqha1011/ShoeStore

Length of output: 268


🏁 Script executed:

cat -n "Backend/src/ShoeStore.Domain/Entities/Voucher.cs"

Repository: tqha1011/ShoeStore

Length of output: 1532


🏁 Script executed:

cat -n "Backend/src/ShoeStore.Domain/Enum/VoucherScope.cs"

Repository: tqha1011/ShoeStore

Length of output: 207


🏁 Script executed:

fd "DiscountType" --type f

Repository: tqha1011/ShoeStore

Length of output: 111


🏁 Script executed:

cat -n "Backend/src/ShoeStore.Domain/Enum/DiscountType.cs"

Repository: tqha1011/ShoeStore

Length of output: 213


🏁 Script executed:

cat -n "Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs"

Repository: tqha1011/ShoeStore

Length of output: 1253


🏁 Script executed:

cat -n "Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs" | head -30

Repository: tqha1011/ShoeStore

Length of output: 1253


Add missing validation rules for voucher enum fields and economic constraints.

The validation is incomplete. VoucherScope and DiscountType accept arbitrary integers, percentage discounts can exceed 100%, and negative values are allowed for MaxPriceDiscount, MinOrderPrice, and MaxUsagePerUser. These should be rejected at the DTO boundary.

🛡️ Add missing validation rules
+        RuleFor(x => x.VoucherScope)
+            .Must(value => value is 1 or 2)
+            .WithMessage("Voucher scope must be 1 (Product) or 2 (Shipping)");
+
+        RuleFor(x => x.DiscountType)
+            .Must(value => value is 1 or 2)
+            .WithMessage("Discount type must be 1 (FixedAmount) or 2 (Percentage)");
+
         RuleFor(x => x.Discount)
             .NotNull().WithMessage("Discount is required")
             .GreaterThanOrEqualTo(0).WithMessage("Discount must be greater than or equal to 0");
+
+        RuleFor(x => x.Discount)
+            .LessThanOrEqualTo(100).WithMessage("Percentage discount must not exceed 100")
+            .When(x => x.DiscountType == 2);
+
+        RuleFor(x => x.MaxPriceDiscount)
+            .GreaterThanOrEqualTo(0).WithMessage("Max price discount must be greater than or equal to 0");
+
+        RuleFor(x => x.MaxUsagePerUser)
+            .GreaterThan(0).WithMessage("Max usage per user must be greater than 0")
+            .When(x => x.MaxUsagePerUser.HasValue);
+
+        RuleFor(x => x.MinOrderPrice)
+            .GreaterThanOrEqualTo(0).WithMessage("Minimum order price must be greater than or equal to 0")
+            .When(x => x.MinOrderPrice.HasValue);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Backend/src/ShoeStore.Application/Validations/VoucherValidation/CreateVoucherDtoValidation.cs`
around lines 10 - 19, Add validations for enum and numeric constraints: ensure
VoucherScope and DiscountType use IsInEnum() (e.g., RuleFor(x =>
x.VoucherScope).IsInEnum().WithMessage(...), RuleFor(x =>
x.DiscountType).IsInEnum().WithMessage(...)); add a conditional rule that when
DiscountType == DiscountType.Percentage the Discount value is <= 100 (RuleFor(x
=> x.Discount).LessThanOrEqualTo(100).When(x => x.DiscountType ==
DiscountType.Percentage).WithMessage(...)); and enforce non-negative constraints
for MaxPriceDiscount, MinOrderPrice and MaxUsagePerUser (e.g., RuleFor(x =>
x.MaxPriceDiscount).GreaterThanOrEqualTo(0)..., RuleFor(x =>
x.MinOrderPrice).GreaterThanOrEqualTo(0)..., RuleFor(x =>
x.MaxUsagePerUser).GreaterThanOrEqualTo(0)...). Use the
CreateVoucherDtoValidation class and the property names VoucherScope,
DiscountType, Discount, MaxPriceDiscount, MinOrderPrice, and MaxUsagePerUser to
locate where to add these rules.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
Backend/src/ShoeStore.Api/Controllers/VoucherController.cs (1)

48-69: Avoid mapping all non-404 service errors to 400.

Both endpoints collapse service failures into BadRequest, which hides conflicts or unexpected server errors and diverges from the existing controller patterns that map by ErrorType/code.

Proposed direction
         return result.Match<IActionResult>(
             _ => Created("", new { message = "Voucher created successfully" }),
-            errors => BadRequest(new
-            {
-                message = "Failed to create voucher",
-                details = errors
-            }));
+            errors => errors[0].Type switch
+            {
+                ErrorType.Conflict => Conflict(new { message = "Failed to create voucher", details = errors }),
+                ErrorType.Validation => BadRequest(new { message = "Failed to create voucher", details = errors }),
+                _ => StatusCode(StatusCodes.Status500InternalServerError,
+                    new { message = "Failed to create voucher", details = errors })
+            });

Apply the same pattern to UpdateVoucher, keeping ErrorType.NotFound => NotFound(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Api/Controllers/VoucherController.cs` around lines 48 -
69, The UpdateVoucher endpoint currently maps all non-NotFound errors to
BadRequest; change the error handling for voucherService.UpdateVoucherAsync in
UpdateVoucher (and mirror CreateVoucher if applicable) to map ErrorType values
explicitly (e.g., if any error.Type == ErrorType.NotFound return NotFound(...);
if any error.Type == ErrorType.Conflict return Conflict(...); otherwise return
StatusCode(500, ...) or BadRequest depending on ErrorType) so conflicts and
server errors are not misclassified; use the existing errors collection and
ErrorType enum to decide the correct IActionResult and include error details
where appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Backend/src/ShoeStore.Api/Controllers/VoucherController.cs`:
- Around line 14-17: The controller has its [Authorize(Roles = "Admin")]
attribute commented out, exposing admin voucher endpoints; re-enable the
attribute by restoring [Authorize(Roles = "Admin")] on the VoucherController
(class VoucherController) or alternatively apply it to the specific mutation
actions (e.g., Create/Update methods in VoucherController) so that only users in
the Admin role can call the admin voucher endpoints.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs`:
- Around line 64-78: The update logic in VoucherService.cs is overwriting
existing voucher fields with DTO defaults because fields like VoucherScope,
DiscountType, and MaxPriceDiscount are non-nullable on voucherUpdateDto and are
always written; also Discount is never updated. Make the corresponding
properties on the update DTO nullable (e.g., int? VoucherScope, int?
DiscountType, decimal? MaxPriceDiscount, and include a nullable Discount
property if intended), then change VoucherService.Update mapping to only assign
when the DTO value.HasValue or DTO string is non-null (e.g., check
voucherUpdateDto.VoucherScope.HasValue before casting and assigning to
voucher.VoucherScope), and add mapping for Discount from voucherUpdateDto when
provided; this preserves existing values on partial updates.
- Around line 52-54: In UpdateVoucherAsync, the database read call using
repository.GetVoucherByGuid(...).FirstOrDefaultAsync() does not pass the
CancellationToken parameter, so the read cannot be cancelled; update the call to
pass the provided CancellationToken token into FirstOrDefaultAsync(token) (or
the appropriate overload) so the cancellation token is honored during the query
before you later call SaveChangesAsync(token).

---

Nitpick comments:
In `@Backend/src/ShoeStore.Api/Controllers/VoucherController.cs`:
- Around line 48-69: The UpdateVoucher endpoint currently maps all non-NotFound
errors to BadRequest; change the error handling for
voucherService.UpdateVoucherAsync in UpdateVoucher (and mirror CreateVoucher if
applicable) to map ErrorType values explicitly (e.g., if any error.Type ==
ErrorType.NotFound return NotFound(...); if any error.Type == ErrorType.Conflict
return Conflict(...); otherwise return StatusCode(500, ...) or BadRequest
depending on ErrorType) so conflicts and server errors are not misclassified;
use the existing errors collection and ErrorType enum to decide the correct
IActionResult and include error details where appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1bea080a-5a90-4a81-8bba-d018b0f15727

📥 Commits

Reviewing files that changed from the base of the PR and between 3d26f35 and f7f19ab.

📒 Files selected for processing (4)
  • Backend/src/ShoeStore.Api/Controllers/VoucherController.cs
  • Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs
  • Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs
  • Backend/src/ShoeStore.Application/Services/VoucherService.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • Backend/src/ShoeStore.Application/Interface/VoucherInterface/IVoucherService.cs
  • Backend/src/ShoeStore.Application/DTOs/VoucherDtos/UpdateVoucherDto.cs

Comment on lines +14 to +17
[ApiController]
[Route("api/admin/vouchers")]
// [Authorize(Roles = "Admin")]
public class VoucherController(IVoucherService voucherService) : ControllerBase
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Re-enable Admin authorization on voucher mutations.

The route and docs say this is Admin-only, but [Authorize(Roles = "Admin")] is commented out, making voucher creation/update publicly reachable if the app has no other gate in front of this controller.

Proposed fix
 [ApiController]
 [Route("api/admin/vouchers")]
-// [Authorize(Roles = "Admin")]
+[Authorize(Roles = "Admin")]
 public class VoucherController(IVoucherService voucherService) : ControllerBase
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[ApiController]
[Route("api/admin/vouchers")]
// [Authorize(Roles = "Admin")]
public class VoucherController(IVoucherService voucherService) : ControllerBase
[ApiController]
[Route("api/admin/vouchers")]
[Authorize(Roles = "Admin")]
public class VoucherController(IVoucherService voucherService) : ControllerBase
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Api/Controllers/VoucherController.cs` around lines 14 -
17, The controller has its [Authorize(Roles = "Admin")] attribute commented out,
exposing admin voucher endpoints; re-enable the attribute by restoring
[Authorize(Roles = "Admin")] on the VoucherController (class VoucherController)
or alternatively apply it to the specific mutation actions (e.g., Create/Update
methods in VoucherController) so that only users in the Admin role can call the
admin voucher endpoints.

Comment on lines +52 to +54
var voucher = await repository
.GetVoucherByGuid(voucherGuid)
.FirstOrDefaultAsync();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no-token EF async query calls in C# files.
# Expected: voucher query uses FirstOrDefaultAsync(token) or an equivalent CancellationToken overload.
rg -nP --type cs -C2 '\.FirstOrDefaultAsync\s*\(\s*\)'

Repository: tqha1011/ShoeStore

Length of output: 545


🏁 Script executed:

cat -n Backend/src/ShoeStore.Application/Services/VoucherService.cs | head -100

Repository: tqha1011/ShoeStore

Length of output: 3990


🏁 Script executed:

rg -n 'FirstOrDefaultAsync\s*\(\s*\)' Backend/src/ShoeStore.Application/Services/VoucherService.cs

Repository: tqha1011/ShoeStore

Length of output: 104


Pass the cancellation token to the database query.

The UpdateVoucherAsync method accepts a CancellationToken token parameter but doesn't pass it to FirstOrDefaultAsync() on line 54. Without the token, the database read operation cannot be cancelled, even though the save operation on line 82 correctly uses it.

Proposed fix
             var voucher = await repository
                 .GetVoucherByGuid(voucherGuid)
-                .FirstOrDefaultAsync();
+                .FirstOrDefaultAsync(token);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs` around lines 52
- 54, In UpdateVoucherAsync, the database read call using
repository.GetVoucherByGuid(...).FirstOrDefaultAsync() does not pass the
CancellationToken parameter, so the read cannot be cancelled; update the call to
pass the provided CancellationToken token into FirstOrDefaultAsync(token) (or
the appropriate overload) so the cancellation token is honored during the query
before you later call SaveChangesAsync(token).

Comment on lines +64 to +78
// 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix update mapping before it corrupts existing voucher fields.

Discount is never updated, while omitted non-nullable fields like VoucherScope, DiscountType, and MaxPriceDiscount are written with DTO defaults. A description-only update can silently reset existing values.

Proposed direction
             // Update logic
             voucher.VoucherDescription = voucherUpdateDto.VoucherDescription ?? voucher.VoucherDescription;
+            voucher.Discount = voucherUpdateDto.Discount ?? voucher.Discount;
 
-            voucher.VoucherScope = (VoucherScope)voucherUpdateDto.VoucherScope;
-            voucher.DiscountType = (DiscountType)voucherUpdateDto.DiscountType;
+            if (voucherUpdateDto.VoucherScope.HasValue)
+                voucher.VoucherScope = (VoucherScope)voucherUpdateDto.VoucherScope.Value;
 
-            voucher.MaxPriceDiscount = voucherUpdateDto.MaxPriceDiscount;
+            if (voucherUpdateDto.DiscountType.HasValue)
+                voucher.DiscountType = (DiscountType)voucherUpdateDto.DiscountType.Value;
+
+            voucher.MaxPriceDiscount = voucherUpdateDto.MaxPriceDiscount ?? voucher.MaxPriceDiscount;

This also requires making the update DTO value fields nullable, for example:

public int? VoucherScope { get; set; }
public int? DiscountType { get; set; }
public decimal? MaxPriceDiscount { get; set; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Backend/src/ShoeStore.Application/Services/VoucherService.cs` around lines 64
- 78, The update logic in VoucherService.cs is overwriting existing voucher
fields with DTO defaults because fields like VoucherScope, DiscountType, and
MaxPriceDiscount are non-nullable on voucherUpdateDto and are always written;
also Discount is never updated. Make the corresponding properties on the update
DTO nullable (e.g., int? VoucherScope, int? DiscountType, decimal?
MaxPriceDiscount, and include a nullable Discount property if intended), then
change VoucherService.Update mapping to only assign when the DTO value.HasValue
or DTO string is non-null (e.g., check voucherUpdateDto.VoucherScope.HasValue
before casting and assigning to voucher.VoucherScope), and add mapping for
Discount from voucherUpdateDto when provided; this preserves existing values on
partial updates.

@tqha1011 tqha1011 marked this pull request as draft April 20, 2026 01:57
@sonarqubecloud
Copy link
Copy Markdown

[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
[HttpGet("user/{userGuid}")]
public async Task<IActionResult> GetVouchersForUser(Guid userGuid, CancellationToken token)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cái này k cần phải là tham số, lấy từ jwt token dùng find first value


namespace ShoeStore.Application.Services
{
public class VoucherService : IVoucherService, IUserVoucherService
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thừa cái IUserVoucherService

})
.ToListAsync(token);

if(vouchers == null || !vouchers.Any())
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bỏ điều kiện null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants