Skip to content

Commit

Permalink
refactor: grocy product stock
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-rw committed Jan 30, 2024
1 parent 1bbf014 commit e6a71c9
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 33 deletions.
34 changes: 6 additions & 28 deletions GrocyScanner.Core/GrocyClient/GrocyClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ public class GrocyClient : IGrocyClient
private readonly IOptions<GrocyConfiguration> _grocyConfiguration;
private readonly IGrocyQuantityUnit _grocyQuantityUnit;
private readonly IGrocyLocations _grocyLocations;
private readonly IProductStock _productStock;

public GrocyClient(IHttpClientFactory httpClientFactory, ILogger<GrocyClient> logger,
IOptions<GrocyConfiguration> grocyConfiguration, IGrocyQuantityUnit grocyQuantityUnit,
IGrocyLocations grocyLocations)
IGrocyLocations grocyLocations, IProductStock productStock)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_grocyConfiguration = grocyConfiguration;
_grocyQuantityUnit = grocyQuantityUnit;
_grocyLocations = grocyLocations;
_productStock = productStock;
}

public async Task<int?> GetProductIdByBarcode(string gtin)
Expand All @@ -43,7 +45,7 @@ public GrocyClient(IHttpClientFactory httpClientFactory, ILogger<GrocyClient> lo
}

string content = await response.Content.ReadAsStringAsync();
var productBarcodes = JsonSerializer.Deserialize<IEnumerable<GrocyProductBarcode>>(content)!;
IEnumerable<GrocyProductBarcode>? productBarcodes = JsonSerializer.Deserialize<IEnumerable<GrocyProductBarcode>>(content)!;
return productBarcodes.SingleOrDefault(productBarcode => productBarcode.Barcode.Equals(gtin))?.ProductId;
}

Expand Down Expand Up @@ -95,7 +97,7 @@ public async Task<bool> UpsertProduct(Product product, int amount, DateOnly? bes
int? existingGrocyProductId = await GetProductIdByBarcode(product.Gtin);
if (existingGrocyProductId.HasValue)
{
await AddProductToStock(existingGrocyProductId.Value, amount, defaultLocationId, bestBefore, price);
await _productStock.AddProductToStockAsync(existingGrocyProductId.Value, amount, defaultLocationId, bestBefore, price);
return true;
}

Expand All @@ -111,34 +113,10 @@ await CreateProductBarcodeAsync(new GrocyProductBarcode
Barcode = product.Gtin,
ProductId = productId.Value
});
await AddProductToStock(productId.Value, amount, defaultLocationId, bestBefore, price);
await _productStock.AddProductToStockAsync(productId.Value, amount, defaultLocationId, bestBefore, price);
return true;
}

public async Task AddProductToStock(int productId, int amount, long locationId, DateOnly? bestBefore, double? price)
{
if (amount <= 0)
{
throw new ArgumentException("Amount cannot be negative", nameof(amount));
}

// this is Grocy's way of "no due date". Inserting null sadly displays "today" as the due date
DateOnly bestBeforeOrDefault = bestBefore ?? new DateOnly(2999, 12, 31);

HttpClient httpClient = _httpClientFactory.CreateClient();
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post,
$"{_grocyConfiguration.Value.BaseUrl}/api/stock/products/{productId}/add");
httpRequestMessage.Headers.Add("GROCY-API-KEY", _grocyConfiguration.Value.ApiKey);
httpRequestMessage.Headers.Add("accept", "application/json");
string json =
$@"{{""amount"": {amount},""best_before_date"":""{bestBeforeOrDefault:yyyy-MM-dd}"",""price"":""{price:N}"",""note"":"""",""location_id"":""{locationId}""}}";
httpRequestMessage.Content = new StringContent(json);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
await httpClient.SendAsync(httpRequestMessage);
_logger.LogInformation("Added {ProductId} to stock with price {Price} and date {BestBeforeDate}, {Json}",
productId, price, bestBefore, json);
}

private async Task<int?> CreateProductAsync(Product product, long locationId, long quantityUnitId)
{
string? pictureFileName = null;
Expand Down
66 changes: 66 additions & 0 deletions GrocyScanner.Core/GrocyClient/GrocyProductStock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Net.Http.Headers;
using GrocyScanner.Core.Configurations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace GrocyScanner.Core.GrocyClient;

public class GrocyProductStock : IProductStock
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IOptions<GrocyConfiguration> _grocyConfiguration;
private readonly ILogger<GrocyProductStock> _logger;

public GrocyProductStock(IHttpClientFactory httpClientFactory, IOptions<GrocyConfiguration> grocyConfiguration,
ILogger<GrocyProductStock> logger)
{
_httpClientFactory = httpClientFactory;
_grocyConfiguration = grocyConfiguration;
_logger = logger;
}

public async Task AddProductToStockAsync(int productId, int amount, long locationId, DateOnly? bestBefore,
double? price)
{
if (amount <= 0)
{
throw new ArgumentException("Amount cannot be negative", nameof(amount));
}

// this is Grocy's way of "no due date". Inserting null sadly displays "today" as the due date
DateOnly bestBeforeOrDefault = bestBefore ?? new DateOnly(2999, 12, 31);

HttpClient httpClient = _httpClientFactory.CreateClient();
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post,
$"{_grocyConfiguration.Value.BaseUrl}/api/stock/products/{productId}/add");
httpRequestMessage.Headers.Add("GROCY-API-KEY", _grocyConfiguration.Value.ApiKey);
httpRequestMessage.Headers.Add("accept", "application/json");
string json =
$@"{{""amount"": {amount},""best_before_date"":""{bestBeforeOrDefault:yyyy-MM-dd}"",""price"":""{price:N}"",""note"":"""",""location_id"":""{locationId}""}}";
httpRequestMessage.Content = new StringContent(json);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
await httpClient.SendAsync(httpRequestMessage);
_logger.LogInformation("Added {ProductId} to stock with price {Price} and date {BestBeforeDate}, {Json}",
productId, price, bestBefore, json);
}

