diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index eb1e3b0..9fa6f86 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,10 +15,10 @@ ] }, "dotnet-ef": { - "version": "6.0.26", + "version": "8.0.7", "commands": [ "dotnet-ef" ] } } -} +} \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..4831c18 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,34 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + services: + mysql: + image: mysql:9.0.1 + env: + MYSQL_ROOT_PASSWORD: b@nk1 + MYSQL_DATABASE: bank_db + ports: + - "3307:3306" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..39bb010 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Bank.Api/bin/Debug/net6.0/Bank.Api.dll", + "args": [], + "cwd": "${workspaceFolder}/Bank.Api", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f42f114 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Bank.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Bank.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Bank.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Bank.Api/Bank.Api.csproj b/Bank.Api/Bank.Api.csproj index ffc75bc..fa8e780 100644 --- a/Bank.Api/Bank.Api.csproj +++ b/Bank.Api/Bank.Api.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 Linux ..\Bank win-x64;osx-x64;linux-x64 @@ -9,10 +9,10 @@ - + - + \ No newline at end of file diff --git a/Bank.Api/Dockerfile b/Bank.Api/Dockerfile index 556035b..f2c57f6 100644 --- a/Bank.Api/Dockerfile +++ b/Bank.Api/Dockerfile @@ -1,10 +1,10 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY . . RUN dotnet restore diff --git a/Bank.Api/Handlers/IncomeRequestHandler.cs b/Bank.Api/Handlers/IncomeRequestHandler.cs index 778c25c..04d9c3c 100644 --- a/Bank.Api/Handlers/IncomeRequestHandler.cs +++ b/Bank.Api/Handlers/IncomeRequestHandler.cs @@ -22,12 +22,18 @@ public IncomeRequestHandler(BankDbContext context, IYieldService yieldService) _yieldService = yieldService; } - public async Task Handle(CalculateIncomeCommand request, CancellationToken cancellationToken) + public Task Handle(CalculateIncomeCommand request, CancellationToken cancellationToken) { var accounts = this._context.Accounts.ToArray(); - _yieldService.CalculateInterestFor(request.ForDate, accounts, request.InterestRate, cancellationToken, days: 1); - await _context.SaveChangesAsync(cancellationToken); - return Unit.Value; + + foreach (var account in accounts) + { + var yield = _yieldService.CalculateInterestFor(request.ForDate, account, request.InterestRate, days: 1); + account.SetYield(yield, request.ForDate); + } + + _context.SaveChanges(); + return Unit.Task; } } } diff --git a/Bank.Api/Startup.cs b/Bank.Api/Startup.cs index 91b3a18..ce299a8 100644 --- a/Bank.Api/Startup.cs +++ b/Bank.Api/Startup.cs @@ -1,27 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -using Bank.Api.Handlers; using Bank.Domain; using Bank.Domain.Contracts; -using Bank.Domain.Events; -using Bank.Domain.SeedWork; using Bank.Infra; using MediatR; -using MediatR.Pipeline; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; namespace Bank.Api diff --git a/Bank.Api/appsettings.Development.json b/Bank.Api/appsettings.Development.json index 721ec62..aba6cbc 100644 --- a/Bank.Api/appsettings.Development.json +++ b/Bank.Api/appsettings.Development.json @@ -3,7 +3,8 @@ "LogLevel": { "Default": "Information", "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.EntityFrameworkCore.Database.Command": "Information" } }, "ConnectionStrings": { diff --git a/Bank.Domain/Account.cs b/Bank.Domain/Account.cs index b2b92c2..cf97706 100644 --- a/Bank.Domain/Account.cs +++ b/Bank.Domain/Account.cs @@ -90,15 +90,16 @@ public void ChargePayment(Invoice invoice) public void AddOperation(AccountOperation accountOperation) => _operations.Add(accountOperation); - public IReadOnlyCollection Operations => _operations; + public IReadOnlyCollection Operations => _operations.AsReadOnly(); public DateTime? LastYieldedDate { get; private set; } public void SetYield(decimal yield, DateTime currentDate) { + this.AddOperation(new AccountOperation(currentDate, $"Rendimento em {currentDate}", yield, EventType.Income)); this.Balance += yield; this.LastYieldedDate = currentDate; this.AddDomainEvent(new CalculatedIncomeEvent(this, yield)); } } -} \ No newline at end of file +} diff --git a/Bank.Domain/AccountOperation.cs b/Bank.Domain/AccountOperation.cs index d5f4b5e..7d1e5d2 100644 --- a/Bank.Domain/AccountOperation.cs +++ b/Bank.Domain/AccountOperation.cs @@ -23,6 +23,7 @@ public AccountOperation(DateTime date, string description, decimal amount, Event public string Description { get; private set; } public decimal Amount { get; private set; } public EventType Operation { get; private set; } - public int AccountNo { get; private set; } + public int AccountNo { get; set; } + public Account Account { get; set; } } -} \ No newline at end of file +} diff --git a/Bank.Domain/Bank.Domain.csproj b/Bank.Domain/Bank.Domain.csproj index e3c6f2f..64e63b2 100644 --- a/Bank.Domain/Bank.Domain.csproj +++ b/Bank.Domain/Bank.Domain.csproj @@ -1,9 +1,9 @@  - net6.0 + net8.0 win-x64;osx-x64;linux-x64 - + \ No newline at end of file diff --git a/Bank.Domain/Contracts/IYieldService.cs b/Bank.Domain/Contracts/IYieldService.cs index 28b66bb..cb0a055 100644 --- a/Bank.Domain/Contracts/IYieldService.cs +++ b/Bank.Domain/Contracts/IYieldService.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace Bank.Domain.Contracts { public interface IYieldService { - void CalculateInterestFor(DateTime currentDate, IYieldAccount account, double interestRate, uint days = 1); - void CalculateInterestFor(DateTime currentDate, IEnumerable accounts, double interestRate, CancellationToken cancellationToken, uint days = 1); + decimal CalculateInterestFor(DateTime currentDate, IYieldAccount account, double interestRate, uint days = 1); } -} \ No newline at end of file +} diff --git a/Bank.Domain/YieldService.cs b/Bank.Domain/YieldService.cs index 7a7c66a..18d0e62 100644 --- a/Bank.Domain/YieldService.cs +++ b/Bank.Domain/YieldService.cs @@ -10,28 +10,21 @@ namespace Bank.Domain { public class YieldService : IYieldService { - public void CalculateInterestFor(DateTime forDate, IYieldAccount account, double interestRate, uint days = 1) + public decimal CalculateInterestFor(DateTime forDate, IYieldAccount account, double interestRate, uint days = 1) { if (HasPositiveBalance(account) && ItHasAlreadyBeenCalculated(forDate, account.LastYieldedDate.GetValueOrDefault(), days)) { decimal calc = Convert.ToDecimal(Math.Pow(1 + interestRate / 100d, days / 252d) - 1); - var balance = Math.Round(calc * account.Balance, 2); - account.SetYield(balance, forDate); + var yield = Math.Round(calc * account.Balance, 2); + return yield; } + return 0; } - - public void CalculateInterestFor(DateTime forDate, IEnumerable accounts, double interestRate, CancellationToken cancellationToken, uint days = 1) - { - var paralleOptions = new ParallelOptions { CancellationToken = cancellationToken }; - - Parallel.ForEach(accounts, paralleOptions, account => - this.CalculateInterestFor(forDate, account, interestRate, days)); - } - + private static bool ItHasAlreadyBeenCalculated(DateTime forDate, DateTime lastYieldedDate, uint days) => forDate > lastYieldedDate.AddDays(days); private static bool HasPositiveBalance(IYieldAccount account) => account.Balance > 0; } -} \ No newline at end of file +} diff --git a/Bank.Infra/Bank.Infra.csproj b/Bank.Infra/Bank.Infra.csproj index 2972b7d..02d93e1 100644 --- a/Bank.Infra/Bank.Infra.csproj +++ b/Bank.Infra/Bank.Infra.csproj @@ -1,19 +1,16 @@ - net6.0 + net8.0 win-x64;osx-x64;linux-x64 - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - + + + + - + \ No newline at end of file diff --git a/Bank.Infra/BankContext.cs b/Bank.Infra/BankContext.cs index e58bca1..23dfca9 100644 --- a/Bank.Infra/BankContext.cs +++ b/Bank.Infra/BankContext.cs @@ -88,7 +88,7 @@ private void ConfigureAccount(ModelBuilder builder) account.Property(a => a.OwnerId); account.HasMany(a => a.Operations) - .WithOne() + .WithOne(o => o.Account) .HasForeignKey(h => h.AccountNo) .OnDelete(DeleteBehavior.Cascade); @@ -106,4 +106,4 @@ private static void CleanEvents(IEnumerable entities) entity.ClearEvents(); } } -} \ No newline at end of file +} diff --git a/Bank.Tests/Bank.Tests.csproj b/Bank.Tests/Bank.Tests.csproj index c123ea5..1814169 100644 --- a/Bank.Tests/Bank.Tests.csproj +++ b/Bank.Tests/Bank.Tests.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 false win-x64;osx-x64;linux-x64 @@ -22,12 +22,12 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + - + \ No newline at end of file diff --git a/Bank.Tests/Unit/YieldServiceTest.cs b/Bank.Tests/Unit/YieldServiceTest.cs index ae581c6..331106c 100644 --- a/Bank.Tests/Unit/YieldServiceTest.cs +++ b/Bank.Tests/Unit/YieldServiceTest.cs @@ -3,6 +3,8 @@ using Bank.Domain; using Bank.Domain.Contracts; +using FluentAssertions; + using NSubstitute; using NSubstitute.ReceivedExtensions; @@ -19,9 +21,9 @@ public void Should_apply_savings_to_balance_every_day_with_by_given_rate() account.Balance.Returns(67.95m); var currentDate = new DateTime(2020, 07, 29); var yieldService = new YieldService(); - yieldService.CalculateInterestFor(currentDate, account, interestRate: 3.52, days: 1); + var yield = yieldService.CalculateInterestFor(currentDate, account, interestRate: 3.52, days: 1); - account.Received().SetYield(.01m, currentDate); + yield.Should().Be(0.01m); } [Theory] @@ -48,9 +50,9 @@ public void Should_not_calculate_interest_rate_twice_for_the_same_day() var currentDate = new DateTime(2020, 07, 29); var yieldService = new YieldService(); - yieldService.CalculateInterestFor(currentDate, account, interestRate: 3.52, days: 1); + var yield = yieldService.CalculateInterestFor(currentDate, account, interestRate: 3.52, days: 1); - account.Received(Quantity.None()).SetYield(Arg.Any(), Arg.Any()); + yield.Should().Be(0); } [Fact] @@ -62,9 +64,9 @@ public void Should_respect_given_days_to_calculate_the_interest_rate() var currentDate = new DateTime(2020, 07, 29); var yieldService = new YieldService(); - yieldService.CalculateInterestFor(currentDate, account, interestRate: 3.52, days: 2); + var yield = yieldService.CalculateInterestFor(currentDate, account, interestRate: 3.52, days: 2); - account.Received(Quantity.None()).SetYield(Arg.Any(), Arg.Any()); + yield.Should().Be(0); } } diff --git a/README.md b/README.md index eb9515c..c579fd3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ## Banco Simples -Esse projeto tem como intenção simular algumas operações bancárias, de maneira simplificada como Depositar, Sacar e Pagar. +Esse projeto tem como intenção simular algumas operações bancárias, de maneira simplificada com ações como: Depositar, Sacar e Pagar. -Foi construído usando .NET 5 e MySQL. +Foi construído usando .NET 8 e MySQL. Para testar a aplicação basta executar o seguinte comando na pasta raiz: @@ -24,7 +24,7 @@ Também é possível interagir com a aplicação vida API: #### Depositar > POST /api/account/deposit -> +> Request body: ```json { @@ -43,7 +43,7 @@ $ curl -X POST "http://localhost:8080/api/account/deposit" \ #### Saque > POST /api/account/withdraw - + Request body: ```json { @@ -59,7 +59,7 @@ $ curl -X POST "http://localhost:8080/api/account/withdraw" \
-#### Pagamento: +#### Pagamento: > POST api/account/payment Request body: @@ -107,7 +107,7 @@ $ curl -X GET "http://localhost:8080/api/account/{id}/statement" -H "accept: te ``` #### Conta com Rendimento -É possível ainda fazer com que a conta tenha rendimentos diários, essa é a api para fazer render a conta +É possível ainda fazer com que a conta tenha rendimentos diários, essa é a api para fazer render a conta > PUT /api/account/calculateIncome Request body