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 AutoMapper;
|
||||
using AutoMapper;
|
||||
using PowderCoating.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -543,7 +543,7 @@ public class AppointmentsController : Controller
|
||||
j => j.Customer,
|
||||
j => j.JobStatus);
|
||||
|
||||
var terminalCodes = new[] { "COMPLETED", "DELIVERED", "CANCELLED" };
|
||||
var terminalCodes = new[] { AppConstants.StatusCodes.Job.Completed, AppConstants.StatusCodes.Job.Delivered, AppConstants.StatusCodes.Job.Cancelled };
|
||||
var jobsInRange = allJobs.Where(j =>
|
||||
!terminalCodes.Contains(j.JobStatus.StatusCode) &&
|
||||
((j.ScheduledDate.HasValue && j.ScheduledDate.Value.Date >= start.Date && j.ScheduledDate.Value.Date <= end.Date) ||
|
||||
@@ -618,16 +618,16 @@ public class AppointmentsController : Controller
|
||||
|
||||
return statusCode switch
|
||||
{
|
||||
"PENDING" or "QUOTED" => "#6c757d", // Gray
|
||||
AppConstants.StatusCodes.Job.Pending or "QUOTED" => "#6c757d", // Gray
|
||||
"APPROVED" => "#0dcaf0", // Cyan
|
||||
"IN_PREPARATION" or "SANDBLASTING" or
|
||||
"MASKING_TAPING" or "CLEANING" => "#0d6efd", // Blue
|
||||
"IN_OVEN" or "CURING" => "#fd7e14", // Orange
|
||||
"COATING" => "#6610f2", // Indigo
|
||||
"QUALITY_CHECK" => "#20c997", // Teal
|
||||
"COMPLETED" or "DELIVERED" or "READY_FOR_PICKUP" => "#198754", // Green
|
||||
"ON_HOLD" => "#ffc107", // Yellow
|
||||
"CANCELLED" => "#adb5bd", // Light gray
|
||||
AppConstants.StatusCodes.Job.InPreparation or AppConstants.StatusCodes.Job.Sandblasting or
|
||||
AppConstants.StatusCodes.Job.MaskingTaping or AppConstants.StatusCodes.Job.Cleaning => "#0d6efd", // Blue
|
||||
AppConstants.StatusCodes.Job.InOven or AppConstants.StatusCodes.Job.Curing => "#fd7e14", // Orange
|
||||
AppConstants.StatusCodes.Job.Coating => "#6610f2", // Indigo
|
||||
AppConstants.StatusCodes.Job.QualityCheck => "#20c997", // Teal
|
||||
AppConstants.StatusCodes.Job.Completed or AppConstants.StatusCodes.Job.Delivered or AppConstants.StatusCodes.Job.ReadyForPickup => "#198754", // Green
|
||||
AppConstants.StatusCodes.Job.OnHold => "#ffc107", // Yellow
|
||||
AppConstants.StatusCodes.Job.Cancelled => "#adb5bd", // Light gray
|
||||
_ => "#0d6efd"
|
||||
};
|
||||
}
|
||||
@@ -745,7 +745,7 @@ public class AppointmentsController : Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
var terminalCodes = new[] { "COMPLETED", "DELIVERED", "CANCELLED" };
|
||||
var terminalCodes = new[] { AppConstants.StatusCodes.Job.Completed, AppConstants.StatusCodes.Job.Delivered, AppConstants.StatusCodes.Job.Cancelled };
|
||||
var allJobs = await _unitOfWork.Jobs.GetAllAsync(false,
|
||||
j => j.Customer, j => j.JobStatus, j => j.JobItems);
|
||||
|
||||
@@ -869,27 +869,18 @@ public class AppointmentsController : Controller
|
||||
/// </summary>
|
||||
private async Task<string> GenerateAppointmentNumberAsync()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var prefix = $"APT-{now:yyMM}-";
|
||||
|
||||
// Get all appointments for current month (including soft-deleted)
|
||||
var allAppointments = await _unitOfWork.Appointments.GetAllAsync(ignoreQueryFilters: true);
|
||||
|
||||
var monthAppointments = allAppointments
|
||||
.Where(a => a.AppointmentNumber.StartsWith(prefix))
|
||||
var prefix = $"APT-{DateTime.UtcNow:yyMM}-";
|
||||
var last = (await _unitOfWork.Appointments.FindAsync(
|
||||
a => a.AppointmentNumber.StartsWith(prefix), ignoreQueryFilters: true))
|
||||
.OrderByDescending(a => a.AppointmentNumber)
|
||||
.ToList();
|
||||
.Select(a => a.AppointmentNumber)
|
||||
.FirstOrDefault();
|
||||
|
||||
var lastNumber = 0;
|
||||
if (monthAppointments.Any())
|
||||
{
|
||||
var lastAppointmentNumber = monthAppointments.First().AppointmentNumber;
|
||||
var numberPart = lastAppointmentNumber.Split('-').Last();
|
||||
int.TryParse(numberPart, out lastNumber);
|
||||
}
|
||||
int next = 1;
|
||||
if (last != null && int.TryParse(last[prefix.Length..], out int num))
|
||||
next = num + 1;
|
||||
|
||||
var newNumber = lastNumber + 1;
|
||||
return $"{prefix}{newNumber:D4}";
|
||||
return $"{prefix}{next:D4}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user