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
27 changes: 26 additions & 1 deletion IMS.API/Controllers/TransactionsController.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
using IMS.API.Models.TransactionsDtos;
using IMS.Core.Contracts;
using IMS.Core.Entities;
using IMS.Infrastructure.Data;
using IMS.Infrastructure.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System.Security.Claims;


namespace IMS.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TransactionsController(ApplicationDbContext _context) : ControllerBase
public class TransactionsController(ApplicationDbContext _context
, IEmailService emailService,
IOptions<NotificationSettings> notificationSettings) : ControllerBase
{

private readonly IEmailService _emailService = emailService;
private readonly NotificationSettings _notificationSettings = notificationSettings.Value;

[HttpGet]
[Authorize(Roles = "Admin,Manager")]
public async Task<IActionResult> GetTransactions()
Expand Down Expand Up @@ -63,6 +71,23 @@ public async Task<IActionResult> RecordTransaction([FromBody] CreateTransactionD
return BadRequest("Insufficient stock for this sale!!");

product.QuantityInStock -= model.Quantity;
if (product.QuantityInStock <= _notificationSettings.LowStockThreshold)
{
var subject = $"Low Stock Alert: {product.Name}";
var body = $"Product '{product.Name}'(ID: {product.Id}) has low stock. \n" +
$"Current quantity: {product.QuantityInStock}\n" +
$"Threshold: {_notificationSettings.LowStockThreshold}\n" +
$"Please restock this item soon";

try
{
await _emailService.SendEmailAsync(_notificationSettings.AdminEmail, subject, body);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send low stock email: {ex.Message}");
}
}
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions IMS.API/IMS.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>3746ec4c-ff25-44f1-8235-94d8fb6bffb6</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<None Include="..\IMS.Infrastructure\Data\SeedData\**\*"
Link="Data\SeedData\%(Filename)%(Extension)">
<None Include="..\IMS.Infrastructure\Data\SeedData\**\*" Link="Data\SeedData\%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions IMS.API/Models/ReportsDtos/InventoryReportDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public class InventoryReportDto

public int TotalProducts { get; set; }

public ReportSummaryDto Sales { get; set; }
public ReportSummaryDto Sales { get; set; } = new ReportSummaryDto();

public ReportSummaryDto Purchases { get; set; }
public ReportSummaryDto Purchases { get; set; } = new ReportSummaryDto();

public List<TopProductDto> TopSellingProducts { get; set; } = new List<TopProductDto>();
}
Expand Down
23 changes: 21 additions & 2 deletions IMS.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using IMS.API.Services;
using IMS.Core.Contracts;
using IMS.Core.Entities;
using IMS.Infrastructure.Data;
using IMS.Infrastructure.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
Expand All @@ -13,6 +15,7 @@




var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<CsvService>();
Expand Down Expand Up @@ -81,6 +84,12 @@
});
});



builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("SmtpSettings"));
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.Configure<NotificationSettings>(builder.Configuration.GetSection("NotificationSettings"));

var app = builder.Build();

// Configure the HTTP request pipeline.
Expand All @@ -96,7 +105,17 @@
app.UseAuthorization();
app.MapControllers();

await RoleSeeder.SeedRolesAsync(app.Services);
await DataSeeder.SeedDataAsync(app.Services);



using (var seedScope = app.Services.CreateScope())
{

var dbContext = seedScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await RoleSeeder.SeedRolesAsync(dbContext, seedScope.ServiceProvider);

await DataSeeder.SeedDataAsync(seedScope.ServiceProvider);
}


app.Run();
24 changes: 21 additions & 3 deletions IMS.API/Services/CsvService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,32 @@ public string ExportProductsToCsv(IEnumerable<Product> products)
{
using var writer = new StringWriter();
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);

csv.WriteHeader<Product>();
csv.WriteField("Id");
csv.WriteField("Name");
csv.WriteField("Descrption");
csv.WriteField("Price");
csv.WriteField("QuantityInStock");
csv.WriteField("Supplier");
csv.WriteField("CategoryId");
csv.WriteField("CreatedAt");
csv.WriteField("LastUpdatedAt");
csv.NextRecord();



