Skip to content

Commit

Permalink
merge 23-criar-anaalise-interna-pelo-setor-financeiro into main
Browse files Browse the repository at this point in the history
  • Loading branch information
ig-nunes committed Mar 12, 2024
2 parents e784a09 + 33946a8 commit ee6e6ac
Show file tree
Hide file tree
Showing 36 changed files with 1,103 additions and 123 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@

using Microsoft.AspNetCore.Http;
using SmartRefund.Domain.Models;

namespace SmartRefund.Application.Interfaces
{
public interface IFileValidatorService
{
public Task<InternalReceipt?> Validate(IFormFile file, uint employeeId);

public bool ValidateExtension(string fileName);

public bool ValidateSize(long lenght);
}
}
14 changes: 14 additions & 0 deletions src/SmartRefund.Application/Interfaces/IVisionTranslatorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using SmartRefund.Domain.Enums;
using SmartRefund.Domain.Models;

namespace SmartRefund.Application.Interfaces
{
public interface IVisionTranslatorService
{
Task<TranslatedVisionReceipt> GetTranslatedVisionReceipt(RawVisionReceipt rawVisionReceipt);
bool GetIsReceipt(string isReceipt);
TranslatedVisionReceiptCategoryEnum GetCategory(string category);
decimal GetTotal(string total);
string GetDescription(string description);
}
}
101 changes: 98 additions & 3 deletions src/SmartRefund.Application/Services/FileValidatorService.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,112 @@
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using SmartRefund.Application.Interfaces;
using SmartRefund.Domain.Models;
using SmartRefund.Infra.Interfaces;
using System.Reflection.PortableExecutable;


namespace SmartRefund.Application.Services
{
public class FileValidatorService : IFileValidatorService
{
private IRepositoryTeste _repository;
private IInternalReceiptRepository _repository;
private ILogger<FileValidatorService> _logger;
public FileValidatorService(IRepositoryTeste repository, ILogger<FileValidatorService> logger)
private string? _errormessage;
public string? ErrorMessage
{
get
{
return _errormessage;
}
private set
{
_errormessage = value;
}
}

public FileValidatorService(IInternalReceiptRepository repository, ILogger<FileValidatorService> logger)
{
_repository = repository;
_logger = logger;
}

public async Task<InternalReceipt?> Validate(IFormFile file, uint employeeId)
{

if (ValidateSize(file.Length) && ValidateType(file) && ValidateExtension(file.FileName)) //await
{
byte[] imageBytes;
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
imageBytes = memoryStream.ToArray();
}

InternalReceipt receipt = new InternalReceipt(employeeId, imageBytes);

await _repository.AddAsync(receipt);

return receipt;
}

return null;

}

public bool ValidateSize(long lenght)
{
if (lenght >= 20 * 1024 * 1024)
{
throw new ArgumentException("Arquivo é maior do que 20MB");
}
return true;
}

public bool ValidateExtension(string fileName)
{
var extension = Path.GetExtension(fileName);

string[] possibleExtensions = [".png", ".jpg", ".jpeg"];

if(possibleExtensions.Contains(extension))
{
return true;
}
else
{
throw new ArgumentException("Extensão não permitida");
}

}

public bool ValidateType(IFormFile file) //async Task<bool>
{
//byte[] header = new byte[4];
//using (var memoryStream = new MemoryStream())
//{
// await file.CopyToAsync(memoryStream);
// memoryStream.Read(header, 0, 4);
//}

//if (IsJpeg(header) || IsPng(header))
//{
return true;
//}

//throw new ArgumentException("Extensão não permitida");
}

private static bool IsJpeg(byte[] header)
{
// Verifica se os primeiros bytes correspondem ao header de um arquivo JPG
return header[0] == 0xFF && header[1] == 0xD8 && header[2] == 0xFF;
}

private static bool IsPng(byte[] header)
{
// Verifica se os primeiros bytes correspondem ao header de um arquivo PNG
return header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47;
}
}
}
173 changes: 173 additions & 0 deletions src/SmartRefund.Application/Services/VisionTranslatorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using Microsoft.Extensions.Logging;
using SmartRefund.Application.Interfaces;
using SmartRefund.CustomExceptions;
using SmartRefund.Domain.Enums;
using SmartRefund.Domain.Models;
using SmartRefund.Infra.Interfaces;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

