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

547 lines
15 KiB
Markdown

# 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)
```bash
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)
```bash
# 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`
```csharp
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`:
```csharp
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`
```html
@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:
```html
<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
---
## 📋 Recommended Development Order
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
4. **Quotes**
- Create quotes for customers
- Line items with pricing
- Quote templates
5. **Quote to Job Conversion**
- Approve quote → Create job
- Transfer all details
### Week 3: Job Management
6. **Jobs**
- Job creation
- Status workflow (15 stages)
- Job items
7. **Job Assignment**
- Assign to employees
- Track progress
8. **Job Photos & Notes**
- Upload before/after photos
- Internal and customer notes
### Week 4: Shop Floor
9. **Shop Floor Display**
- Real-time job board
- Color-coded by priority
- TV-optimized view
10. **SignalR Integration**
- Real-time status updates
- Auto-refresh displays
### Week 5+: Advanced Features
11. **Equipment Management**
12. **Maintenance Tracking**
13. **Reporting & Analytics**
14. **AI-Powered Quoting**
15. **Customer Portal**
---
## 🛠️ Essential Commands Reference
### Database Commands:
```bash
# 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:
```bash
# 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
```bash
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! 🎉