Add Formula Library ratings, Job Profitability report, and Quote Revision History improvements
- Formula Library ratings: thumbs up/down per company per formula; toggle on/off; sorts by net score; own formulas not rateable; FormulaLibraryRating entity + migration AddFormulaLibraryRatings - Job Profitability report: actual labor cost (logged hours x StandardLaborRate) vs powder cost vs billed price per job; gross margin % color-coded; time-tracked-only filter; totals footer - Quote Revision History: track Total price changes on every save; log Sent/Resent events with recipient email; replace flat table with grouped timeline UI (icons per event type, total-change badge on header) - Setup Wizard: cap CompletedCount at TotalSteps so old 10-step data no longer shows 10/5 - Formula Library card: fix badge overflow on long titles; add Rate: label to make voting buttons discoverable - Help docs and AI knowledge base updated for all three features Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1320,6 +1320,7 @@ public class QuotesController : Controller
|
||||
Terms = quote.Terms,
|
||||
Notes = quote.Notes,
|
||||
TaxPercent = quote.TaxPercent,
|
||||
Total = quote.Total,
|
||||
DiscountType = quote.DiscountType,
|
||||
DiscountValue = quote.DiscountValue,
|
||||
DiscountReason = quote.DiscountReason,
|
||||
@@ -1342,9 +1343,27 @@ public class QuotesController : Controller
|
||||
// Set calculated pricing — snapshot at save time; never recalculate on load
|
||||
_quotePricingAssemblyService.ApplyPricingSnapshot(quote, pricingResult);
|
||||
|
||||
// Track changes
|
||||
// All change history records are accumulated here, then saved in bulk below
|
||||
var changeHistories = new List<QuoteChangeHistory>();
|
||||
|
||||
// Log a total-change entry now that the new Total is known
|
||||
if (Math.Round(oldValues.Total, 2) != Math.Round(quote.Total, 2))
|
||||
{
|
||||
changeHistories.Add(new QuoteChangeHistory
|
||||
{
|
||||
QuoteId = quote.Id,
|
||||
ChangedByUserId = currentUser.Id,
|
||||
ChangedAt = DateTime.UtcNow,
|
||||
FieldName = "Total",
|
||||
OldValue = oldValues.Total.ToString("C"),
|
||||
NewValue = quote.Total.ToString("C"),
|
||||
ChangeDescription = $"Total changed from {oldValues.Total:C} to {quote.Total:C}",
|
||||
CompanyId = currentUser.CompanyId
|
||||
});
|
||||
}
|
||||
|
||||
// Track changes
|
||||
|
||||
_logger.LogInformation("=== CHANGE TRACKING DEBUG ===");
|
||||
_logger.LogInformation("Old Status: {OldStatus}, New Status: {NewStatus}", oldValues.QuoteStatusId, quote.QuoteStatusId);
|
||||
_logger.LogInformation("Old Date: {OldDate}, New Date: {NewDate}", oldValues.QuoteDate, quote.QuoteDate);
|
||||
@@ -3174,6 +3193,22 @@ public class QuotesController : Controller
|
||||
}
|
||||
|
||||
await _unitOfWork.Quotes.UpdateAsync(quote);
|
||||
|
||||
// Log send event so the history timeline shows when the quote was emailed
|
||||
var sentHistoryEntry = new QuoteChangeHistory
|
||||
{
|
||||
QuoteId = quote.Id,
|
||||
ChangedByUserId = currentUser!.Id,
|
||||
ChangedAt = DateTime.UtcNow,
|
||||
FieldName = "Sent",
|
||||
OldValue = null,
|
||||
NewValue = recipientEmail,
|
||||
ChangeDescription = $"Quote sent to {recipientName} ({recipientEmail})",
|
||||
CompanyId = quote.CompanyId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
await _unitOfWork.QuoteChangeHistories.AddAsync(sentHistoryEntry);
|
||||
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
await _notificationService.NotifyQuoteSentAsync(quote, pdfBytes, pdfFilename, trimmedOverride);
|
||||
|
||||
Reference in New Issue
Block a user