foreach (var product in products)
{
csv.WriteRecord(product);
csv.WriteField(product.Id);
csv.WriteField(product.Name);
csv.WriteField(product.Description);
csv.WriteField(product.Price);
csv.WriteField(product.QuantityInStock);
csv.WriteField(product.Supplier);
csv.WriteField(product.CategoryId);
csv.WriteField(product.CreatedAt);
csv.WriteField(product.LastUpdatedAt);
csv.NextRecord();

}
;

Expand Down
15 changes: 15 additions & 0 deletions IMS.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@
"Audience": "InventoryManagementApi",
"SecretKey": "YourSuperSecretKey123456789012345678901234567890",
"ExpiryMinutes": 60
},
"SmtpSettings": {
"Host": "smtp.gmail.com",
"Port": 587,
"Username": "",
"Password": "",
"EnableSsl": true,
"FromEmail": "",
"FromName": "IMS Inventory System"
},
"NotificationSettings": {
"AdminEmail": "mariomedhat899@gmail.com",
"LowStockThreshold" : 10
}

}


7 changes: 7 additions & 0 deletions IMS.Core/Contracts/IEmailService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace IMS.Core.Contracts
{
public interface IEmailService
{
public Task SendEmailAsync(string email, string subject, string body);
}
}
2 changes: 1 addition & 1 deletion IMS.Core/Entities/Category.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class Category : BaseEntity
{

public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }

public ICollection<Product>? Products { get; set; }
Expand Down
39 changes: 36 additions & 3 deletions IMS.Infrastructure/Data/DataSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,55 @@ public static async Task SeedDataAsync(IServiceProvider service)

await context.SaveChangesAsync();


var categoryMap = await context.categories
.ToDictionaryAsync(c => c.Name!, c => c.Id);

var productPath = Path.Combine(AppContext.BaseDirectory, "Data", "SeedData", "Products.json");

var productsData = await File.ReadAllTextAsync(productPath);
var productOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};

var products = JsonSerializer.Deserialize<List<Product>>(productsData);
var products = JsonSerializer.Deserialize<List<ProductSeedDto>>(productsData, productOptions);

if (products is null) return;
foreach (var product in products)
{
if (!categoryMap.TryGetValue(product.CategoryName, out int categoryID))
continue;

context.Products.Add(new Product
{
Name = product.Name,
Description = product.Description,
Price = product.Price,
QuantityInStock = product.QuantityInStock,
Supplier = product.Supplier,
CategoryId = categoryID,
});

}
;

await context.Products.AddRangeAsync(products);

await context.SaveChangesAsync();



}
private class ProductSeedDto
{

public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public int QuantityInStock { get; set; }
public string Supplier { get; set; } = string.Empty;
public string CategoryName { get; set; } = string.Empty;

}
}
}

37 changes: 36 additions & 1 deletion IMS.Infrastructure/Data/RoleSeeder.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using IMS.Core.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace IMS.Infrastructure.Data
{
public class RoleSeeder
{
public static async Task SeedRolesAsync(IServiceProvider serviceProvider)
public static async Task SeedRolesAsync(ApplicationDbContext _context, IServiceProvider serviceProvider)
{
if (_context.Database.GetPendingMigrationsAsync().GetAwaiter().GetResult().Any())
await _context.Database.MigrateAsync();

using var scope = serviceProvider.CreateScope();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
Expand Down Expand Up @@ -44,6 +47,38 @@ public static async Task SeedRolesAsync(IServiceProvider serviceProvider)
await userManager.AddToRoleAsync(adminUser, "Admin");
}
}
var managerEmail = "Manager@test.com";
var managerUser = await userManager.FindByEmailAsync(managerEmail);
if (managerUser is null)
{
managerUser = new ApplicationUser
{
UserName = "manager",
Email = managerEmail,
EmailConfirmed = true
};

var managerResult = await userManager.CreateAsync(managerUser, "Manager@123");
if (managerResult.Succeeded)
{
await userManager.AddToRoleAsync(managerUser, "Manager");
}
}


var staffEmail = "staff@test.com";
var staffUser = await userManager.FindByEmailAsync(staffEmail);
if (staffUser is null)
{
staffUser = new ApplicationUser
{
UserName = "staff",
Email = staffEmail,
EmailConfirmed = true
};
var staffResult = await userManager.CreateAsync(staffUser, "Staff@123");
if (staffResult.Succeeded) await userManager.AddToRoleAsync(staffUser, "Staff");
}



Expand Down
Loading
Loading