public async Task ConsumeProduct(int productId, int amount, bool spoiled)
{
if (amount < 1)
{
throw new ArgumentException("Amount must be greater or equal than one", nameof(amount));
}

HttpClient httpClient = _httpClientFactory.CreateClient();
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post,
$"{_grocyConfiguration.Value.BaseUrl}/api/stock/products/{productId}/consume");
httpRequestMessage.Headers.Add("GROCY-API-KEY", _grocyConfiguration.Value.ApiKey);
httpRequestMessage.Headers.Add("accept", "application/json");
string json =
$@"{{""amount"": {amount},""transaction_type"": ""consume"",""spoiled"":""{spoiled}""}}";
httpRequestMessage.Content = new StringContent(json);
httpRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
}
}
2 changes: 0 additions & 2 deletions GrocyScanner.Core/GrocyClient/IGrocyClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@ public interface IGrocyClient
public Task<Product?> GetProductByBarcode(string gtin);

public Task<bool> UpsertProduct(Product product, int amount, DateOnly? bestBefore, double? price);

public Task AddProductToStock(int productId, int amount, long locationId, DateOnly? bestBefore, double? price);
}
8 changes: 8 additions & 0 deletions GrocyScanner.Core/GrocyClient/IProductStock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace GrocyScanner.Core.GrocyClient;

public interface IProductStock
{
public Task AddProductToStockAsync(int productId, int amount, long locationId, DateOnly? bestBefore, double? price);

public Task ConsumeProduct(int productId, int amount, bool spoiled);
}
2 changes: 1 addition & 1 deletion GrocyScanner.Core/Providers/MigrosProductProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private async ValueTask<string> GetAndCreateAuthorizationHeader()
using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, "https://www.migros.ch/authentication/public/v1/api/guest?authorizationNotRequired=true");
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
var values = httpResponseMessage.Headers.GetValues("leshopch");
IEnumerable<string>? values = httpResponseMessage.Headers.GetValues("leshopch");
string authorizationToken = values.First();
_authorizationHeader = authorizationToken;
return authorizationToken;
Expand Down
2 changes: 1 addition & 1 deletion GrocyScanner.Service/Pages/Scanner.razor
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@

private async Task OpenDialog(string barcode)
{
var parameters = new DialogParameters<AddProductDialog> { { nameof(AddProductDialog.Barcode), barcode } };
DialogParameters<AddProductDialog>? parameters = new DialogParameters<AddProductDialog> { { nameof(AddProductDialog.Barcode), barcode } };
Barcode = string.Empty;
IDialogReference dialogReference = await DialogService.ShowAsync<AddProductDialog>("Purchase Product", parameters, new DialogOptions
{
Expand Down
1 change: 1 addition & 0 deletions GrocyScanner.Service/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
builder.Services.AddSingleton<IGrocyClient, GrocyClient>();
builder.Services.AddSingleton<IGrocyQuantityUnit, GrocyQuantityUnitsMasterData>();
builder.Services.AddSingleton<IGrocyLocations, GrocyLocationMasterData>();
builder.Services.AddSingleton<IProductStock, GrocyProductStock>();
builder.Services.AddSignalR();
builder.Services.AddScoped<QrCodeScanHub>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
// product is not known, must be searched in the providers
if (productFromGrocy == null)
{
var products = await Task.WhenAll(ProductProviders.Select(async provider => await provider.GetProductByGtin(Barcode)));
Product?[]? products = await Task.WhenAll(ProductProviders.Select(async provider => await provider.GetProductByGtin(Barcode)));
Product = BestValueCalculator.GetProductWithMostValue(products.Where(product => product != null).Cast<Product>().ToList());
}
else
Expand Down

0 comments on commit e6a71c9

Please sign in to comment.