a9048dea2e
Added notification preference indicators to both views so staff can see at a glance whether a customer has email/SMS enabled without opening edit mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
517 lines
28 KiB
Plaintext
517 lines
28 KiB
Plaintext
@model PowderCoating.Application.DTOs.Customer.CustomerDto
|
|
|
|
@{
|
|
ViewData["Title"] = !string.IsNullOrWhiteSpace(Model.CompanyName)
|
|
? Model.CompanyName
|
|
: $"{Model.ContactFirstName} {Model.ContactLastName}".Trim();
|
|
ViewData["PageIcon"] = "bi-person-circle";
|
|
}
|
|
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-10">
|
|
<div class="d-flex justify-content-end gap-2 mb-4">
|
|
<div class="d-flex gap-2">
|
|
<a asp-action="Activity" asp-route-id="@Model.Id" class="btn btn-outline-info">
|
|
<i class="bi bi-clock-history me-2"></i>View Activity
|
|
</a>
|
|
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-warning">
|
|
<i class="bi bi-pencil me-2"></i>Edit
|
|
</a>
|
|
<a asp-action="Index" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-2"></i>Back to List
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Banner -->
|
|
<div class="alert @(Model.IsActive ? "alert-success" : "alert-danger") alert-permanent d-flex align-items-center mb-4">
|
|
<i class="bi @(Model.IsActive ? "bi-check-circle" : "bi-x-circle") me-2" style="font-size: 1.5rem;"></i>
|
|
<div>
|
|
<strong>Status:</strong> @(Model.IsActive ? "Active Customer" : "Inactive Customer")
|
|
@if (!Model.IsActive)
|
|
{
|
|
<span class="ms-2">- This customer is currently inactive</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<!-- Left Column -->
|
|
<div class="col-lg-8">
|
|
<!-- Company Information -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-building me-2 text-primary"></i>Company Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-8">
|
|
<label class="text-muted small mb-1">Company Name</label>
|
|
<p class="fw-semibold mb-0">@Model.CompanyName</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="text-muted small mb-1">Customer Type</label>
|
|
<p class="mb-0">
|
|
@if (Model.IsCommercial)
|
|
{
|
|
<span class="badge bg-primary bg-opacity-10 text-primary">
|
|
<i class="bi bi-building me-1"></i>Commercial
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary bg-opacity-10 text-secondary">
|
|
<i class="bi bi-person me-1"></i>Individual
|
|
</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contact Information -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-person me-2 text-primary"></i>Contact Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Contact Name</label>
|
|
<p class="mb-0">
|
|
@if (!string.IsNullOrEmpty(Model.ContactFirstName) || !string.IsNullOrEmpty(Model.ContactLastName))
|
|
{
|
|
<span>@Model.ContactFirstName @Model.ContactLastName</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">Not provided</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Email</label>
|
|
<p class="mb-0">
|
|
<a href="mailto:@Model.Email" class="text-decoration-none">
|
|
<i class="bi bi-envelope me-1"></i>@Model.Email
|
|
</a>
|
|
</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Phone</label>
|
|
<p class="mb-0">
|
|
@if (!string.IsNullOrEmpty(Model.Phone))
|
|
{
|
|
<a href="tel:@Model.Phone" class="text-decoration-none">
|
|
<i class="bi bi-telephone me-1"></i>@Model.Phone
|
|
</a>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">Not provided</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Mobile Phone</label>
|
|
<p class="mb-0">
|
|
@if (!string.IsNullOrEmpty(Model.MobilePhone))
|
|
{
|
|
<a href="tel:@Model.MobilePhone" class="text-decoration-none">
|
|
<i class="bi bi-phone me-1"></i>@Model.MobilePhone
|
|
</a>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">Not provided</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="text-muted small mb-1">Notifications</label>
|
|
<div class="d-flex gap-2">
|
|
@if (Model.NotifyByEmail)
|
|
{
|
|
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25">
|
|
<i class="bi bi-envelope-fill me-1"></i>Email on
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25">
|
|
<i class="bi bi-envelope-slash me-1"></i>Email off
|
|
</span>
|
|
}
|
|
@if (Model.NotifyBySms)
|
|
{
|
|
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25">
|
|
<i class="bi bi-chat-fill me-1"></i>SMS on
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25">
|
|
<i class="bi bi-chat-slash me-1"></i>SMS off
|
|
</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address Information -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-geo-alt me-2 text-primary"></i>Address
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
@if (!string.IsNullOrEmpty(Model.Address))
|
|
{
|
|
<p class="mb-2">@Model.Address</p>
|
|
<p class="mb-0">
|
|
@if (!string.IsNullOrEmpty(Model.City))
|
|
{
|
|
<span>@Model.City</span>
|
|
}
|
|
@if (!string.IsNullOrEmpty(Model.State))
|
|
{
|
|
<span>, @Model.State</span>
|
|
}
|
|
@if (!string.IsNullOrEmpty(Model.ZipCode))
|
|
{
|
|
<span> @Model.ZipCode</span>
|
|
}
|
|
</p>
|
|
@if (!string.IsNullOrEmpty(Model.Country))
|
|
{
|
|
<p class="mb-0 text-muted">@Model.Country</p>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<p class="text-muted mb-0">No address provided</p>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Business Information -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-briefcase me-2 text-primary"></i>Business Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Tax ID / EIN</label>
|
|
<p class="mb-0">@(Model.TaxId ?? "Not provided")</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Payment Terms</label>
|
|
<p class="mb-0">@(Model.PaymentTerms ?? "Not set")</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Credit Limit</label>
|
|
<p class="mb-0 fw-semibold">@Model.CreditLimit.ToString("C")</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Pricing Tier</label>
|
|
<p class="mb-0">@(Model.PricingTierName ?? "Standard")</p>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="text-muted small mb-1">Tax Status</label>
|
|
<p class="mb-0">
|
|
@if (Model.IsTaxExempt)
|
|
{
|
|
<span class="badge bg-success">
|
|
<i class="bi bi-check-circle"></i> Tax Exempt
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Taxable</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
@if (Model.HasTaxExemptCertificate)
|
|
{
|
|
<div class="col-md-12">
|
|
<label class="text-muted small mb-1">Tax Exempt Certificate</label>
|
|
<div class="alert alert-success d-flex justify-content-between align-items-center mb-0 mt-2">
|
|
<div>
|
|
<i class="bi bi-file-earmark-check me-2"></i>
|
|
<strong>File on record:</strong> @Model.TaxExemptCertificateFileName
|
|
</div>
|
|
<a asp-action="TaxExemptCertificate" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-dark" target="_blank">
|
|
<i class="bi bi-download"></i> Download
|
|
</a>
|
|
</div>
|
|
</div>
|
|
}
|
|
@if (Model.IsTaxExempt && !Model.HasTaxExemptCertificate)
|
|
{
|
|
<div class="col-md-12">
|
|
<label class="text-muted small mb-1">Tax Exempt Certificate</label>
|
|
<div class="alert alert-warning mb-0 mt-2">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
<strong>No certificate on file.</strong> Please upload a tax exempt certificate to complete the record.
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes -->
|
|
@if (!string.IsNullOrEmpty(Model.GeneralNotes))
|
|
{
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-journal-text me-2 text-primary"></i>Notes
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="mb-0" style="white-space: pre-wrap;">@Model.GeneralNotes</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Right Column - Statistics -->
|
|
<div class="col-lg-4">
|
|
<!-- Financial Summary -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-currency-dollar me-2 text-primary"></i>Financial Summary
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="text-muted small mb-1">Outstanding Balance</label>
|
|
<h3 class="mb-0 @(Model.CurrentBalance > 0 ? "text-danger" : "text-success")">
|
|
@Model.CurrentBalance.ToString("C")
|
|
</h3>
|
|
</div>
|
|
@if (Model.CreditLimit > 0)
|
|
{
|
|
<div class="mb-3">
|
|
<label class="text-muted small mb-1">Credit Limit</label>
|
|
<p class="mb-0 fw-semibold">@Model.CreditLimit.ToString("C")</p>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="text-muted small mb-1">Available Credit</label>
|
|
<p class="mb-0 fw-semibold text-success">
|
|
@((Model.CreditLimit - Model.CurrentBalance).ToString("C"))
|
|
</p>
|
|
</div>
|
|
}
|
|
<hr class="my-2" />
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<label class="text-muted small mb-1">Store Credit Balance</label>
|
|
<h4 class="mb-0 @(Model.CreditBalance > 0 ? "text-success fw-bold" : "text-muted")">
|
|
@Model.CreditBalance.ToString("C")
|
|
</h4>
|
|
<small class="text-muted">Available for future invoices</small>
|
|
</div>
|
|
@if (User.IsInRole("SuperAdmin") || User.IsInRole("Administrator"))
|
|
{
|
|
<button type="button" class="btn btn-sm btn-success" data-bs-toggle="modal" data-bs-target="#addCreditModal">
|
|
<i class="bi bi-plus-circle me-1"></i>Add Credit
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Store Credit History -->
|
|
@{
|
|
var creditMemos = ViewBag.CreditMemos as List<PowderCoating.Core.Entities.CreditMemo>;
|
|
}
|
|
@if (creditMemos != null && creditMemos.Count > 0)
|
|
{
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-credit-card me-2 text-primary"></i>Store Credit History
|
|
</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th class="ps-3">Memo #</th>
|
|
<th>Issued</th>
|
|
<th class="text-end">Amount</th>
|
|
<th class="text-end pe-3">Remaining</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var memo in creditMemos)
|
|
{
|
|
<tr>
|
|
<td class="ps-3">
|
|
<span class="fw-semibold small">@memo.MemoNumber</span>
|
|
<div class="text-muted" style="font-size:0.75rem;">@memo.Reason</div>
|
|
</td>
|
|
<td class="small text-muted align-middle">@memo.IssueDate.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yy")</td>
|
|
<td class="text-end align-middle small">@memo.Amount.ToString("C")</td>
|
|
<td class="text-end pe-3 align-middle">
|
|
@if (memo.RemainingBalance > 0)
|
|
{
|
|
<span class="badge bg-success">@memo.RemainingBalance.ToString("C")</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-secondary">Used</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Activity -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-clock-history me-2 text-primary"></i>Activity
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="text-muted small mb-1">Last Contact</label>
|
|
<p class="mb-0">
|
|
@if (Model.LastContactDate.HasValue)
|
|
{
|
|
<span>@Model.LastContactDate.Value.ToString("MMMM dd, yyyy")</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">No contact recorded</span>
|
|
}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label class="text-muted small mb-1">Customer Since</label>
|
|
<p class="mb-0">@Model.CreatedAt.ToString("MMMM dd, yyyy")</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white border-0 py-3">
|
|
<h5 class="mb-0 fw-semibold">
|
|
<i class="bi bi-lightning me-2 text-primary"></i>Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-outline-primary">
|
|
<i class="bi bi-pencil me-2"></i>Edit Customer
|
|
</a>
|
|
<a asp-action="JobHistory" asp-route-id="@Model.Id" class="btn btn-outline-success">
|
|
<i class="bi bi-clock-history me-2"></i>Job History
|
|
</a>
|
|
<a asp-action="Invoices" asp-route-id="@Model.Id" class="btn btn-outline-warning">
|
|
<i class="bi bi-receipt me-2"></i>View Invoices
|
|
</a>
|
|
<a asp-controller="Jobs" asp-action="Create" asp-route-customerId="@Model.Id" class="btn btn-outline-success">
|
|
<i class="bi bi-plus-circle me-2"></i>New Job
|
|
</a>
|
|
<a asp-controller="Quotes" asp-action="Create" asp-route-customerId="@Model.Id" class="btn btn-outline-info">
|
|
<i class="bi bi-file-text me-2"></i>New Quote
|
|
</a>
|
|
@if (User.IsInRole("SuperAdmin") || User.IsInRole("Administrator"))
|
|
{
|
|
<button type="button" class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#addCreditModal">
|
|
<i class="bi bi-wallet2 me-2"></i>Add Store Credit
|
|
</button>
|
|
}
|
|
<a asp-action="Delete" asp-route-id="@Model.Id" class="btn btn-outline-danger">
|
|
<i class="bi bi-trash me-2"></i>Delete Customer
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Store Credit Modal -->
|
|
@if (User.IsInRole("SuperAdmin") || User.IsInRole("Administrator"))
|
|
{
|
|
<div class="modal fade" id="addCreditModal" tabindex="-1" aria-labelledby="addCreditModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form asp-action="AddCredit" asp-route-id="@Model.Id" method="post">
|
|
@Html.AntiForgeryToken()
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="addCreditModalLabel">
|
|
<i class="bi bi-wallet2 me-2 text-success"></i>Add Store Credit
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-muted small mb-3">
|
|
Credits can be applied to any future invoice for this customer.
|
|
Current balance: <strong class="text-success">@Model.CreditBalance.ToString("C")</strong>
|
|
</p>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Amount <span class="text-danger">*</span></label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">$</span>
|
|
<input type="number" name="Amount" class="form-control" step="0.01" min="0.01" max="99999.99" required placeholder="0.00" />
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Reason <span class="text-danger">*</span></label>
|
|
<select name="Reason" class="form-select" required id="creditReasonSelect">
|
|
<option value="">— Select reason —</option>
|
|
<option value="Pre-payment / Deposit">Pre-payment / Deposit</option>
|
|
<option value="Gift / Gift Card">Gift / Gift Card</option>
|
|
<option value="Overpayment credit">Overpayment credit</option>
|
|
<option value="Goodwill credit">Goodwill credit</option>
|
|
<option value="Other">Other</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Notes</label>
|
|
<textarea name="Notes" class="form-control" rows="2" maxlength="1000" placeholder="Optional details..."></textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Expiry Date <span class="text-muted fw-normal">(optional)</span></label>
|
|
<input type="date" name="ExpiryDate" class="form-control" />
|
|
<div class="form-text">Leave blank for no expiry.</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="bi bi-plus-circle me-1"></i>Add Credit
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|