Refactor: centralize accounting helpers, status constants, and query deduplication
- AccountingDropdownHelper: wired into BillsController and ExpensesController, replacing 35-40 lines of duplicated DB queries per controller - AppConstants.StatusCodes: added Job.* and Quote.* constants to replace all magic status strings across Jobs, Quotes, Appointments, OvenScheduler, AiQuickQuote, QuoteApproval, and AccountingDropdownHelper - AccountingRules: extracted IsNormalDebitBalance into shared Infrastructure helper; removed duplicate private method from AccountBalanceService and LedgerService (~50 lines deleted) - AccountDataExportController: extracted 9 Fetch*Async methods (superset of includes) so Add*Sheet and Build*Csv no longer duplicate DB queries; each entity is queried once regardless of whether XLSX or CSV format is requested - BillsController.Create and ExpensesController.Create wrapped in ExecuteInTransactionAsync; blob uploads moved after commit to keep financial data atomic and prevent orphaned blobs from rolling back - Number generators (Appointments, CreditMemo, OvenBatch) fixed from full-table GetAllAsync to prefix-filtered FindAsync Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using AutoMapper;
|
||||
using PowderCoating.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -125,18 +125,18 @@ public class JobsController : Controller
|
||||
var todayDate = DateTime.Today;
|
||||
if (statusGroup == "active")
|
||||
{
|
||||
filter = j => j.JobStatus.StatusCode != "COMPLETED"
|
||||
&& j.JobStatus.StatusCode != "READY_FOR_PICKUP"
|
||||
&& j.JobStatus.StatusCode != "DELIVERED"
|
||||
&& j.JobStatus.StatusCode != "CANCELLED";
|
||||
filter = j => j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Completed
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.ReadyForPickup
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Delivered
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Cancelled;
|
||||
}
|
||||
else if (statusGroup == "overdue")
|
||||
{
|
||||
filter = j => j.DueDate < todayDate
|
||||
&& j.JobStatus.StatusCode != "COMPLETED"
|
||||
&& j.JobStatus.StatusCode != "READY_FOR_PICKUP"
|
||||
&& j.JobStatus.StatusCode != "DELIVERED"
|
||||
&& j.JobStatus.StatusCode != "CANCELLED";
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Completed
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.ReadyForPickup
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Delivered
|
||||
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Cancelled;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(searchTerm))
|
||||
@@ -577,7 +577,7 @@ public class JobsController : Controller
|
||||
ViewBag.SourceQuoteId = job.QuoteId;
|
||||
ViewBag.SourceQuoteNumber = job.Quote.QuoteNumber;
|
||||
var preProductionCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{ "PENDING", "QUOTED", "APPROVED" };
|
||||
{ AppConstants.StatusCodes.Job.Pending, AppConstants.StatusCodes.Job.Quoted, AppConstants.StatusCodes.Job.Approved };
|
||||
ViewBag.CanResyncFromQuote = preProductionCodes.Contains(job.JobStatus?.StatusCode ?? "");
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@ public class JobsController : Controller
|
||||
var oldStatusId = job.JobStatusId;
|
||||
job.JobStatusId = newStatusId;
|
||||
job.UpdatedAt = DateTime.UtcNow;
|
||||
if (newStatus.StatusCode == "COMPLETED") job.CompletedDate = DateTime.UtcNow;
|
||||
if (newStatus.StatusCode == AppConstants.StatusCodes.Job.Completed) job.CompletedDate = DateTime.UtcNow;
|
||||
|
||||
var userName = User.Identity?.Name ?? "Shop Floor";
|
||||
await _unitOfWork.JobStatusHistory.AddAsync(new JobStatusHistory
|
||||
@@ -870,10 +870,10 @@ public class JobsController : Controller
|
||||
jobToUpdate.UpdatedAt = now;
|
||||
|
||||
// Optionally advance status to In Preparation
|
||||
if (advanceToInPreparation && jobToUpdate.JobStatus.StatusCode != "IN_PREPARATION")
|
||||
if (advanceToInPreparation && jobToUpdate.JobStatus.StatusCode != AppConstants.StatusCodes.Job.InPreparation)
|
||||
{
|
||||
var allStatuses = await _unitOfWork.JobStatusLookups.GetAllAsync();
|
||||
var inPrepStatus = allStatuses.FirstOrDefault(s => s.StatusCode == "IN_PREPARATION");
|
||||
var inPrepStatus = allStatuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Job.InPreparation);
|
||||
if (inPrepStatus != null)
|
||||
{
|
||||
var oldStatusId = jobToUpdate.JobStatusId;
|
||||
@@ -927,10 +927,10 @@ public class JobsController : Controller
|
||||
job.IntakeCheckedByUserId = userId;
|
||||
job.UpdatedAt = now;
|
||||
|
||||
if (advanceToInPreparation && job.JobStatus.StatusCode != "IN_PREPARATION" && !job.JobStatus.IsTerminalStatus)
|
||||
if (advanceToInPreparation && job.JobStatus.StatusCode != AppConstants.StatusCodes.Job.InPreparation && !job.JobStatus.IsTerminalStatus)
|
||||
{
|
||||
var allStatuses = await _unitOfWork.JobStatusLookups.GetAllAsync();
|
||||
var inPrepStatus = allStatuses.FirstOrDefault(s => s.StatusCode == "IN_PREPARATION");
|
||||
var inPrepStatus = allStatuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Job.InPreparation);
|
||||
if (inPrepStatus != null)
|
||||
{
|
||||
var oldStatusId = job.JobStatusId;
|
||||
@@ -1095,7 +1095,7 @@ public class JobsController : Controller
|
||||
{
|
||||
// Get default "Pending" status (cached)
|
||||
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == "PENDING");
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Job.Pending);
|
||||
|
||||
var job = new Job
|
||||
{
|
||||
@@ -1427,11 +1427,11 @@ public class JobsController : Controller
|
||||
// Update status-related dates
|
||||
if (oldStatusId != dto.JobStatusId && newStatus != null)
|
||||
{
|
||||
if (newStatus.StatusCode == "IN_PREPARATION" && job.StartedDate == null)
|
||||
if (newStatus.StatusCode == AppConstants.StatusCodes.Job.InPreparation && job.StartedDate == null)
|
||||
{
|
||||
job.StartedDate = DateTime.UtcNow;
|
||||
}
|
||||
else if (newStatus.StatusCode == "COMPLETED" && job.CompletedDate == null)
|
||||
else if (newStatus.StatusCode == AppConstants.StatusCodes.Job.Completed && job.CompletedDate == null)
|
||||
{
|
||||
job.CompletedDate = DateTime.UtcNow;
|
||||
}
|
||||
@@ -1916,9 +1916,9 @@ public class JobsController : Controller
|
||||
|
||||
// Load all non-terminal statuses for the progress strip (excluding nav/hold/cancel)
|
||||
var allStatusesEnum = await _unitOfWork.JobStatusLookups.FindAsync(s =>
|
||||
s.StatusCode != "ON_HOLD" && s.StatusCode != "CANCELLED"
|
||||
&& s.StatusCode != "DELIVERED" && s.StatusCode != "QUOTED"
|
||||
&& s.StatusCode != "PENDING" && s.StatusCode != "APPROVED");
|
||||
s.StatusCode != AppConstants.StatusCodes.Job.OnHold && s.StatusCode != AppConstants.StatusCodes.Job.Cancelled
|
||||
&& s.StatusCode != AppConstants.StatusCodes.Job.Delivered && s.StatusCode != AppConstants.StatusCodes.Job.Quoted
|
||||
&& s.StatusCode != AppConstants.StatusCodes.Job.Pending && s.StatusCode != AppConstants.StatusCodes.Job.Approved);
|
||||
var allStatuses = allStatusesEnum.OrderBy(s => s.DisplayOrder).ToList();
|
||||
|
||||
// Get all jobs scheduled for today with related data including items and coats
|
||||
@@ -1935,8 +1935,8 @@ public class JobsController : Controller
|
||||
{
|
||||
var nextStatus = allStatuses
|
||||
.Where(s => s.DisplayOrder > j.JobStatus.DisplayOrder
|
||||
&& s.StatusCode != "ON_HOLD" && s.StatusCode != "CANCELLED"
|
||||
&& s.StatusCode != "DELIVERED")
|
||||
&& s.StatusCode != AppConstants.StatusCodes.Job.OnHold && s.StatusCode != AppConstants.StatusCodes.Job.Cancelled
|
||||
&& s.StatusCode != AppConstants.StatusCodes.Job.Delivered)
|
||||
.OrderBy(s => s.DisplayOrder)
|
||||
.FirstOrDefault();
|
||||
|
||||
@@ -2024,8 +2024,8 @@ public class JobsController : Controller
|
||||
|
||||
var allStatusesEnum = await _unitOfWork.JobStatusLookups.FindAsync(s =>
|
||||
!s.IsTerminalStatus
|
||||
&& s.StatusCode != "ON_HOLD" && s.StatusCode != "CANCELLED"
|
||||
&& s.StatusCode != "DELIVERED");
|
||||
&& s.StatusCode != AppConstants.StatusCodes.Job.OnHold && s.StatusCode != AppConstants.StatusCodes.Job.Cancelled
|
||||
&& s.StatusCode != AppConstants.StatusCodes.Job.Delivered);
|
||||
var allStatuses = allStatusesEnum.OrderBy(s => s.DisplayOrder).ToList();
|
||||
|
||||
var jobs = await _unitOfWork.Jobs.GetActiveJobsForMobileAsync(companyId.Value, workerId);
|
||||
@@ -2164,7 +2164,7 @@ public class JobsController : Controller
|
||||
var oldStatusId = job.JobStatusId;
|
||||
job.JobStatusId = request.NewStatusId;
|
||||
job.UpdatedAt = DateTime.UtcNow;
|
||||
if (newStatus.StatusCode == "COMPLETED") job.CompletedDate = DateTime.UtcNow;
|
||||
if (newStatus.StatusCode == AppConstants.StatusCodes.Job.Completed) job.CompletedDate = DateTime.UtcNow;
|
||||
|
||||
// Log status history
|
||||
await _unitOfWork.JobStatusHistory.AddAsync(new JobStatusHistory
|
||||
@@ -2655,7 +2655,7 @@ public class JobsController : Controller
|
||||
|
||||
// Find the "Completed" status
|
||||
var completedStatus = await _unitOfWork.JobStatusLookups
|
||||
.FirstOrDefaultAsync(s => s.StatusCode == "COMPLETED" && s.CompanyId == job.CompanyId);
|
||||
.FirstOrDefaultAsync(s => s.StatusCode == AppConstants.StatusCodes.Job.Completed && s.CompanyId == job.CompanyId);
|
||||
|
||||
if (completedStatus != null)
|
||||
{
|
||||
@@ -3410,7 +3410,7 @@ public class JobsController : Controller
|
||||
|
||||
// Generate rework job number
|
||||
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == "PENDING");
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Job.Pending);
|
||||
var priorities = await _lookupCache.GetJobPriorityLookupsAsync(companyId);
|
||||
var normalPriority = priorities.FirstOrDefault(p => p.PriorityCode == "NORMAL") ?? priorities.First();
|
||||
|
||||
@@ -3564,7 +3564,7 @@ public class JobsController : Controller
|
||||
|
||||
// Load status lookups to find Pending status
|
||||
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == "PENDING");
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Job.Pending);
|
||||
if (pendingStatus == null) return Json(new { success = false, message = "Could not find Pending status." });
|
||||
|
||||
var priorities = await _lookupCache.GetJobPriorityLookupsAsync(companyId);
|
||||
@@ -3642,7 +3642,7 @@ public class JobsController : Controller
|
||||
|
||||
// Guard: only allow re-sync while job is pre-production
|
||||
var preProductionCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{ "PENDING", "QUOTED", "APPROVED" };
|
||||
{ AppConstants.StatusCodes.Job.Pending, AppConstants.StatusCodes.Job.Quoted, AppConstants.StatusCodes.Job.Approved };
|
||||
if (!preProductionCodes.Contains(job.JobStatus?.StatusCode ?? ""))
|
||||
{
|
||||
TempData["Error"] = "Re-sync is only available before shop work has started.";
|
||||
|
||||
Reference in New Issue
Block a user