15 KiB
Next Steps - Your Powder Coating Application is Ready!
🎉 Current Status: BUILD SUCCESSFUL! ✅
Congratulations! Your application builds without errors. Here's your roadmap to get it running and start building features.
🚀 IMMEDIATE ACTION: Get the App Running (Do This First!)
Step 1: Create the Database (5 minutes)
cd src/PowderCoating.Web
# Create the initial migration
dotnet ef migrations add InitialCreate --project ../PowderCoating.Infrastructure
# Apply it to create the database
dotnet ef database update --project ../PowderCoating.Infrastructure
✅ What This Does:
- Creates
PowderCoatingDbdatabase in SQL Express - Creates 25+ tables (Customers, Jobs, Quotes, Inventory, etc.)
- Seeds 3 pricing tiers (Standard, Preferred, Premium)
- Creates 5 roles (Administrator, Manager, Employee, ShopFloor, ReadOnly)
- Creates admin user:
admin@powdercoating.com/Admin123!
Step 2: Run the Application (1 minute)
# Still in src/PowderCoating.Web
dotnet run
You should see:
info: Now listening on: https://localhost:7001
Step 3: Login and Verify (2 minutes)
- Open browser: https://localhost:7001
- Click Login (top right)
- Use credentials:
- Email:
admin@powdercoating.com - Password:
Admin123!
- Email:
✅ Success! You should be logged in as administrator!
📋 Next: Build Your First Feature (Customer Management)
Now let's add actual functionality. Start with Customer Management because every job needs a customer.
Create the Customers Controller
Create file: src/PowderCoating.Web/Controllers/CustomersController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using AutoMapper;
using PowderCoating.Core.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Application.DTOs.Customer;
namespace PowderCoating.Web.Controllers;
[Authorize]
public class CustomersController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly ILogger<CustomersController> _logger;
public CustomersController(
IUnitOfWork unitOfWork,
IMapper mapper,
ILogger<CustomersController> logger)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_logger = logger;
}
// GET: Customers
public async Task<IActionResult> Index()
{
var customers = await _unitOfWork.Customers.GetAllAsync();
var customerDtos = _mapper.Map<List<CustomerListDto>>(customers);
return View(customerDtos);
}
// GET: Customers/Details/5
public async Task<IActionResult> Details(int id)
{
var customer = await _unitOfWork.Customers.GetByIdAsync(id);
if (customer == null)
{
return NotFound();
}
var customerDto = _mapper.Map<CustomerDto>(customer);
return View(customerDto);
}
// GET: Customers/Create
public IActionResult Create()
{
return View(new CreateCustomerDto());
}
// POST: Customers/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateCustomerDto dto)
{
if (!ModelState.IsValid)
{
return View(dto);
}
try
{
var customer = _mapper.Map<Customer>(dto);
await _unitOfWork.Customers.AddAsync(customer);
await _unitOfWork.SaveChangesAsync();
TempData["Success"] = "Customer created successfully!";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating customer");
ModelState.AddModelError("", "An error occurred while creating the customer.");
return View(dto);
}
}
// GET: Customers/Edit/5
public async Task<IActionResult> Edit(int id)
{
var customer = await _unitOfWork.Customers.GetByIdAsync(id);
if (customer == null)
{
return NotFound();
}
var dto = _mapper.Map<UpdateCustomerDto>(customer);
return View(dto);
}
// POST: Customers/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, UpdateCustomerDto dto)
{
if (id != dto.Id)
{
return NotFound();
}
if (!ModelState.IsValid)
{
return View(dto);
}
try
{
var customer = await _unitOfWork.Customers.GetByIdAsync(id);
if (customer == null)
{
return NotFound();
}
_mapper.Map(dto, customer);
await _unitOfWork.Customers.UpdateAsync(customer);
await _unitOfWork.SaveChangesAsync();
TempData["Success"] = "Customer updated successfully!";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating customer {CustomerId}", id);
ModelState.AddModelError("", "An error occurred while updating the customer.");
return View(dto);
}
}
// GET: Customers/Delete/5
public async Task<IActionResult> Delete(int id)
{
var customer = await _unitOfWork.Customers.GetByIdAsync(id);
if (customer == null)
{
return NotFound();
}
var dto = _mapper.Map<CustomerDto>(customer);
return View(dto);
}
// POST: Customers/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
await _unitOfWork.Customers.SoftDeleteAsync(id);
await _unitOfWork.SaveChangesAsync();
TempData["Success"] = "Customer deleted successfully!";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting customer {CustomerId}", id);
TempData["Error"] = "An error occurred while deleting the customer.";
return RedirectToAction(nameof(Index));
}
}
}
Add Missing AutoMapper Mappings
The UpdateCustomerDto mappings are missing. Update CustomerProfile.cs:
public class CustomerProfile : Profile
{
public CustomerProfile()
{
CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.PricingTierName,
opt => opt.MapFrom(src => src.PricingTier != null ? src.PricingTier.TierName : null));
CreateMap<CreateCustomerDto, Customer>();
CreateMap<UpdateCustomerDto, Customer>(); // ← ADD THIS
CreateMap<Customer, UpdateCustomerDto>(); // ← ADD THIS TOO
CreateMap<Customer, CustomerListDto>()
.ForMember(dest => dest.ContactName,
opt => opt.MapFrom(src => !string.IsNullOrEmpty(src.ContactFirstName) || !string.IsNullOrEmpty(src.ContactLastName)
? $"{src.ContactFirstName} {src.ContactLastName}".Trim()
: string.Empty));
}
}
Create a Basic Index View
Create file: src/PowderCoating.Web/Views/Customers/Index.cshtml
@model List<PowderCoating.Application.DTOs.Customer.CustomerListDto>
@{
ViewData["Title"] = "Customers";
}
<div class="container-fluid">
<div class="row mb-3">
<div class="col">
<h2>Customers</h2>
</div>
<div class="col text-end">
<a asp-action="Create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Add New Customer
</a>
</div>
</div>
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show">
@TempData["Success"]
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
}
<div class="card">
<div class="card-body">
@if (!Model.Any())
{
<p class="text-muted">No customers found. Click "Add New Customer" to get started.</p>
}
else
{
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Company Name</th>
<th>Contact</th>
<th>Email</th>
<th>Phone</th>
<th>Type</th>
<th>Balance</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var customer in Model)
{
<tr>
<td>@customer.CompanyName</td>
<td>@customer.ContactName</td>
<td>@customer.Email</td>
<td>@customer.Phone</td>
<td>
@if (customer.IsCommercial)
{
<span class="badge bg-primary">Commercial</span>
}
else
{
<span class="badge bg-secondary">Non-Commercial</span>
}
</td>
<td>@customer.CurrentBalance.ToString("C")</td>
<td>
@if (customer.IsActive)
{
<span class="badge bg-success">Active</span>
}
else
{
<span class="badge bg-danger">Inactive</span>
}
</td>
<td>
<a asp-action="Details" asp-route-id="@customer.Id" class="btn btn-sm btn-info">Details</a>
<a asp-action="Edit" asp-route-id="@customer.Id" class="btn btn-sm btn-warning">Edit</a>
<a asp-action="Delete" asp-route-id="@customer.Id" class="btn btn-sm btn-danger">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
</div>
Add to Navigation Menu
Edit src/PowderCoating.Web/Views/Shared/_Layout.cshtml and add the Customers link:
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-controller="Customers" asp-action="Index">Customers</a>
</li>
</ul>
Test It!
- Run the app:
dotnet run - Navigate to: https://localhost:7001/Customers
- You should see an empty customer list with "Add New Customer" button
📋 Recommended Development Order
Build features in this sequence for maximum value:
Week 1: Core Data Management
-
✅ Customers (you just started this!)
- Complete Create/Edit forms
- Add validation
- Test CRUD operations
-
Inventory Items
- Powder colors and materials
- SKU tracking
- Reorder alerts
-
Suppliers
- Manage powder suppliers
- Contact information
Week 2: Job Quoting
-
Quotes
- Create quotes for customers
- Line items with pricing
- Quote templates
-
Quote to Job Conversion
- Approve quote → Create job
- Transfer all details
Week 3: Job Management
-
Jobs
- Job creation
- Status workflow (15 stages)
- Job items
-
Job Assignment
- Assign to employees
- Track progress
-
Job Photos & Notes
- Upload before/after photos
- Internal and customer notes
Week 4: Shop Floor
-
Shop Floor Display
- Real-time job board
- Color-coded by priority
- TV-optimized view
-
SignalR Integration
- Real-time status updates
- Auto-refresh displays
Week 5+: Advanced Features
- Equipment Management
- Maintenance Tracking
- Reporting & Analytics
- AI-Powered Quoting
- Customer Portal
🛠️ Essential Commands Reference
Database Commands:
# Add migration after changing entities
dotnet ef migrations add MigrationName --project src/PowderCoating.Infrastructure --startup-project src/PowderCoating.Web
# Update database
dotnet ef database update --project src/PowderCoating.Infrastructure --startup-project src/PowderCoating.Web
# Rollback one migration
dotnet ef database update PreviousMigration --project src/PowderCoating.Infrastructure --startup-project src/PowderCoating.Web
# List all migrations
dotnet ef migrations list --project src/PowderCoating.Infrastructure --startup-project src/PowderCoating.Web
Development:
# Run with auto-reload
dotnet watch run
# Build
dotnet build
# Clean
dotnet clean
# Run tests
dotnet test
💡 Development Tips
1. Use dotnet watch run
Auto-reloads when you save files - huge time saver!
2. Check the Logs
Serilog writes to:
- Console (see terminal)
- File:
logs/powdercoating-YYYYMMDD.txt
3. Bootstrap is Ready
Use Bootstrap 5 classes:
- Tables:
.table,.table-striped,.table-hover - Buttons:
.btn,.btn-primary,.btn-sm - Cards:
.card,.card-body - Forms:
.form-control,.form-label
4. Test Incrementally
Test each feature immediately after building it. Don't wait!
5. Commit Often
git add .
git commit -m "Add customer management"
✅ Your Immediate To-Do List
- Create database (Step 1 above)
- Run the app and login (Steps 2-3)
- Create CustomersController
- Add missing AutoMapper mappings
- Create Index view
- Add navigation menu item
- Test viewing empty customer list
- Create the Create.cshtml form
- Test adding your first customer
- Create Edit and Details views
- Test full CRUD operations
🎯 Success Criteria
You'll know you're on track when:
✅ Database created successfully ✅ Can login as admin ✅ Can navigate to /Customers ✅ See empty list with "Add New Customer" button ✅ Can create a customer ✅ Customer appears in the list ✅ Can edit the customer ✅ Can view customer details ✅ Can delete (soft delete) the customer
🚀 You're Ready to Build!
You have:
- ✅ Solid architecture (Clean Architecture pattern)
- ✅ Database ready (Entity Framework Core)
- ✅ Authentication working (ASP.NET Identity)
- ✅ AutoMapper configured
- ✅ Both Web and API projects
- ✅ Repository pattern implemented
- ✅ Logging set up (Serilog)
Start with the Customer Management module and you'll be up and running in no time!
Need help with specific features? Just ask! 🎉