Files
2026-04-23 21:38:24 -04:00

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 PowderCoatingDb database 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)

  1. Open browser: https://localhost:7001
  2. Click Login (top right)
  3. Use credentials:
    • Email: admin@powdercoating.com
    • Password: Admin123!

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!

  1. Run the app: dotnet run
  2. Navigate to: https://localhost:7001/Customers
  3. You should see an empty customer list with "Add New Customer" button

Build features in this sequence for maximum value:

Week 1: Core Data Management

  1. Customers (you just started this!)

    • Complete Create/Edit forms
    • Add validation
    • Test CRUD operations
  2. Inventory Items

    • Powder colors and materials
    • SKU tracking
    • Reorder alerts
  3. Suppliers

    • Manage powder suppliers
    • Contact information

Week 2: Job Quoting

  1. Quotes

    • Create quotes for customers
    • Line items with pricing
    • Quote templates
  2. Quote to Job Conversion

    • Approve quote → Create job
    • Transfer all details

Week 3: Job Management

  1. Jobs

    • Job creation
    • Status workflow (15 stages)
    • Job items
  2. Job Assignment

    • Assign to employees
    • Track progress
  3. Job Photos & Notes

    • Upload before/after photos
    • Internal and customer notes

Week 4: Shop Floor

  1. Shop Floor Display

    • Real-time job board
    • Color-coded by priority
    • TV-optimized view
  2. SignalR Integration

    • Real-time status updates
    • Auto-refresh displays

Week 5+: Advanced Features

  1. Equipment Management
  2. Maintenance Tracking
  3. Reporting & Analytics
  4. AI-Powered Quoting
  5. 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! 🎉