Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.idea.Fin-Backend/.idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Fin-Backend.sln.DotSettings.user
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AParameterExpression_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F6182caf029c8666b96a8b3bae244ef29b988c59b58e9b3439b794a4f8195bec_003FParameterExpression_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AType_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7e333a9f3297ba553cccfd3b7c3f1f96125b23d09f883e4d6e66d531559a4c_003FType_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6e670ad021647bd9cd5d3c28cc553172c800_003F99_003F09c94557_003FValidationResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=5f989a48_002Defa4_002D493c_002D924b_002D2fab4b9713fd/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt;
&lt;/SessionState&gt;</s:String>



Expand Down
63 changes: 63 additions & 0 deletions Fin.Api/CreditCards/CreditCardController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Fin.Application.CreditCards.Dtos;
using Fin.Application.CreditCards.Enums;
using Fin.Application.CreditCards.Services;
using Fin.Domain.Global.Classes;
using Fin.Domain.CreditCards.Dtos;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Fin.Api.CreditCards;

[Route("credit-cards")]
[Authorize]
public class CreditCardController(ICreditCardService service) : ControllerBase
{
[HttpGet]
public async Task<PagedOutput<CreditCardOutput>> GetList([FromQuery] CreditCardGetListInput input)
{
return await service.GetList(input);
}

[HttpGet("{id:guid}")]
public async Task<ActionResult<CreditCardOutput>> Get([FromRoute] Guid id)
{
var category = await service.Get(id);
return category != null ? Ok(category) : NotFound();
}

[HttpPost]
public async Task<ActionResult<CreditCardOutput>> Create([FromBody] CreditCardInput input)
{
var validationResult = await service.Create(input, autoSave: true);
return validationResult.Success
? Created($"categories/{validationResult.Data?.Id}", validationResult.Data)
: UnprocessableEntity(validationResult);
}

[HttpPut("{id:guid}")]
public async Task<ActionResult> Update([FromRoute] Guid id, [FromBody] CreditCardInput input)
{
var validationResult = await service.Update(id, input, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == CreditCardCreateOrUpdateErrorCode.CreditCardNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}

[HttpPut("toggle-inactivated/{id:guid}")]
public async Task<ActionResult> ToggleInactivated([FromRoute] Guid id)
{
var validationResult = await service.ToggleInactive(id, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == CreditCardToggleInactiveErrorCode.CreditCardNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}

[HttpDelete("{id:guid}")]
public async Task<ActionResult> Delete([FromRoute] Guid id)
{
var validationResult = await service.Delete(id, autoSave: true);
return validationResult.Success ? Ok() :
validationResult.ErrorCode == CreditCardDeleteErrorCode.CreditCardNotFound ? NotFound(validationResult) :
UnprocessableEntity(validationResult);
}
}
11 changes: 11 additions & 0 deletions Fin.Application/CreditCards/Dtos/CreditCardGetListInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Fin.Domain.Global.Classes;

namespace Fin.Application.CreditCards.Dtos;

public class CreditCardGetListInput: PagedFilteredAndSortedInput
{
public bool? Inactivated { get; set; }
public List<Guid> DebitWalletIds { get; set; } = [];
public List<Guid> FinancialInstitutionIds { get; set; } = [];
public List<Guid> CardBrandIds { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Fin.Application.CreditCards.Enums;

public enum CreditCardCreateOrUpdateErrorCode
{
NameIsRequired = 0,
NameAlreadyInUse = 1,
NameTooLong = 2,
ColorIsRequired = 3,
ColorTooLong = 4,
IconIsRequired = 5,
IconTooLong = 6,
CreditCardNotFound = 7,

FinancialInstitutionNotFound = 8,
FinancialInstitutionInactivated = 9,

DebitWalletNotFound = 10,
DebitWalletInactivated = 11,

CardBrandNotFound = 12,

LimitMinValueZero = 13,
DueDayOutOfRange = 14,
ClosingDayOutOfRange = 15
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Fin.Application.CreditCards.Enums;

public enum CreditCardDeleteErrorCode
{
CreditCardNotFound = 0,
CreditCardInUse = 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Fin.Application.CreditCards.Enums;

public enum CreditCardToggleInactiveErrorCode
{
CreditCardNotFound = 0
}
97 changes: 97 additions & 0 deletions Fin.Application/CreditCards/Services/CreditCardService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using Fin.Application.Globals.Dtos;
using Fin.Application.CreditCards.Dtos;
using Fin.Application.CreditCards.Enums;
using Fin.Domain.Global.Classes;
using Fin.Domain.CreditCards.Dtos;
using Fin.Domain.CreditCards.Entities;
using Fin.Infrastructure.AutoServices.Interfaces;
using Fin.Infrastructure.Database.Extensions;
using Fin.Infrastructure.Database.Repositories;
using Microsoft.EntityFrameworkCore;

namespace Fin.Application.CreditCards.Services;

public interface ICreditCardService
{
public Task<CreditCardOutput> Get(Guid id);
public Task<PagedOutput<CreditCardOutput>> GetList(CreditCardGetListInput input);
public Task<ValidationResultDto<CreditCardOutput, CreditCardCreateOrUpdateErrorCode>> Create(CreditCardInput input, bool autoSave = false);
public Task<ValidationResultDto<bool, CreditCardCreateOrUpdateErrorCode>> Update(Guid id, CreditCardInput input, bool autoSave = false);
public Task<ValidationResultDto<bool, CreditCardDeleteErrorCode>> Delete(Guid id, bool autoSave = false);
public Task<ValidationResultDto<bool, CreditCardToggleInactiveErrorCode>> ToggleInactive(Guid id, bool autoSave = false);
}

public class CreditCardService(
IRepository<CreditCard> repository,
ICreditCardValidationService validationService
) : ICreditCardService, IAutoTransient
{
public async Task<CreditCardOutput> Get(Guid id)
{
var entity = await repository.Query(false).FirstOrDefaultAsync(n => n.Id == id);
return entity != null ? new CreditCardOutput(entity) : null;
}

public async Task<PagedOutput<CreditCardOutput>> GetList(CreditCardGetListInput input)
{
return await repository.Query(false)
.WhereIf(input.Inactivated.HasValue, n => n.Inactivated == input.Inactivated.Value)
.WhereIf(input.CardBrandIds.Any(), creditCard => input.CardBrandIds.Contains(creditCard.CardBrandId))
.WhereIf(input.DebitWalletIds.Any(), creditCard => input.DebitWalletIds.Contains(creditCard.DebitWalletId))
.WhereIf(input.FinancialInstitutionIds.Any(), creditCard => input.FinancialInstitutionIds.Contains(creditCard.FinancialInstitutionId))
.OrderBy(m => m.Inactivated)
.ThenBy(m => m.Name)
.ApplyFilterAndSorter(input)
.Select(n => new CreditCardOutput(n))
.ToPagedResult(input);
}

public async Task<ValidationResultDto<CreditCardOutput, CreditCardCreateOrUpdateErrorCode>> Create(CreditCardInput input, bool autoSave = false)
{
var validation = await validationService.ValidateInput<CreditCardOutput>(input);
if (!validation.Success) return validation;

var creditCard = new CreditCard(input);
await repository.AddAsync(creditCard, autoSave);
validation.Data = new CreditCardOutput(creditCard);
return validation;
}

public async Task<ValidationResultDto<bool, CreditCardCreateOrUpdateErrorCode>> Update(Guid id, CreditCardInput input, bool autoSave = false)
{
var validation = await validationService.ValidateInput<bool>(input, id);
if (!validation.Success) return validation;

var creditCard = await repository.Query().FirstAsync(u => u.Id == id);
creditCard.Update(input);
await repository.UpdateAsync(creditCard, autoSave);

validation.Data = true;
return validation;
}

public async Task<ValidationResultDto<bool, CreditCardDeleteErrorCode>> Delete(Guid id, bool autoSave = false)
{
var validation = await validationService.ValidateDelete(id);
if (!validation.Success) return validation;

var creditCard = await repository.Query().FirstAsync(u => u.Id == id);
await repository.DeleteAsync(creditCard, autoSave);

validation.Success = true;
return validation;
}

public async Task<ValidationResultDto<bool, CreditCardToggleInactiveErrorCode>> ToggleInactive(Guid id, bool autoSave = false)
{
var validation = await validationService.ValidateToggleInactive(id);
if (!validation.Success) return validation;

var creditCard = await repository.Query().FirstAsync(u => u.Id == id);
creditCard.ToggleInactivated();
await repository.UpdateAsync(creditCard, autoSave);

validation.Data = true;
return validation;
}
}
Loading
Loading