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:
@@ -84,6 +84,31 @@ public class FormulaLibraryController : Controller
|
||||
return File(bytes, contentType ?? "image/jpeg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records or toggles a thumbs-up/down vote for the current company.
|
||||
/// Returns updated counts so the UI can update without a page reload.
|
||||
/// Companies cannot rate their own formulas; own-formula cards have no rating buttons.
|
||||
/// </summary>
|
||||
// POST: /FormulaLibrary/Rate
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Rate([FromBody] RateFormulaRequest request)
|
||||
{
|
||||
var companyId = _tenantContext.GetCurrentCompanyId();
|
||||
if (companyId == null) return Json(new { success = false, message = "No company context." });
|
||||
|
||||
try
|
||||
{
|
||||
var (up, down, myVote) = await _libraryService.RateAsync(
|
||||
request.LibraryItemId, companyId.Value, request.IsPositive);
|
||||
return Json(new { success = true, thumbsUp = up, thumbsDown = down, myVote });
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Json(new { success = false, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Imports a library entry as a new local template for the current company.</summary>
|
||||
// POST: /FormulaLibrary/Import
|
||||
[HttpPost]
|
||||
@@ -107,3 +132,11 @@ public class FormulaLibraryController : Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Body for the Rate endpoint.</summary>
|
||||
public class RateFormulaRequest
|
||||
{
|
||||
public int LibraryItemId { get; set; }
|
||||
/// <summary>True = thumbs up, false = thumbs down.</summary>
|
||||
public bool IsPositive { get; set; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user