namespace SmartRefund.Application.Services
{
public class VisionTranslatorService : IVisionTranslatorService
{
private ITranslatedVisionReceiptRepository _translatorRepository;
private IRawVisionReceiptRepository _rawRepository;
private ILogger<VisionTranslatorService> _logger;

public VisionTranslatorService(ITranslatedVisionReceiptRepository translatorRepository, IRawVisionReceiptRepository rawRepository, ILogger<VisionTranslatorService> logger)
{
_translatorRepository = translatorRepository;
_rawRepository = rawRepository;
_logger = logger;
}

public async Task<TranslatedVisionReceipt> GetTranslatedVisionReceipt(RawVisionReceipt rawVisionReceipt)
{
if(rawVisionReceipt.IsTranslated)
throw new ReceiptAlreadyTranslatedException(rawVisionReceipt.Id);

TranslatedVisionReceipt translatedVisionReceipt =

new TranslatedVisionReceipt(
rawVisionReceipt : rawVisionReceipt,
isReceipt: GetIsReceipt(rawVisionReceipt.IsReceipt),
category: GetCategory(rawVisionReceipt.Category),
status: TranslatedVisionReceiptStatusEnum.SUBMETIDO,
total: GetTotal(rawVisionReceipt.Total),
description: GetDescription(rawVisionReceipt.Description)
);

var addedReceipt = await _translatorRepository.AddAsync(translatedVisionReceipt);
rawVisionReceipt.SetIsTranslated(true);
var updatedRawReceipt = await _rawRepository.UpdateAsync(rawVisionReceipt);

LogSuccess(addedReceipt, updatedRawReceipt);

return addedReceipt;
}

public bool GetIsReceipt(string isReceipt)
{
if (string.IsNullOrWhiteSpace(isReceipt))
throw new FieldIsNullOrWhitespaceException("IsRecept", isReceipt);

bool isYes = Regex.IsMatch(isReceipt, @"\bsim\b", RegexOptions.IgnoreCase);
bool isNo = Regex.IsMatch(isReceipt, @"\bn(ã|a)o\b", RegexOptions.IgnoreCase);
bool isReceiptBool;

if (isYes && !isNo)
isReceiptBool = true;
else if (isNo)
isReceiptBool = false;
else
throw new UnnableToTranslateException("IsReceipt", isReceipt);

LogInformation("IsReceipt", isReceipt, isReceiptBool.ToString());
return isReceiptBool;
}
public TranslatedVisionReceiptCategoryEnum GetCategory(string category)
{
if (string.IsNullOrWhiteSpace(category))
throw new FieldIsNullOrWhitespaceException("Category", category);

string cleanCategory = RemoveDiacritics(category)
.ToLower().Replace("ç", "c").Replace("ã", "a");
//por que é necessário se o RemoveDiacritics passa no teste???

string enumCandidate = GetMatchingCategory(cleanCategory);

if (Enum.TryParse(enumCandidate, out TranslatedVisionReceiptCategoryEnum result))
{
LogInformation("Category", category, result.ToString());
return result;
}

throw new UnnableToTranslateException("Category", category);
}
public decimal GetTotal(string total)
{
if (string.IsNullOrWhiteSpace(total))
throw new FieldIsNullOrWhitespaceException("Total", total);

string numberPattern = @"[\d]+(?:[,.][\d]+)*";
Match match = Regex.Match(total, numberPattern);

if (match.Success)
{
string decimalString = match.Value;

if (decimalString.Length >= 3 && decimalString[decimalString.Length - 3] == ',')
decimalString = decimalString.Remove(decimalString.Length - 3, 1).Insert(decimalString.Length - 3, ".");

for (int i = 0; i < decimalString.Length; i++)
{
if (i != decimalString.Length - 3 && decimalString[i] == '.')
decimalString = decimalString.Remove(i, 1);
}

if (decimal.TryParse(decimalString, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal decimalValue))
{
LogInformation("Total", total, decimalValue.ToString());
return decimalValue;
}
else
throw new UnnableToTranslateException("Total", $"couldn't parse to decimal - {total}");
}
else
{
throw new UnnableToTranslateException("Total", $"no number found at string - {total}");
}
}
public string GetDescription(string description)
{
if (string.IsNullOrWhiteSpace(description))
throw new FieldIsNullOrWhitespaceException("Description", description);

string cleanDescription = description.Replace("\n", "").Trim();
LogInformation("Description", description, cleanDescription);
return cleanDescription;
}

#region Aux Category Methods
public string GetMatchingCategory(string cleanCategory)
{
var validCategories = Enum.GetNames(typeof(TranslatedVisionReceiptCategoryEnum));

return validCategories.FirstOrDefault(validCategory =>
cleanCategory.Contains(validCategory, StringComparison.OrdinalIgnoreCase))
?? "OUTROS";
}

public string RemoveDiacritics(string text)
{
string normalizedText = text.Normalize(NormalizationForm.FormD);
StringBuilder result = new StringBuilder();

foreach (char c in normalizedText)
{
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
result.Append(c);
}

return result.ToString();
}

#endregion

#region Log Aux
private void LogSuccess(TranslatedVisionReceipt addedReceipt, RawVisionReceipt updatedRawReceipt)
{
_logger.LogInformation($"Receipt translated and saved to database " +
$"as TranslatedVisionReceipt (Id {addedReceipt.Id}) " +
$"and RawVisionReceiptUpdated marked as translated (Id {updatedRawReceipt.Id})");
}
private void LogInformation(string type, string raw, string clean)
{
_logger.LogInformation($"Raw{type}: {raw}");
_logger.LogInformation($"Clean{type}: {clean}");
}
#endregion
}
}
4 changes: 4 additions & 0 deletions src/SmartRefund.Application/SmartRefund.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SmartRefund.CustomExceptions\SmartRefund.CustomExceptions.csproj" />
<ProjectReference Include="..\SmartRefund.Domain\SmartRefund.Domain.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

using System.Diagnostics.CodeAnalysis;

namespace SmartRefund.CustomExceptions
{
[ExcludeFromCodeCoverage]
[Serializable]
public class FieldIsNullOrWhitespaceException : Exception
{
public string SpecificEntity { get; private set; }
public string Field { get; private set; }
public FieldIsNullOrWhitespaceException(string field, string specificEntity)
: base($"{field} cannot be null or empty. We need to send it again to GPT ({specificEntity})")
{
Field = field;
SpecificEntity = specificEntity;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

using System.Diagnostics.CodeAnalysis;

namespace SmartRefund.CustomExceptions
{
[ExcludeFromCodeCoverage]
[Serializable]
public class ReceiptAlreadyTranslatedException : Exception
{
public uint ReceiptId { get; private set; }
public ReceiptAlreadyTranslatedException(uint receiptId)
: base($"RawVisionReceipt is already translated. (Id {receiptId})")
{
ReceiptId = receiptId;
}
}
}
19 changes: 19 additions & 0 deletions src/SmartRefund.CustomExceptions/UnnableToTranslateException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

using System.Diagnostics.CodeAnalysis;

namespace SmartRefund.CustomExceptions
{
[ExcludeFromCodeCoverage]
[Serializable]
public class UnnableToTranslateException : Exception
{
public string SpecificEntity { get; private set; }
public string Field { get; private set; }
public UnnableToTranslateException(string field, string specificEntity)
: base($"{field} couldn't be translated. ({specificEntity})")
{
SpecificEntity = specificEntity;
Field = field;
}
}
}
Loading

0 comments on commit ee6e6ac

Please sign in to comment.