Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7ab85ff92 | |||
| ce7b00b68c | |||
| c5c1244177 | |||
| 25140554ad | |||
| 46cadea367 | |||
| cfe937c0c3 | |||
| 3ad6b0d08f | |||
| fdac0240d1 |
@@ -486,7 +486,6 @@ public class ReworkRecordDto
|
|||||||
public decimal ActualReworkCost { get; set; }
|
public decimal ActualReworkCost { get; set; }
|
||||||
public bool IsBillableToCustomer { get; set; }
|
public bool IsBillableToCustomer { get; set; }
|
||||||
public string? BillingNotes { get; set; }
|
public string? BillingNotes { get; set; }
|
||||||
public PowderCoating.Core.Enums.ReworkPricingType? ReworkPricingType { get; set; }
|
|
||||||
|
|
||||||
public PowderCoating.Core.Enums.ReworkStatus Status { get; set; }
|
public PowderCoating.Core.Enums.ReworkStatus Status { get; set; }
|
||||||
public string StatusDisplay { get; set; } = string.Empty;
|
public string StatusDisplay { get; set; } = string.Empty;
|
||||||
@@ -512,11 +511,6 @@ public class CreateReworkRecordDto
|
|||||||
public decimal EstimatedReworkCost { get; set; }
|
public decimal EstimatedReworkCost { get; set; }
|
||||||
public bool IsBillableToCustomer { get; set; }
|
public bool IsBillableToCustomer { get; set; }
|
||||||
public string? BillingNotes { get; set; }
|
public string? BillingNotes { get; set; }
|
||||||
|
|
||||||
// Rework job creation (opt-in)
|
|
||||||
public bool CreateReworkJob { get; set; }
|
|
||||||
public List<int>? ReworkJobItemIds { get; set; } // null = not creating a job
|
|
||||||
public PowderCoating.Core.Enums.ReworkPricingType? ReworkPricingType { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateReworkRecordDto
|
public class UpdateReworkRecordDto
|
||||||
|
|||||||
@@ -196,9 +196,7 @@ public class JobProfile : Profile
|
|||||||
.ForMember(dest => dest.JobItemDescription,
|
.ForMember(dest => dest.JobItemDescription,
|
||||||
opt => opt.MapFrom(src => src.JobItem != null ? src.JobItem.Description : null))
|
opt => opt.MapFrom(src => src.JobItem != null ? src.JobItem.Description : null))
|
||||||
.ForMember(dest => dest.ReworkJobNumber,
|
.ForMember(dest => dest.ReworkJobNumber,
|
||||||
opt => opt.MapFrom(src => src.ReworkJob != null ? src.ReworkJob.JobNumber : null))
|
opt => opt.MapFrom(src => src.ReworkJob != null ? src.ReworkJob.JobNumber : null));
|
||||||
.ForMember(dest => dest.ReworkPricingType,
|
|
||||||
opt => opt.MapFrom(src => src.ReworkPricingType));
|
|
||||||
|
|
||||||
// Job → JobDto (rework fields)
|
// Job → JobDto (rework fields)
|
||||||
// (IsReworkJob and OriginalJobId map by convention; OriginalJobNumber needs explicit map — handled in controller)
|
// (IsReworkJob and OriginalJobId map by convention; OriginalJobNumber needs explicit map — handled in controller)
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ public class ReworkRecord : BaseEntity
|
|||||||
public bool IsBillableToCustomer { get; set; }
|
public bool IsBillableToCustomer { get; set; }
|
||||||
public string? BillingNotes { get; set; }
|
public string? BillingNotes { get; set; }
|
||||||
|
|
||||||
// Pricing attribution for the linked rework job (null on pre-existing records)
|
|
||||||
public ReworkPricingType? ReworkPricingType { get; set; }
|
|
||||||
|
|
||||||
// ── Resolution ────────────────────────────────────────────────────────────
|
// ── Resolution ────────────────────────────────────────────────────────────
|
||||||
public ReworkStatus Status { get; set; } = ReworkStatus.Open;
|
public ReworkStatus Status { get; set; } = ReworkStatus.Open;
|
||||||
public ReworkResolution? Resolution { get; set; }
|
public ReworkResolution? Resolution { get; set; }
|
||||||
|
|||||||
@@ -144,14 +144,6 @@ public enum ReworkResolution
|
|||||||
NoActionRequired = 4
|
NoActionRequired = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Who bears the cost of the rework job, recorded at the time the rework is logged.</summary>
|
|
||||||
public enum ReworkPricingType
|
|
||||||
{
|
|
||||||
ShopFault = 0, // Redo is on the shop — rework job items priced at $0
|
|
||||||
CustomerReduced = 1, // Customer caused it; we're helping — prices copied, user edits
|
|
||||||
CustomerFull = 2 // Customer caused it; full original pricing applies
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BugReportStatus
|
public enum BugReportStatus
|
||||||
{
|
{
|
||||||
New = 0,
|
New = 0,
|
||||||
|
|||||||
@@ -92,10 +92,4 @@ public interface IJobRepository : IRepository<Job>
|
|||||||
/// were never completed and rolled past their scheduled day.
|
/// were never completed and rolled past their scheduled day.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<List<Job>> GetOverdueScheduledJobsAsync();
|
Task<List<Job>> GetOverdueScheduledJobsAsync();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the count of rework jobs linked to <paramref name="originalJobId"/>
|
|
||||||
/// (including soft-deleted) so the next rework suffix (R1, R2, …) can be determined.
|
|
||||||
/// </summary>
|
|
||||||
Task<int> GetReworkJobCountAsync(int originalJobId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-10642
File diff suppressed because it is too large
Load Diff
@@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace PowderCoating.Infrastructure.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class AddReworkPricingType : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<int>(
|
|
||||||
name: "ReworkPricingType",
|
|
||||||
table: "ReworkRecords",
|
|
||||||
type: "int",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "PricingTiers",
|
|
||||||
keyColumn: "Id",
|
|
||||||
keyValue: 1,
|
|
||||||
column: "CreatedAt",
|
|
||||||
value: new DateTime(2026, 5, 21, 14, 32, 24, 337, DateTimeKind.Utc).AddTicks(8533));
|
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "PricingTiers",
|
|
||||||
keyColumn: "Id",
|
|
||||||
keyValue: 2,
|
|
||||||
column: "CreatedAt",
|
|
||||||
value: new DateTime(2026, 5, 21, 14, 32, 24, 337, DateTimeKind.Utc).AddTicks(8542));
|
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "PricingTiers",
|
|
||||||
keyColumn: "Id",
|
|
||||||
keyValue: 3,
|
|
||||||
column: "CreatedAt",
|
|
||||||
value: new DateTime(2026, 5, 21, 14, 32, 24, 337, DateTimeKind.Utc).AddTicks(8543));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ReworkPricingType",
|
|
||||||
table: "ReworkRecords");
|
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "PricingTiers",
|
|
||||||
keyColumn: "Id",
|
|
||||||
keyValue: 1,
|
|
||||||
column: "CreatedAt",
|
|
||||||
value: new DateTime(2026, 5, 19, 19, 26, 9, 226, DateTimeKind.Utc).AddTicks(5186));
|
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "PricingTiers",
|
|
||||||
keyColumn: "Id",
|
|
||||||
keyValue: 2,
|
|
||||||
column: "CreatedAt",
|
|
||||||
value: new DateTime(2026, 5, 19, 19, 26, 9, 226, DateTimeKind.Utc).AddTicks(5190));
|
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
|
||||||
table: "PricingTiers",
|
|
||||||
keyColumn: "Id",
|
|
||||||
keyValue: 3,
|
|
||||||
column: "CreatedAt",
|
|
||||||
value: new DateTime(2026, 5, 19, 19, 26, 9, 226, DateTimeKind.Utc).AddTicks(5191));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6711,7 +6711,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
CompanyId = 0,
|
CompanyId = 0,
|
||||||
CreatedAt = new DateTime(2026, 5, 21, 14, 32, 24, 337, DateTimeKind.Utc).AddTicks(8533),
|
CreatedAt = new DateTime(2026, 5, 19, 19, 26, 9, 226, DateTimeKind.Utc).AddTicks(5186),
|
||||||
Description = "Standard pricing for regular customers",
|
Description = "Standard pricing for regular customers",
|
||||||
DiscountPercent = 0m,
|
DiscountPercent = 0m,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
@@ -6722,7 +6722,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
CompanyId = 0,
|
CompanyId = 0,
|
||||||
CreatedAt = new DateTime(2026, 5, 21, 14, 32, 24, 337, DateTimeKind.Utc).AddTicks(8542),
|
CreatedAt = new DateTime(2026, 5, 19, 19, 26, 9, 226, DateTimeKind.Utc).AddTicks(5190),
|
||||||
Description = "5% discount for preferred customers",
|
Description = "5% discount for preferred customers",
|
||||||
DiscountPercent = 5m,
|
DiscountPercent = 5m,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
@@ -6733,7 +6733,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
CompanyId = 0,
|
CompanyId = 0,
|
||||||
CreatedAt = new DateTime(2026, 5, 21, 14, 32, 24, 337, DateTimeKind.Utc).AddTicks(8543),
|
CreatedAt = new DateTime(2026, 5, 19, 19, 26, 9, 226, DateTimeKind.Utc).AddTicks(5191),
|
||||||
Description = "10% discount for premium customers",
|
Description = "10% discount for premium customers",
|
||||||
DiscountPercent = 10m,
|
DiscountPercent = 10m,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
@@ -7990,9 +7990,6 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
b.Property<int?>("ReworkJobId")
|
b.Property<int?>("ReworkJobId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int?>("ReworkPricingType")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<int>("ReworkType")
|
b.Property<int>("ReworkType")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
@@ -187,14 +187,6 @@ public class JobRepository : Repository<Job>, IJobRepository
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public async Task<int> GetReworkJobCountAsync(int originalJobId)
|
|
||||||
{
|
|
||||||
return await _context.Jobs
|
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.CountAsync(j => j.OriginalJobId == originalJobId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<List<Job>> GetOverdueScheduledJobsAsync()
|
public async Task<List<Job>> GetOverdueScheduledJobsAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1623,12 +1623,11 @@ public class InventoryController : Controller
|
|||||||
/// Renders a print-optimised label for the inventory item containing the QR code,
|
/// Renders a print-optimised label for the inventory item containing the QR code,
|
||||||
/// item name, SKU, and colour. Designed to be printed directly from the browser.
|
/// item name, SKU, and colour. Designed to be printed directly from the browser.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IActionResult> Label(int? id, bool embed = false)
|
public async Task<IActionResult> Label(int? id)
|
||||||
{
|
{
|
||||||
if (id == null) return NotFound();
|
if (id == null) return NotFound();
|
||||||
var item = await _unitOfWork.InventoryItems.GetByIdAsync(id.Value);
|
var item = await _unitOfWork.InventoryItems.GetByIdAsync(id.Value);
|
||||||
if (item == null) return NotFound();
|
if (item == null) return NotFound();
|
||||||
ViewBag.IsEmbed = embed;
|
|
||||||
return View(_mapper.Map<InventoryItemDto>(item));
|
return View(_mapper.Map<InventoryItemDto>(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2407,28 +2407,6 @@ public class JobsController : Controller
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a rework job reaches a terminal status, close out the linked ReworkRecord
|
|
||||||
// on the original job so the shop doesn't have to do it manually.
|
|
||||||
// Cancelled → WrittenOff; any other terminal → Resolved.
|
|
||||||
if (newStatus?.IsTerminalStatus == true && job.IsReworkJob)
|
|
||||||
{
|
|
||||||
var linkedRecords = await _unitOfWork.ReworkRecords.FindAsync(
|
|
||||||
r => r.ReworkJobId == job.Id && r.CompanyId == job.CompanyId, false);
|
|
||||||
foreach (var rr in linkedRecords)
|
|
||||||
{
|
|
||||||
if (rr.Status == ReworkStatus.Resolved || rr.Status == ReworkStatus.WrittenOff)
|
|
||||||
continue;
|
|
||||||
rr.Status = newStatus.StatusCode == AppConstants.StatusCodes.Job.Cancelled
|
|
||||||
? ReworkStatus.WrittenOff
|
|
||||||
: ReworkStatus.Resolved;
|
|
||||||
rr.ResolvedDate ??= DateTime.UtcNow;
|
|
||||||
rr.UpdatedAt = DateTime.UtcNow;
|
|
||||||
await _unitOfWork.ReworkRecords.UpdateAsync(rr);
|
|
||||||
}
|
|
||||||
if (linkedRecords.Any())
|
|
||||||
await _unitOfWork.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify customer on status change (only if user opted in)
|
// Notify customer on status change (only if user opted in)
|
||||||
if (request.SendEmail && newStatus != null)
|
if (request.SendEmail && newStatus != null)
|
||||||
{
|
{
|
||||||
@@ -3550,13 +3528,10 @@ public class JobsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Records a rework event against a job. Optionally creates a linked rework job so the
|
/// Records a rework event against a job item (e.g. defect found during QC).
|
||||||
/// repair can flow through the full shop lifecycle. When creating a rework job:
|
/// Automatically creates a new linked rework Job so the repair work can be tracked
|
||||||
/// - Job number uses sub-number format: {parentNumber}-R{n} (e.g. JOB-2605-0007-R1)
|
/// through the same job lifecycle. The rework job inherits the original job's customer,
|
||||||
/// - Only items selected by the user are copied (partial rework support)
|
/// oven, and items so the shop has a complete specification to work from.
|
||||||
/// - Pricing obeys the ReworkPricingType: ShopFault zeros all item prices;
|
|
||||||
/// CustomerReduced/CustomerFull copy prices as-is (user edits after if needed)
|
|
||||||
/// - Job starts at the first non-Pending status in the company's workflow
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> AddReworkRecord([FromBody] CreateReworkRecordDto dto)
|
public async Task<IActionResult> AddReworkRecord([FromBody] CreateReworkRecordDto dto)
|
||||||
@@ -3565,207 +3540,95 @@ public class JobsController : Controller
|
|||||||
if (job == null) return NotFound();
|
if (job == null) return NotFound();
|
||||||
|
|
||||||
var companyId = job.CompanyId;
|
var companyId = job.CompanyId;
|
||||||
Job? reworkJob = null;
|
|
||||||
|
|
||||||
if (dto.CreateReworkJob && dto.ReworkJobItemIds != null && dto.ReworkJobItemIds.Count > 0 && dto.ReworkPricingType.HasValue)
|
// Generate rework job number
|
||||||
|
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
||||||
|
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();
|
||||||
|
|
||||||
|
var allJobs = await _unitOfWork.Jobs.GetAllAsync(true);
|
||||||
|
var year = DateTime.Now.ToString("yy");
|
||||||
|
var month = DateTime.Now.ToString("MM");
|
||||||
|
var prefix = $"JOB-{year}{month}-";
|
||||||
|
var maxNum = allJobs
|
||||||
|
.Where(j => j.JobNumber.StartsWith(prefix))
|
||||||
|
.Select(j => { int.TryParse(j.JobNumber.Replace(prefix, ""), out int n); return n; })
|
||||||
|
.DefaultIfEmpty(0).Max();
|
||||||
|
|
||||||
|
var reworkJob = pendingStatus != null ? new Job
|
||||||
{
|
{
|
||||||
var typeLabel = dto.ReworkType switch
|
JobNumber = $"{prefix}{(maxNum + 1):D4}",
|
||||||
{
|
CustomerId = job.CustomerId,
|
||||||
ReworkType.InternalDefect => "Internal Defect",
|
Description = $"REWORK: {job.Description}",
|
||||||
ReworkType.CustomerWarranty => "Customer Warranty",
|
JobStatusId = pendingStatus.Id,
|
||||||
ReworkType.CustomerDamage => "Customer Damage",
|
JobPriorityId = normalPriority.Id,
|
||||||
_ => dto.ReworkType.ToString()
|
IsReworkJob = true,
|
||||||
};
|
OriginalJobId = job.Id,
|
||||||
var reasonLabel = dto.Reason switch
|
SpecialInstructions = $"Rework of {job.JobNumber}.",
|
||||||
{
|
CompanyId = companyId,
|
||||||
ReworkReason.AdhesionFailure => "Adhesion Failure",
|
CreatedAt = DateTime.UtcNow
|
||||||
ReworkReason.Contamination => "Contamination",
|
} : null;
|
||||||
ReworkReason.ColorMismatch => "Color Mismatch",
|
|
||||||
ReworkReason.RunsSags => "Runs / Sags",
|
|
||||||
ReworkReason.SurfacePrepFailure => "Surface Prep Failure",
|
|
||||||
ReworkReason.OvenIssue => "Oven Issue",
|
|
||||||
ReworkReason.InsufficientCoverage => "Insufficient Coverage",
|
|
||||||
ReworkReason.HandlingDamage => "Handling Damage",
|
|
||||||
_ => "Other"
|
|
||||||
};
|
|
||||||
var pricingLabel = dto.ReworkPricingType.Value switch
|
|
||||||
{
|
|
||||||
ReworkPricingType.ShopFault => "Shop Fault — no charge",
|
|
||||||
ReworkPricingType.CustomerReduced => "Customer responsible — reduced rate",
|
|
||||||
ReworkPricingType.CustomerFull => "Customer responsible — full price",
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
var defect = string.IsNullOrWhiteSpace(dto.DefectDescription) ? "" : $": {dto.DefectDescription}";
|
|
||||||
var reworkDescription = $"REWORK ({typeLabel} / {reasonLabel}){defect}. Pricing: {pricingLabel}.";
|
|
||||||
|
|
||||||
var currentUserId = _userManager.GetUserId(User);
|
if (reworkJob != null)
|
||||||
reworkJob = await BuildReworkJobAsync(job, dto.ReworkJobItemIds, dto.ReworkPricingType.Value, companyId, reworkDescription, currentUserId);
|
{
|
||||||
|
await _unitOfWork.Jobs.AddAsync(reworkJob);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
// Copy items: specific item if flagged, otherwise all items
|
||||||
|
var itemsToCopy = dto.JobItemId.HasValue
|
||||||
|
? job.JobItems.Where(i => i.Id == dto.JobItemId.Value).ToList()
|
||||||
|
: job.JobItems.ToList();
|
||||||
|
|
||||||
|
foreach (var item in itemsToCopy)
|
||||||
|
{
|
||||||
|
var createdAtUtc = DateTime.UtcNow;
|
||||||
|
var newItem = _jobItemAssemblyService.CreateJobItem(item, reworkJob.Id, companyId, createdAtUtc);
|
||||||
|
|
||||||
|
await _unitOfWork.JobItems.AddAsync(newItem);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
foreach (var coat in _jobItemAssemblyService.CreateJobItemCoats(item, newItem.Id, companyId, createdAtUtc))
|
||||||
|
{
|
||||||
|
await _unitOfWork.JobItemCoats.AddAsync(coat);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var prepService in _jobItemAssemblyService.CreateJobItemPrepServices(item, newItem.Id, companyId, createdAtUtc))
|
||||||
|
{
|
||||||
|
await _unitOfWork.JobItemPrepServices.AddAsync(prepService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var record = new ReworkRecord
|
var record = new ReworkRecord
|
||||||
{
|
{
|
||||||
JobId = dto.JobId,
|
JobId = dto.JobId,
|
||||||
JobItemId = dto.JobItemId,
|
JobItemId = dto.JobItemId,
|
||||||
ReworkType = dto.ReworkType,
|
ReworkType = dto.ReworkType,
|
||||||
Reason = dto.Reason,
|
Reason = dto.Reason,
|
||||||
DefectDescription = dto.DefectDescription,
|
DefectDescription = dto.DefectDescription,
|
||||||
DiscoveredBy = dto.DiscoveredBy,
|
DiscoveredBy = dto.DiscoveredBy,
|
||||||
DiscoveredDate = dto.DiscoveredDate,
|
DiscoveredDate = dto.DiscoveredDate,
|
||||||
ReportedByName = dto.ReportedByName,
|
ReportedByName = dto.ReportedByName,
|
||||||
EstimatedReworkCost = dto.EstimatedReworkCost,
|
EstimatedReworkCost = dto.EstimatedReworkCost,
|
||||||
IsBillableToCustomer = dto.IsBillableToCustomer,
|
IsBillableToCustomer = dto.IsBillableToCustomer,
|
||||||
BillingNotes = dto.BillingNotes,
|
BillingNotes = dto.BillingNotes,
|
||||||
ReworkPricingType = dto.ReworkPricingType,
|
ReworkJobId = reworkJob?.Id,
|
||||||
ReworkJobId = reworkJob?.Id,
|
Status = reworkJob != null ? ReworkStatus.InProgress : ReworkStatus.Open,
|
||||||
Status = reworkJob != null ? ReworkStatus.InProgress : ReworkStatus.Open,
|
CompanyId = companyId,
|
||||||
CompanyId = companyId,
|
CreatedAt = DateTime.UtcNow
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await _unitOfWork.ReworkRecords.AddAsync(record);
|
await _unitOfWork.ReworkRecords.AddAsync(record);
|
||||||
await _unitOfWork.CompleteAsync();
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
// Reload with navigation for response
|
||||||
var saved = await _unitOfWork.ReworkRecords.FindAsync(r => r.Id == record.Id, false, r => r.JobItem, r => r.ReworkJob);
|
var saved = await _unitOfWork.ReworkRecords.FindAsync(r => r.Id == record.Id, false, r => r.JobItem, r => r.ReworkJob);
|
||||||
return Json(_mapper.Map<ReworkRecordDto>(saved.First()));
|
return Json(_mapper.Map<ReworkRecordDto>(saved.First()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a linked rework Job from an existing rework record that was saved without one.
|
|
||||||
/// Uses sub-number format and applies the specified pricing attribution.
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost]
|
|
||||||
public async Task<IActionResult> CreateReworkJob([FromBody] CreateReworkJobRequest req)
|
|
||||||
{
|
|
||||||
var reworkRecord = await _unitOfWork.ReworkRecords.GetByIdAsync(req.ReworkRecordId, false, r => r.Job);
|
|
||||||
if (reworkRecord == null) return NotFound();
|
|
||||||
|
|
||||||
var originalJob = await _unitOfWork.Jobs.LoadForDetailsAsync(reworkRecord.JobId);
|
|
||||||
if (originalJob == null) return NotFound();
|
|
||||||
|
|
||||||
var companyId = originalJob.CompanyId;
|
|
||||||
var itemIds = req.ItemIds ?? originalJob.JobItems.Select(i => i.Id).ToList();
|
|
||||||
var pricingType = req.ReworkPricingType ?? ReworkPricingType.ShopFault;
|
|
||||||
|
|
||||||
var pricingLabel = pricingType switch
|
|
||||||
{
|
|
||||||
ReworkPricingType.ShopFault => "Shop Fault — no charge",
|
|
||||||
ReworkPricingType.CustomerReduced => "Customer responsible — reduced rate",
|
|
||||||
ReworkPricingType.CustomerFull => "Customer responsible — full price",
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
var notes = string.IsNullOrWhiteSpace(req.Notes) ? "" : $" Notes: {req.Notes}";
|
|
||||||
var reworkDescription = $"REWORK: {pricingLabel}.{notes}";
|
|
||||||
var currentUserId = _userManager.GetUserId(User);
|
|
||||||
var reworkJob = await BuildReworkJobAsync(originalJob, itemIds, pricingType, companyId, reworkDescription, currentUserId);
|
|
||||||
|
|
||||||
reworkRecord.ReworkJobId = reworkJob.Id;
|
|
||||||
reworkRecord.ReworkPricingType = pricingType;
|
|
||||||
reworkRecord.Status = ReworkStatus.InProgress;
|
|
||||||
reworkRecord.UpdatedAt = DateTime.UtcNow;
|
|
||||||
await _unitOfWork.ReworkRecords.UpdateAsync(reworkRecord);
|
|
||||||
await _unitOfWork.CompleteAsync();
|
|
||||||
|
|
||||||
return Json(new { success = true, reworkJobId = reworkJob.Id, reworkJobNumber = reworkJob.JobNumber });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shared helper that creates and persists a rework Job with sub-numbered job number,
|
|
||||||
/// copies the specified items (with coats and prep services), applies pricing attribution,
|
|
||||||
/// sets descriptive job description from the rework record data, and auto-records intake
|
|
||||||
/// (parts are already on hand when rework is logged).
|
|
||||||
/// Called by both AddReworkRecord and CreateReworkJob.
|
|
||||||
/// </summary>
|
|
||||||
private async Task<Job> BuildReworkJobAsync(
|
|
||||||
Job originalJob,
|
|
||||||
List<int> itemIds,
|
|
||||||
ReworkPricingType pricingType,
|
|
||||||
int companyId,
|
|
||||||
string reworkDescription,
|
|
||||||
string? checkedByUserId)
|
|
||||||
{
|
|
||||||
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
|
||||||
var priorities = await _lookupCache.GetJobPriorityLookupsAsync(companyId);
|
|
||||||
|
|
||||||
// First non-Pending status by workflow order
|
|
||||||
var firstActiveStatus = statuses
|
|
||||||
.Where(s => s.StatusCode != AppConstants.StatusCodes.Job.Pending)
|
|
||||||
.OrderBy(s => s.DisplayOrder)
|
|
||||||
.First();
|
|
||||||
|
|
||||||
var normalPriority = priorities.FirstOrDefault(p => p.PriorityCode == "NORMAL") ?? priorities.First();
|
|
||||||
|
|
||||||
// Sub-number: {parentJobNumber}-R{n+1}
|
|
||||||
var reworkCount = await _unitOfWork.Jobs.GetReworkJobCountAsync(originalJob.Id);
|
|
||||||
var reworkNumber = $"{originalJob.JobNumber}-R{reworkCount + 1}";
|
|
||||||
|
|
||||||
var reworkJob = new Job
|
|
||||||
{
|
|
||||||
JobNumber = reworkNumber,
|
|
||||||
CustomerId = originalJob.CustomerId,
|
|
||||||
Description = reworkDescription,
|
|
||||||
JobStatusId = firstActiveStatus.Id,
|
|
||||||
JobPriorityId = normalPriority.Id,
|
|
||||||
IsReworkJob = true,
|
|
||||||
OriginalJobId = originalJob.Id,
|
|
||||||
SpecialInstructions = $"Rework of {originalJob.JobNumber}.",
|
|
||||||
// Auto-intake: parts are already on hand when rework is logged
|
|
||||||
IntakeDate = DateTime.UtcNow,
|
|
||||||
IntakeConditionNotes = $"Parts auto-checked in as rework from {originalJob.JobNumber}.",
|
|
||||||
IntakeCheckedByUserId = checkedByUserId,
|
|
||||||
CompanyId = companyId,
|
|
||||||
CreatedAt = DateTime.UtcNow
|
|
||||||
};
|
|
||||||
|
|
||||||
await _unitOfWork.Jobs.AddAsync(reworkJob);
|
|
||||||
await _unitOfWork.CompleteAsync();
|
|
||||||
|
|
||||||
var itemsToCopy = originalJob.JobItems.Where(i => itemIds.Contains(i.Id)).ToList();
|
|
||||||
var createdAtUtc = DateTime.UtcNow;
|
|
||||||
|
|
||||||
foreach (var item in itemsToCopy)
|
|
||||||
{
|
|
||||||
var newItem = _jobItemAssemblyService.CreateJobItem(item, reworkJob.Id, companyId, createdAtUtc);
|
|
||||||
|
|
||||||
// Shop-fault rework jobs are done at no charge
|
|
||||||
if (pricingType == ReworkPricingType.ShopFault)
|
|
||||||
{
|
|
||||||
newItem.UnitPrice = 0;
|
|
||||||
newItem.ManualUnitPrice = 0;
|
|
||||||
newItem.TotalPrice = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _unitOfWork.JobItems.AddAsync(newItem);
|
|
||||||
await _unitOfWork.CompleteAsync();
|
|
||||||
|
|
||||||
foreach (var coat in _jobItemAssemblyService.CreateJobItemCoats(item, newItem.Id, companyId, createdAtUtc))
|
|
||||||
await _unitOfWork.JobItemCoats.AddAsync(coat);
|
|
||||||
|
|
||||||
foreach (var prep in _jobItemAssemblyService.CreateJobItemPrepServices(item, newItem.Id, companyId, createdAtUtc))
|
|
||||||
await _unitOfWork.JobItemPrepServices.AddAsync(prep);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set intake part count now that items are known
|
|
||||||
reworkJob.IntakePartCount = (int)Math.Ceiling(itemsToCopy.Sum(i => i.Quantity));
|
|
||||||
|
|
||||||
// Write a pricing snapshot so the Details page and inline edit both work correctly
|
|
||||||
var itemsSubtotal = pricingType == ReworkPricingType.ShopFault
|
|
||||||
? 0m
|
|
||||||
: itemsToCopy.Sum(i => i.TotalPrice);
|
|
||||||
reworkJob.FinalPrice = itemsSubtotal;
|
|
||||||
reworkJob.PricingBreakdownJson = System.Text.Json.JsonSerializer.Serialize(new QuotePricingBreakdownDto
|
|
||||||
{
|
|
||||||
ItemsSubtotal = itemsSubtotal,
|
|
||||||
SubtotalBeforeDiscount = itemsSubtotal,
|
|
||||||
SubtotalAfterDiscount = itemsSubtotal,
|
|
||||||
Total = itemsSubtotal
|
|
||||||
});
|
|
||||||
|
|
||||||
await _unitOfWork.Jobs.UpdateAsync(reworkJob);
|
|
||||||
await _unitOfWork.CompleteAsync();
|
|
||||||
|
|
||||||
return reworkJob;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a rework record's status, resolution notes, cost, and billability.
|
/// Updates a rework record's status, resolution notes, cost, and billability.
|
||||||
/// Auto-sets ResolvedDate when status transitions to Resolved or WrittenOff (if not already set).
|
/// Auto-sets ResolvedDate when status transitions to Resolved or WrittenOff (if not already set).
|
||||||
@@ -3817,6 +3680,66 @@ public class JobsController : Controller
|
|||||||
return Json(new { success = true });
|
return Json(new { success = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new rework Job from an existing rework record and links them.
|
||||||
|
/// The rework job is a lightweight clone of the original job — same customer, description, and
|
||||||
|
/// oven — but starts fresh with Pending status so it goes through the full workflow again.
|
||||||
|
/// The ReworkJob FK on the rework record is updated so the Detail view can link to it.
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> CreateReworkJob([FromBody] CreateReworkJobRequest req)
|
||||||
|
{
|
||||||
|
var reworkRecord = await _unitOfWork.ReworkRecords.GetByIdAsync(req.ReworkRecordId, false, r => r.Job);
|
||||||
|
if (reworkRecord == null) return NotFound();
|
||||||
|
|
||||||
|
var originalJob = reworkRecord.Job;
|
||||||
|
var companyId = originalJob.CompanyId;
|
||||||
|
|
||||||
|
// Load status lookups to find Pending status
|
||||||
|
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
||||||
|
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);
|
||||||
|
var normalPriority = priorities.FirstOrDefault(p => p.PriorityCode == "NORMAL") ?? priorities.First();
|
||||||
|
|
||||||
|
// Generate job number
|
||||||
|
var allJobs = await _unitOfWork.Jobs.GetAllAsync(true);
|
||||||
|
var year = DateTime.Now.ToString("yy");
|
||||||
|
var month = DateTime.Now.ToString("MM");
|
||||||
|
var prefix = $"JOB-{year}{month}-";
|
||||||
|
var maxNum = allJobs
|
||||||
|
.Where(j => j.JobNumber.StartsWith(prefix))
|
||||||
|
.Select(j => { int.TryParse(j.JobNumber.Replace(prefix, ""), out int n); return n; })
|
||||||
|
.DefaultIfEmpty(0).Max();
|
||||||
|
|
||||||
|
var reworkJob = new Job
|
||||||
|
{
|
||||||
|
JobNumber = $"{prefix}{(maxNum + 1):D4}",
|
||||||
|
CustomerId = originalJob.CustomerId,
|
||||||
|
Description = $"REWORK: {originalJob.Description}",
|
||||||
|
JobStatusId = pendingStatus.Id,
|
||||||
|
JobPriorityId = normalPriority.Id,
|
||||||
|
IsReworkJob = true,
|
||||||
|
OriginalJobId = originalJob.Id,
|
||||||
|
SpecialInstructions = $"Rework of {originalJob.JobNumber}. {req.Notes}".Trim().TrimEnd('.') + ".",
|
||||||
|
CompanyId = companyId,
|
||||||
|
CreatedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
await _unitOfWork.Jobs.AddAsync(reworkJob);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
// Link rework record to new job
|
||||||
|
reworkRecord.ReworkJobId = reworkJob.Id;
|
||||||
|
reworkRecord.Status = ReworkStatus.InProgress;
|
||||||
|
reworkRecord.UpdatedAt = DateTime.UtcNow;
|
||||||
|
await _unitOfWork.ReworkRecords.UpdateAsync(reworkRecord);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
return Json(new { success = true, reworkJobId = reworkJob.Id, reworkJobNumber = reworkJob.JobNumber });
|
||||||
|
}
|
||||||
|
|
||||||
// ── Quote-Changed Banner Actions ──────────────────────────────────────────
|
// ── Quote-Changed Banner Actions ──────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -4388,13 +4311,7 @@ public class LogMaterialRequest
|
|||||||
public string TransactionType { get; set; } = "JobUsage";
|
public string TransactionType { get; set; } = "JobUsage";
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
}
|
}
|
||||||
public class CreateReworkJobRequest
|
public class CreateReworkJobRequest { public int ReworkRecordId { get; set; } public string? Notes { get; set; } }
|
||||||
{
|
|
||||||
public int ReworkRecordId { get; set; }
|
|
||||||
public List<int>? ItemIds { get; set; }
|
|
||||||
public PowderCoating.Core.Enums.ReworkPricingType? ReworkPricingType { get; set; }
|
|
||||||
public string? Notes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UpdateWorkerAssignmentRequest
|
public class UpdateWorkerAssignmentRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="fw-bold">@c.ClosedYear</td>
|
<td class="fw-bold">@c.ClosedYear</td>
|
||||||
<td>@c.ClosedAt.ToLocalTime().ToString("MM/dd/yyyy h:mm tt")</td>
|
<td>@c.ClosedAt.ToLocalTime().ToString("MM/dd/yyyy h:mm tt")</td>
|
||||||
<td>@Html.Raw(c.ClosedBy ?? "—")</td>
|
<td>@(c.ClosedBy ?? "—")</td>
|
||||||
<td>
|
<td>
|
||||||
@if (c.JournalEntry != null)
|
@if (c.JournalEntry != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -207,16 +207,16 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center @(row.Today > 0 ? "fw-semibold" : "text-muted")">
|
<td class="text-center @(row.Today > 0 ? "fw-semibold" : "text-muted")">
|
||||||
@Html.Raw(row.Today > 0 ? row.Today.ToString("N0") : "—")
|
@(row.Today > 0 ? row.Today.ToString("N0") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center @(row.Last7Days > 0 ? "fw-semibold" : "text-muted")">
|
<td class="text-center @(row.Last7Days > 0 ? "fw-semibold" : "text-muted")">
|
||||||
@Html.Raw(row.Last7Days > 0 ? row.Last7Days.ToString("N0") : "—")
|
@(row.Last7Days > 0 ? row.Last7Days.ToString("N0") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center @(row.Last30Days > 0 ? "fw-semibold" : "text-muted")">
|
<td class="text-center @(row.Last30Days > 0 ? "fw-semibold" : "text-muted")">
|
||||||
@Html.Raw(row.Last30Days > 0 ? row.Last30Days.ToString("N0") : "—")
|
@(row.Last30Days > 0 ? row.Last30Days.ToString("N0") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center @(row.AllTime > 0 ? "" : "text-muted")">
|
<td class="text-center @(row.AllTime > 0 ? "" : "text-muted")">
|
||||||
@Html.Raw(row.AllTime > 0 ? row.AllTime.ToString("N0") : "—")
|
@(row.AllTime > 0 ? row.AllTime.ToString("N0") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center @(row.PhotoCount > 0 ? "" : "text-muted")">
|
<td class="text-center @(row.PhotoCount > 0 ? "" : "text-muted")">
|
||||||
@if (row.PhotoCount > 0)
|
@if (row.PhotoCount > 0)
|
||||||
|
|||||||
@@ -46,19 +46,19 @@
|
|||||||
<dd class="col-7">@Model.EntityType</dd>
|
<dd class="col-7">@Model.EntityType</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">Entity ID</dt>
|
<dt class="col-5 text-muted">Entity ID</dt>
|
||||||
<dd class="col-7">@Html.Raw(Model.EntityId ?? "—")</dd>
|
<dd class="col-7">@(Model.EntityId ?? "—")</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">Description</dt>
|
<dt class="col-5 text-muted">Description</dt>
|
||||||
<dd class="col-7">@Html.Raw(Model.EntityDescription ?? "—")</dd>
|
<dd class="col-7">@(Model.EntityDescription ?? "—")</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">User</dt>
|
<dt class="col-5 text-muted">User</dt>
|
||||||
<dd class="col-7">@Model.UserName</dd>
|
<dd class="col-7">@Model.UserName</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">Company</dt>
|
<dt class="col-5 text-muted">Company</dt>
|
||||||
<dd class="col-7">@Html.Raw(Model.CompanyName ?? (Model.CompanyId?.ToString() ?? "—"))</dd>
|
<dd class="col-7">@(Model.CompanyName ?? (Model.CompanyId?.ToString() ?? "—"))</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">IP Address</dt>
|
<dt class="col-5 text-muted">IP Address</dt>
|
||||||
<dd class="col-7">@Html.Raw(Model.IpAddress ?? "—")</dd>
|
<dd class="col-7">@(Model.IpAddress ?? "—")</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,8 +95,8 @@
|
|||||||
var newVal = newData.ValueKind == JsonValueKind.Object && newData.TryGetProperty(key, out var nv) ? nv.ToString() : null;
|
var newVal = newData.ValueKind == JsonValueKind.Object && newData.TryGetProperty(key, out var nv) ? nv.ToString() : null;
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fw-medium">@key</td>
|
<td class="fw-medium">@key</td>
|
||||||
<td class="text-danger font-monospace">@Html.Raw(oldVal ?? "—")</td>
|
<td class="text-danger font-monospace">@(oldVal ?? "—")</td>
|
||||||
<td class="text-success font-monospace">@Html.Raw(newVal ?? "—")</td>
|
<td class="text-success font-monospace">@(newVal ?? "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@
|
|||||||
{
|
{
|
||||||
<tr class="text-muted">
|
<tr class="text-muted">
|
||||||
<td><code>@ban.IpAddress</code></td>
|
<td><code>@ban.IpAddress</code></td>
|
||||||
<td><small>@Html.Raw(ban.Reason ?? "—")</small></td>
|
<td><small>@(ban.Reason ?? "—")</small></td>
|
||||||
<td><small>@ban.BannedAt.ToString("MMM dd, yyyy")</small></td>
|
<td><small>@ban.BannedAt.ToString("MMM dd, yyyy")</small></td>
|
||||||
<td>
|
<td>
|
||||||
@if (!ban.IsActive)
|
@if (!ban.IsActive)
|
||||||
|
|||||||
@@ -162,7 +162,7 @@
|
|||||||
<td><span class="badge bg-@entry.StatusColor">@entry.StatusLabel</span></td>
|
<td><span class="badge bg-@entry.StatusColor">@entry.StatusLabel</span></td>
|
||||||
<td class="text-end">@entry.Total.ToString("C")</td>
|
<td class="text-end">@entry.Total.ToString("C")</td>
|
||||||
<td class="text-end fw-medium @(entry.BalanceDue > 0 ? "text-danger" : "text-muted")">
|
<td class="text-end fw-medium @(entry.BalanceDue > 0 ? "text-danger" : "text-muted")">
|
||||||
@Html.Raw(entry.EntryType == "Bill" ? entry.BalanceDue.ToString("C") : "—")
|
@(entry.EntryType == "Bill" ? entry.BalanceDue.ToString("C") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (entry.EntryType == "Bill")
|
@if (entry.EntryType == "Bill")
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-sm table-borderless mb-0">
|
<table class="table table-sm table-borderless mb-0">
|
||||||
<tr><th style="width:40%">Company Name</th><td>@Model.CompanyName</td></tr>
|
<tr><th style="width:40%">Company Name</th><td>@Model.CompanyName</td></tr>
|
||||||
<tr><th>Code</th><td>@Html.Raw(Model.CompanyCode ?? "—")</td></tr>
|
<tr><th>Code</th><td>@(Model.CompanyCode ?? "—")</td></tr>
|
||||||
<tr><th>Status</th><td><span class="badge @(Model.IsActive ? "bg-success" : "bg-danger")">@(Model.IsActive ? "Active" : "Inactive")</span></td></tr>
|
<tr><th>Status</th><td><span class="badge @(Model.IsActive ? "bg-success" : "bg-danger")">@(Model.IsActive ? "Active" : "Inactive")</span></td></tr>
|
||||||
<tr><th>Time Zone</th><td>@(Model.TimeZone ?? "America/New_York")</td></tr>
|
<tr><th>Time Zone</th><td>@(Model.TimeZone ?? "America/New_York")</td></tr>
|
||||||
<tr><th>Created</th><td>@Model.CreatedAt.ToString("MMM d, yyyy h:mm tt")</td></tr>
|
<tr><th>Created</th><td>@Model.CreatedAt.ToString("MMM d, yyyy h:mm tt")</td></tr>
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
<table class="table table-sm table-borderless mb-0">
|
<table class="table table-sm table-borderless mb-0">
|
||||||
<tr><th style="width:40%">Contact Name</th><td>@Model.PrimaryContactName</td></tr>
|
<tr><th style="width:40%">Contact Name</th><td>@Model.PrimaryContactName</td></tr>
|
||||||
<tr><th>Email</th><td><a href="mailto:@Model.PrimaryContactEmail">@Model.PrimaryContactEmail</a></td></tr>
|
<tr><th>Email</th><td><a href="mailto:@Model.PrimaryContactEmail">@Model.PrimaryContactEmail</a></td></tr>
|
||||||
<tr><th>Phone</th><td>@Html.Raw(Model.Phone ?? "—")</td></tr>
|
<tr><th>Phone</th><td>@(Model.Phone ?? "—")</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,7 +283,7 @@
|
|||||||
}
|
}
|
||||||
else { <span class="text-muted">N/A</span> }
|
else { <span class="text-muted">N/A</span> }
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(user.Department ?? "—")</td>
|
<td>@(user.Department ?? "—")</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge @(user.IsActive ? "bg-success" : "bg-danger")">
|
<span class="badge @(user.IsActive ? "bg-success" : "bg-danger")">
|
||||||
@(user.IsActive ? "Active" : "Inactive")
|
@(user.IsActive ? "Active" : "Inactive")
|
||||||
@@ -527,20 +527,20 @@
|
|||||||
@{
|
@{
|
||||||
var firstActivity = onboarding.FirstJobCreatedAt ?? onboarding.FirstQuoteCreatedAt;
|
var firstActivity = onboarding.FirstJobCreatedAt ?? onboarding.FirstQuoteCreatedAt;
|
||||||
}
|
}
|
||||||
@Html.Raw(firstActivity.HasValue ? firstActivity.Value.ToString("MMM d, yyyy") : "—")
|
@(firstActivity.HasValue ? firstActivity.Value.ToString("MMM d, yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>First Invoice</th>
|
<th>First Invoice</th>
|
||||||
<td>@Html.Raw(onboarding.FirstInvoiceCreatedAt.HasValue ? onboarding.FirstInvoiceCreatedAt.Value.ToString("MMM d, yyyy") : "—")</td>
|
<td>@(onboarding.FirstInvoiceCreatedAt.HasValue ? onboarding.FirstInvoiceCreatedAt.Value.ToString("MMM d, yyyy") : "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Workflow Completed</th>
|
<th>Workflow Completed</th>
|
||||||
<td>@Html.Raw(onboarding.FirstWorkflowCompletedAt.HasValue ? onboarding.FirstWorkflowCompletedAt.Value.ToString("MMM d, yyyy") : "—")</td>
|
<td>@(onboarding.FirstWorkflowCompletedAt.HasValue ? onboarding.FirstWorkflowCompletedAt.Value.ToString("MMM d, yyyy") : "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Widget Dismissed</th>
|
<th>Widget Dismissed</th>
|
||||||
<td>@Html.Raw(onboarding.GuidedActivationDismissedAt.HasValue ? onboarding.GuidedActivationDismissedAt.Value.ToString("MMM d, yyyy") : "—")</td>
|
<td>@(onboarding.GuidedActivationDismissedAt.HasValue ? onboarding.GuidedActivationDismissedAt.Value.ToString("MMM d, yyyy") : "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -181,7 +181,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>@a.AppliedDate.ToLocalTime().ToString("MM/dd/yyyy")</td>
|
<td>@a.AppliedDate.ToLocalTime().ToString("MM/dd/yyyy")</td>
|
||||||
<td class="text-end fw-semibold text-success">@a.AmountApplied.ToString("C")</td>
|
<td class="text-end fw-semibold text-success">@a.AmountApplied.ToString("C")</td>
|
||||||
<td class="small text-muted">@Html.Raw(a.AppliedBy?.FullName ?? "—")</td>
|
<td class="small text-muted">@(a.AppliedBy?.FullName ?? "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ else
|
|||||||
</td>
|
</td>
|
||||||
<td>@m.IssueDate.ToLocalTime().ToString("MM/dd/yyyy")</td>
|
<td>@m.IssueDate.ToLocalTime().ToString("MM/dd/yyyy")</td>
|
||||||
<td class="@(expired ? "text-danger fw-semibold" : "")">
|
<td class="@(expired ? "text-danger fw-semibold" : "")">
|
||||||
@Html.Raw(m.ExpiryDate.HasValue ? m.ExpiryDate.Value.ToLocalTime().ToString("MM/dd/yyyy") : "—")
|
@(m.ExpiryDate.HasValue ? m.ExpiryDate.Value.ToLocalTime().ToString("MM/dd/yyyy") : "—")
|
||||||
@if (expired) { <small>(Expired)</small> }
|
@if (expired) { <small>(Expired)</small> }
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -224,7 +224,7 @@
|
|||||||
}
|
}
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Phone</span>
|
<span class="mobile-card-label">Phone</span>
|
||||||
<span class="mobile-card-value">@Html.Raw(customer.Phone ?? "—")</span>
|
<span class="mobile-card-value">@(customer.Phone ?? "—")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Type</span>
|
<span class="mobile-card-label">Type</span>
|
||||||
|
|||||||
@@ -563,7 +563,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">Vendor Total</td>
|
<td colspan="2">Vendor Total</td>
|
||||||
<td class="text-end">@vendorGroup.TotalLbsNeeded.ToString("N2") lbs</td>
|
<td class="text-end">@vendorGroup.TotalLbsNeeded.ToString("N2") lbs</td>
|
||||||
<td class="text-end">@Html.Raw(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
<td class="text-end">@(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
@@ -680,7 +680,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">Vendor Total</td>
|
<td colspan="2">Vendor Total</td>
|
||||||
<td class="text-end">@vendorGroup.TotalLbsNeeded.ToString("N2") lbs</td>
|
<td class="text-end">@vendorGroup.TotalLbsNeeded.ToString("N2") lbs</td>
|
||||||
<td class="text-end">@Html.Raw(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
<td class="text-end">@(vendorGroup.TotalEstCost > 0 ? vendorGroup.TotalEstCost.ToString("C") : "—")</td>
|
||||||
<td colspan="2"></td>
|
<td colspan="2"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
else { <span class="text-muted">—</span> }
|
else { <span class="text-muted">—</span> }
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted">
|
<td class="text-muted">
|
||||||
@Html.Raw(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "—")
|
@(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="checkbox" class="form-check-input entity-select"
|
<input type="checkbox" class="form-check-input entity-select"
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-title">
|
<div class="mobile-card-title">
|
||||||
<h6>@s.Label</h6>
|
<h6>@s.Label</h6>
|
||||||
<small>Oldest: @Html.Raw(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "—")</small>
|
<small>Oldest: @(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "—")</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-body">
|
<div class="mobile-card-body">
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<div class="small text-muted">@(string.IsNullOrWhiteSpace(row.RecipientEmail) ? "No primary contact email configured" : row.RecipientEmail)</div>
|
<div class="small text-muted">@(string.IsNullOrWhiteSpace(row.RecipientEmail) ? "No primary contact email configured" : row.RecipientEmail)</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>@Html.Raw(string.IsNullOrWhiteSpace(row.CompanyAdminName) ? "—" : row.CompanyAdminName)</div>
|
<div>@(string.IsNullOrWhiteSpace(row.CompanyAdminName) ? "—" : row.CompanyAdminName)</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(row.CompanyAdminEmail))
|
@if (!string.IsNullOrWhiteSpace(row.CompanyAdminEmail))
|
||||||
{
|
{
|
||||||
<div class="small text-muted">@row.CompanyAdminEmail</div>
|
<div class="small text-muted">@row.CompanyAdminEmail</div>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<div class="fw-semibold">@company.CompanyName</div>
|
<div class="fw-semibold">@company.CompanyName</div>
|
||||||
<div class="small text-muted">#@company.CompanyId</div>
|
<div class="small text-muted">#@company.CompanyId</div>
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(string.IsNullOrWhiteSpace(company.PrimaryContactName) ? "—" : company.PrimaryContactName)</td>
|
<td>@(string.IsNullOrWhiteSpace(company.PrimaryContactName) ? "—" : company.PrimaryContactName)</td>
|
||||||
<td>
|
<td>
|
||||||
@if (string.IsNullOrWhiteSpace(company.PrimaryContactEmail))
|
@if (string.IsNullOrWhiteSpace(company.PrimaryContactEmail))
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>@Html.Raw(string.IsNullOrWhiteSpace(company.CompanyAdminName) ? "—" : company.CompanyAdminName)</div>
|
<div>@(string.IsNullOrWhiteSpace(company.CompanyAdminName) ? "—" : company.CompanyAdminName)</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(company.CompanyAdminEmail))
|
@if (!string.IsNullOrWhiteSpace(company.CompanyAdminEmail))
|
||||||
{
|
{
|
||||||
<div class="small text-muted">@company.CompanyAdminEmail</div>
|
<div class="small text-muted">@company.CompanyAdminEmail</div>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="text-muted small mb-1">Location</label>
|
<label class="text-muted small mb-1">Location</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.Location ?? "—")</p>
|
<p class="mb-0">@(Model.Location ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="text-muted small mb-1">Status</label>
|
<label class="text-muted small mb-1">Status</label>
|
||||||
@@ -76,15 +76,15 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Manufacturer</label>
|
<label class="text-muted small mb-1">Manufacturer</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.Manufacturer ?? "—")</p>
|
<p class="mb-0">@(Model.Manufacturer ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Model</label>
|
<label class="text-muted small mb-1">Model</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.Model ?? "—")</p>
|
<p class="mb-0">@(Model.Model ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Serial Number</label>
|
<label class="text-muted small mb-1">Serial Number</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.SerialNumber ?? "—")</p>
|
<p class="mb-0">@(Model.SerialNumber ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,15 +94,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Manufacturer</label>
|
<label class="text-muted small mb-1">Manufacturer</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.Manufacturer ?? "—")</p>
|
<p class="mb-0">@(Model.Manufacturer ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Model</label>
|
<label class="text-muted small mb-1">Model</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.Model ?? "—")</p>
|
<p class="mb-0">@(Model.Model ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Serial Number</label>
|
<label class="text-muted small mb-1">Serial Number</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.SerialNumber ?? "—")</p>
|
<p class="mb-0">@(Model.SerialNumber ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
<td>@cert.OriginalAmount.ToString("C")</td>
|
<td>@cert.OriginalAmount.ToString("C")</td>
|
||||||
<td>@cert.IssueDate.ToLocalTime().ToString("MMM d, yyyy")</td>
|
<td>@cert.IssueDate.ToLocalTime().ToString("MMM d, yyyy")</td>
|
||||||
<td>
|
<td>
|
||||||
@Html.Raw(cert.ExpiryDate.HasValue
|
@(cert.ExpiryDate.HasValue
|
||||||
? cert.ExpiryDate.Value.ToLocalTime().ToString("MMM d, yyyy")
|
? cert.ExpiryDate.Value.ToLocalTime().ToString("MMM d, yyyy")
|
||||||
: "—")
|
: "—")
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -475,41 +475,12 @@
|
|||||||
actual hours vs. estimated hours for costing and productivity analysis.
|
actual hours vs. estimated hours for costing and productivity analysis.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="h6 fw-semibold mt-3 mb-2">Rework (also called Redo)</h3>
|
<h3 class="h6 fw-semibold mt-3 mb-2">Rework</h3>
|
||||||
<p>
|
<p>
|
||||||
If a finished part fails quality inspection or a customer returns it damaged, open the original
|
If finished parts fail quality inspection or need to be re-coated, create a rework record
|
||||||
job’s Details page and use the <strong>Rework Log</strong> section to record it. Rework
|
from the Job Details page. Rework records track the rework type, the reason (adhesion failure,
|
||||||
and redo mean the same thing throughout the system.
|
color mismatch, damage, etc.), and the resolution. This data helps identify recurring quality
|
||||||
</p>
|
issues over time.
|
||||||
<p>Each entry captures the type (internal defect, customer damage, warranty), the reason (adhesion
|
|
||||||
failure, color mismatch, runs/sags, insufficient coverage, etc.), a defect description, who
|
|
||||||
discovered the issue, and pricing responsibility.</p>
|
|
||||||
|
|
||||||
<h4 class="h6 fw-semibold mt-3 mb-1">Pricing Responsibility</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Shop Fault — no charge:</strong> All copied item prices are set to $0.</li>
|
|
||||||
<li><strong>Customer responsible — reduced rate:</strong> Prices are copied from the original job; edit them down after creation.</li>
|
|
||||||
<li><strong>Customer responsible — full price:</strong> Prices are copied as-is.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4 class="h6 fw-semibold mt-3 mb-1">Creating a Rework Job</h4>
|
|
||||||
<p>
|
|
||||||
Toggle <strong>Parts are back — create a Rework Job</strong> at the top of the log form.
|
|
||||||
Select the items that need to be redone and choose the pricing responsibility. The system will:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Create a new job with a sub-number (e.g., <code>JOB-2605-0001-R1</code>)</li>
|
|
||||||
<li>Copy the selected items with their coats and prep services</li>
|
|
||||||
<li>Auto-record intake — parts are already on hand when rework is logged</li>
|
|
||||||
<li>Set the job description to the defect type, reason, and pricing so it is visible at the top of the job</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4 class="h6 fw-semibold mt-3 mb-1">Automatic Resolution</h4>
|
|
||||||
<p>
|
|
||||||
When the rework job reaches a terminal status (Completed, Delivered, etc.), the linked rework
|
|
||||||
record on the original job is automatically marked <strong>Resolved</strong> — no manual
|
|
||||||
follow-up needed. If the rework job is <strong>Cancelled</strong>, the record is marked
|
|
||||||
<strong>Written Off</strong> instead.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -298,14 +298,14 @@
|
|||||||
{
|
{
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="text-muted small mb-1">Inventory Account</label>
|
<label class="text-muted small mb-1">Inventory Account</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.InventoryAccountName ?? "—")</p>
|
<p class="mb-0">@(Model.InventoryAccountName ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Model.CogsAccountId.HasValue)
|
@if (Model.CogsAccountId.HasValue)
|
||||||
{
|
{
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="text-muted small mb-1">COGS Account</label>
|
<label class="text-muted small mb-1">COGS Account</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.CogsAccountName ?? "—")</p>
|
<p class="mb-0">@(Model.CogsAccountName ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -380,7 +380,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="text-muted small mb-1">Location</label>
|
<label class="text-muted small mb-1">Location</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.Location ?? "—")</p>
|
<p class="mb-0">@(Model.Location ?? "—")</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<label class="text-muted small mb-1">Reorder Point</label>
|
<label class="text-muted small mb-1">Reorder Point</label>
|
||||||
@@ -457,9 +457,9 @@
|
|||||||
<button type="button" class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#stockAdjustmentModal">
|
<button type="button" class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#stockAdjustmentModal">
|
||||||
<i class="bi bi-plus-slash-minus me-2"></i>Stock Adjustment
|
<i class="bi bi-plus-slash-minus me-2"></i>Stock Adjustment
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#qrLabelModal">
|
<a asp-action="Label" asp-route-id="@Model.Id" target="_blank" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-qr-code me-2"></i>Print QR Label
|
<i class="bi bi-qr-code me-2"></i>Print QR Label
|
||||||
</button>
|
</a>
|
||||||
<a asp-action="Ledger" asp-route-inventoryItemId="@Model.Id" class="btn btn-outline-secondary">
|
<a asp-action="Ledger" asp-route-inventoryItemId="@Model.Id" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-journal-text me-2"></i>View Activity History
|
<i class="bi bi-journal-text me-2"></i>View Activity History
|
||||||
</a>
|
</a>
|
||||||
@@ -644,33 +644,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* QR Label Modal *@
|
|
||||||
<div class="modal fade" id="qrLabelModal" tabindex="-1" aria-labelledby="qrLabelModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header py-2">
|
|
||||||
<h6 class="modal-title" id="qrLabelModalLabel">
|
|
||||||
<i class="bi bi-qr-code me-2"></i>QR Label — @Model.Name
|
|
||||||
</h6>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body p-0 d-flex justify-content-center" style="background:#f0f0f0;min-height:360px;">
|
|
||||||
<iframe id="qrLabelFrame"
|
|
||||||
src="@Url.Action("Label", new { id = Model.Id, embed = true })"
|
|
||||||
style="width:100%;height:400px;border:none;"
|
|
||||||
title="QR Label Preview"></iframe>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer py-2">
|
|
||||||
<button type="button" class="btn btn-primary btn-sm"
|
|
||||||
onclick="document.getElementById('qrLabelFrame').contentWindow.print()">
|
|
||||||
<i class="bi bi-printer me-2"></i>Print Label
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script>
|
<script>
|
||||||
/* ── Stock Adjustment Modal ───────────────────────────────── */
|
/* ── Stock Adjustment Modal ───────────────────────────────── */
|
||||||
|
|||||||
@@ -22,11 +22,6 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.embedded {
|
|
||||||
padding-top: 24px;
|
|
||||||
min-height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.screen-controls {
|
.screen-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -117,10 +112,8 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="@((bool)(ViewBag.IsEmbed ?? false) ? "embedded" : "")">
|
<body>
|
||||||
|
|
||||||
@if (!(bool)(ViewBag.IsEmbed ?? false))
|
|
||||||
{
|
|
||||||
<div class="screen-controls">
|
<div class="screen-controls">
|
||||||
<button class="btn btn-primary" onclick="window.print()">
|
<button class="btn btn-primary" onclick="window.print()">
|
||||||
🖶 Print Label
|
🖶 Print Label
|
||||||
@@ -129,7 +122,6 @@
|
|||||||
← Back to Item
|
← Back to Item
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
<div class="label-card">
|
<div class="label-card">
|
||||||
<div class="label-logo">Powder Coating Logix</div>
|
<div class="label-logo">Powder Coating Logix</div>
|
||||||
|
|||||||
@@ -291,7 +291,7 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
<td>@Html.Raw(u.CoatColor ?? "—")</td>
|
<td>@(u.CoatColor ?? "—")</td>
|
||||||
<td class="text-end">@u.EstimatedLbs.ToString("N3")</td>
|
<td class="text-end">@u.EstimatedLbs.ToString("N3")</td>
|
||||||
<td class="text-end fw-semibold">@u.ActualLbsUsed.ToString("N3")</td>
|
<td class="text-end fw-semibold">@u.ActualLbsUsed.ToString("N3")</td>
|
||||||
<td class="text-end @(variance > 0 ? "variance-over" : variance < 0 ? "variance-under" : "")">
|
<td class="text-end @(variance > 0 ? "variance-over" : variance < 0 ? "variance-under" : "")">
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ else
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="color:#aaa;font-size:9pt;">@row</td>
|
<td style="color:#aaa;font-size:9pt;">@row</td>
|
||||||
<td><strong>@item.Name</strong></td>
|
<td><strong>@item.Name</strong></td>
|
||||||
<td>@Html.Raw(item.ColorName ?? "—")</td>
|
<td>@(item.ColorName ?? "—")</td>
|
||||||
<td style="font-family:monospace;font-size:9.5pt;">@item.SKU</td>
|
<td style="font-family:monospace;font-size:9.5pt;">@item.SKU</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,9 +244,9 @@
|
|||||||
<div class="text-muted small">@item.Name</div>
|
<div class="text-muted small">@item.Name</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(item.Manufacturer ?? "—")</td>
|
<td>@(item.Manufacturer ?? "—")</td>
|
||||||
<td class="text-muted small">@Html.Raw(item.ManufacturerPartNumber ?? "—")</td>
|
<td class="text-muted small">@(item.ManufacturerPartNumber ?? "—")</td>
|
||||||
<td>@Html.Raw(item.Finish ?? "—")</td>
|
<td>@(item.Finish ?? "—")</td>
|
||||||
<td>
|
<td>
|
||||||
@if (item.QuantityOnHand > 0)
|
@if (item.QuantityOnHand > 0)
|
||||||
{
|
{
|
||||||
@@ -399,9 +399,9 @@
|
|||||||
<div class="text-muted small">@item.Name</div>
|
<div class="text-muted small">@item.Name</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(item.Manufacturer ?? "—")</td>
|
<td>@(item.Manufacturer ?? "—")</td>
|
||||||
<td class="text-muted small">@Html.Raw(item.ManufacturerPartNumber ?? "—")</td>
|
<td class="text-muted small">@(item.ManufacturerPartNumber ?? "—")</td>
|
||||||
<td>@Html.Raw(item.Finish ?? "—")</td>
|
<td>@(item.Finish ?? "—")</td>
|
||||||
<td class="text-end pe-3">
|
<td class="text-end pe-3">
|
||||||
<button class="btn btn-sm btn-outline-danger me-1 btn-toggle-panel"
|
<button class="btn btn-sm btn-outline-danger me-1 btn-toggle-panel"
|
||||||
data-item-id="@item.Id" data-has-panel="false"
|
data-item-id="@item.Id" data-has-panel="false"
|
||||||
@@ -461,7 +461,7 @@
|
|||||||
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.Manufacturer ?? "")</td>
|
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.Manufacturer ?? "")</td>
|
||||||
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.ManufacturerPartNumber ?? "")</td>
|
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.ManufacturerPartNumber ?? "")</td>
|
||||||
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.Finish ?? "")</td>
|
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.Finish ?? "")</td>
|
||||||
<td style="border:1px solid #ccc;padding:6px 10px;">@Html.Raw(item.QuantityOnHand > 0 ? item.QuantityOnHand.ToString("N2") + " " + item.UnitOfMeasure : "—")</td>
|
<td style="border:1px solid #ccc;padding:6px 10px;">@(item.QuantityOnHand > 0 ? item.QuantityOnHand.ToString("N2") + " " + item.UnitOfMeasure : "—")</td>
|
||||||
<td style="border:1px solid #ccc;padding:6px 10px;"> </td>
|
<td style="border:1px solid #ccc;padding:6px 10px;"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,12 +179,12 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Due Date</label>
|
<label class="text-muted small mb-1">Due Date</label>
|
||||||
<p class="mb-0 @(Model.Status == InvoiceStatus.Overdue ? "text-danger fw-bold" : "")">
|
<p class="mb-0 @(Model.Status == InvoiceStatus.Overdue ? "text-danger fw-bold" : "")">
|
||||||
@Html.Raw(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "—")
|
@(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "—")
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="text-muted small mb-1">Sent Date</label>
|
<label class="text-muted small mb-1">Sent Date</label>
|
||||||
<p class="mb-0">@Html.Raw(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "—")</p>
|
<p class="mb-0">@(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "—")</p>
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.CustomerPO))
|
@if (!string.IsNullOrWhiteSpace(Model.CustomerPO))
|
||||||
{
|
{
|
||||||
@@ -350,7 +350,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted">
|
<td class="text-muted">
|
||||||
@Html.Raw(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "—")
|
@(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end fw-semibold">@gcItem.TotalPrice.ToString("C")</td>
|
<td class="text-end fw-semibold">@gcItem.TotalPrice.ToString("C")</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -396,7 +396,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>@p.PaymentDate.ToString("MM/dd/yyyy")</td>
|
<td>@p.PaymentDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td>@p.PaymentMethodDisplay</td>
|
<td>@p.PaymentMethodDisplay</td>
|
||||||
<td>@Html.Raw(p.Reference ?? "—")</td>
|
<td>@(p.Reference ?? "—")</td>
|
||||||
<td>
|
<td>
|
||||||
@if (!string.IsNullOrEmpty(p.DepositAccountName))
|
@if (!string.IsNullOrEmpty(p.DepositAccountName))
|
||||||
{
|
{
|
||||||
@@ -407,7 +407,7 @@
|
|||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(p.RecordedByName ?? "—")</td>
|
<td>@(p.RecordedByName ?? "—")</td>
|
||||||
<td class="text-end fw-semibold text-success">@p.Amount.ToString("C")</td>
|
<td class="text-end fw-semibold text-success">@p.Amount.ToString("C")</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
@if (!isVoided)
|
@if (!isVoided)
|
||||||
@@ -463,7 +463,7 @@
|
|||||||
<td>@r.RefundDate.ToString("MM/dd/yyyy")</td>
|
<td>@r.RefundDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td>@r.RefundMethodDisplay</td>
|
<td>@r.RefundMethodDisplay</td>
|
||||||
<td>@r.Reason</td>
|
<td>@r.Reason</td>
|
||||||
<td>@Html.Raw(r.Reference ?? "—")</td>
|
<td>@(r.Reference ?? "—")</td>
|
||||||
<td><span class="badge bg-@refundStatusColor">@r.Status</span></td>
|
<td><span class="badge bg-@refundStatusColor">@r.Status</span></td>
|
||||||
<td class="text-end fw-semibold text-danger">(@r.Amount.ToString("C"))</td>
|
<td class="text-end fw-semibold text-danger">(@r.Amount.ToString("C"))</td>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
<td>@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td class="@(inv.IsOverdue ? "fw-bold text-danger" : "")">
|
<td class="@(inv.IsOverdue ? "fw-bold text-danger" : "")">
|
||||||
@Html.Raw(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MM/dd/yyyy") : "—")
|
@(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MM/dd/yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">@inv.Total.ToString("C")</td>
|
<td class="text-end">@inv.Total.ToString("C")</td>
|
||||||
<td class="text-end @(inv.BalanceDue > 0 ? "fw-semibold" : "text-muted")">
|
<td class="text-end @(inv.BalanceDue > 0 ? "fw-semibold" : "text-muted")">
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
<td class="text-end">@gross.ToString("C")</td>
|
<td class="text-end">@gross.ToString("C")</td>
|
||||||
<td class="text-end text-muted">@inv.OnlineSurchargeCollected.ToString("C")</td>
|
<td class="text-end text-muted">@inv.OnlineSurchargeCollected.ToString("C")</td>
|
||||||
<td class="text-end fw-semibold">@net.ToString("C")</td>
|
<td class="text-end fw-semibold">@net.ToString("C")</td>
|
||||||
<td>@Html.Raw(dateDisplay)</td>
|
<td>@dateDisplay</td>
|
||||||
<td><span class="badge @statusClass">@inv.OnlinePaymentStatus</span></td>
|
<td><span class="badge @statusClass">@inv.OnlinePaymentStatus</span></td>
|
||||||
<td>
|
<td>
|
||||||
@if (!string.IsNullOrEmpty(inv.StripePaymentIntentId))
|
@if (!string.IsNullOrEmpty(inv.StripePaymentIntentId))
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@Html.Raw(invNum)
|
@invNum
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@custName</td>
|
<td>@custName</td>
|
||||||
|
|||||||
@@ -28,20 +28,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (Model.IsReworkJob && Model.OriginalJobId.HasValue)
|
|
||||||
{
|
|
||||||
<div class="alert alert-warning alert-permanent d-flex align-items-center gap-3 mb-4">
|
|
||||||
<i class="bi bi-arrow-repeat fs-5 flex-shrink-0"></i>
|
|
||||||
<div>
|
|
||||||
<strong>Rework Job</strong> — This job was created to redo work from
|
|
||||||
<a asp-action="Details" asp-route-id="@Model.OriginalJobId" class="alert-link fw-semibold">
|
|
||||||
@(Model.OriginalJobNumber ?? $"Job #{Model.OriginalJobId}")
|
|
||||||
</a>.
|
|
||||||
All costs for this redo are tracked here separately from the original job.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Status Banner -->
|
<!-- Status Banner -->
|
||||||
<div class="alert alert-@Model.StatusColorClass alert-permanent d-flex align-items-center mb-4">
|
<div class="alert alert-@Model.StatusColorClass alert-permanent d-flex align-items-center mb-4">
|
||||||
<i class="bi bi-info-circle me-2" style="font-size: 1.5rem;"></i>
|
<i class="bi bi-info-circle me-2" style="font-size: 1.5rem;"></i>
|
||||||
@@ -2203,88 +2189,6 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div id="reworkAddForm">
|
<div id="reworkAddForm">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|
||||||
<!-- Step 1: Are parts back in the shop? -->
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" id="rwCreateJobToggle" onchange="rework.toggleCreateJob(this.checked)" />
|
|
||||||
<label class="form-check-label fw-semibold" for="rwCreateJobToggle">
|
|
||||||
<i class="bi bi-briefcase me-1"></i>Parts are back — create a Rework Job in the shop
|
|
||||||
</label>
|
|
||||||
<div class="text-muted small">Turn this on if the parts are physically in the shop and need to go back through the workflow.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Item selection: checkboxes when creating a job, single dropdown otherwise -->
|
|
||||||
<div id="rwCreateJobOptions" style="display:none;" class="col-12">
|
|
||||||
<div class="border rounded p-3 bg-light">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-semibold mb-1">Which items need to be redone? <span class="text-danger">*</span></label>
|
|
||||||
<div class="text-muted small mb-2">Only checked items will be copied to the rework job.</div>
|
|
||||||
<div id="rwItemCheckboxes">
|
|
||||||
@if (Model.Items != null)
|
|
||||||
{
|
|
||||||
@foreach (var item in Model.Items)
|
|
||||||
{
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input rw-item-cb" type="checkbox" value="@item.Id" id="rwItem_@item.Id" />
|
|
||||||
<label class="form-check-label" for="rwItem_@item.Id">@item.Description</label>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="form-label fw-semibold mb-1">Who is responsible? <span class="text-danger">*</span></label>
|
|
||||||
<div class="row g-2">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="rwPricingType" id="rwPricingShopFault" value="0" />
|
|
||||||
<label class="form-check-label" for="rwPricingShopFault">
|
|
||||||
<strong>Shop Fault</strong>
|
|
||||||
<span class="text-muted small d-block">Our mistake — rework job priced at $0.</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="rwPricingType" id="rwPricingCustomerReduced" value="1" />
|
|
||||||
<label class="form-check-label" for="rwPricingCustomerReduced">
|
|
||||||
<strong>Customer — Reduced Rate</strong>
|
|
||||||
<span class="text-muted small d-block">Customer caused it but we’re helping out — prices copied, edit after creation.</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="rwPricingType" id="rwPricingCustomerFull" value="2" />
|
|
||||||
<label class="form-check-label" for="rwPricingCustomerFull">
|
|
||||||
<strong>Customer — Full Price</strong>
|
|
||||||
<span class="text-muted small d-block">Customer caused it — original pricing applies.</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="rwSpecificItemRow" class="col-md-6">
|
|
||||||
<label class="form-label">Specific Item (optional)</label>
|
|
||||||
<select class="form-select" id="rwJobItem">
|
|
||||||
<option value="">– Whole Job –</option>
|
|
||||||
@if (Model.Items != null)
|
|
||||||
{
|
|
||||||
@foreach (var item in Model.Items)
|
|
||||||
{
|
|
||||||
<option value="@item.Id">@item.Description</option>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class="my-0" />
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Type <span class="text-danger">*</span></label>
|
<label class="form-label">Type <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="rwType">
|
<select class="form-select" id="rwType">
|
||||||
@@ -2311,6 +2215,19 @@
|
|||||||
<label class="form-label">Defect Description <span class="text-danger">*</span></label>
|
<label class="form-label">Defect Description <span class="text-danger">*</span></label>
|
||||||
<textarea class="form-control" id="rwDefect" rows="2" placeholder="Describe the defect or issue..."></textarea>
|
<textarea class="form-control" id="rwDefect" rows="2" placeholder="Describe the defect or issue..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Specific Item (optional)</label>
|
||||||
|
<select class="form-select" id="rwJobItem">
|
||||||
|
<option value="">– Whole Job –</option>
|
||||||
|
@if (Model.Items != null)
|
||||||
|
{
|
||||||
|
@foreach (var item in Model.Items)
|
||||||
|
{
|
||||||
|
<option value="@item.Id">@item.Description</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Discovered By</label>
|
<label class="form-label">Discovered By</label>
|
||||||
<select class="form-select" id="rwDiscoveredBy">
|
<select class="form-select" id="rwDiscoveredBy">
|
||||||
@@ -2737,20 +2654,9 @@
|
|||||||
document.getElementById('rwBillingNotes').value = '';
|
document.getElementById('rwBillingNotes').value = '';
|
||||||
document.getElementById('rwReportedBy').value = '';
|
document.getElementById('rwReportedBy').value = '';
|
||||||
document.getElementById('rwDiscoveredDate').value = new Date().toISOString().split('T')[0];
|
document.getElementById('rwDiscoveredDate').value = new Date().toISOString().split('T')[0];
|
||||||
// Reset rework job creation section
|
|
||||||
document.getElementById('rwCreateJobToggle').checked = false;
|
|
||||||
document.getElementById('rwCreateJobOptions').style.display = 'none';
|
|
||||||
document.querySelectorAll('.rw-item-cb').forEach(cb => cb.checked = false);
|
|
||||||
document.querySelectorAll('input[name="rwPricingType"]').forEach(r => r.checked = false);
|
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCreateJob(on) {
|
|
||||||
document.getElementById('rwCreateJobOptions').style.display = on ? '' : 'none';
|
|
||||||
document.getElementById('rwSpecificItemRow').style.display = on ? 'none' : '';
|
|
||||||
if (!on) document.getElementById('rwJobItem').value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEdit(id) {
|
function openEdit(id) {
|
||||||
editId = id;
|
editId = id;
|
||||||
const r = records.find(x => x.id === id);
|
const r = records.find(x => x.id === id);
|
||||||
@@ -2779,23 +2685,9 @@
|
|||||||
// Create
|
// Create
|
||||||
const defect = document.getElementById('rwDefect').value.trim();
|
const defect = document.getElementById('rwDefect').value.trim();
|
||||||
if (!defect) { alert('Defect description is required.'); return; }
|
if (!defect) { alert('Defect description is required.'); return; }
|
||||||
|
|
||||||
const createJob = document.getElementById('rwCreateJobToggle').checked;
|
|
||||||
const selectedItemIds = createJob
|
|
||||||
? Array.from(document.querySelectorAll('.rw-item-cb:checked')).map(cb => parseInt(cb.value))
|
|
||||||
: null;
|
|
||||||
const pricingRadio = document.querySelector('input[name="rwPricingType"]:checked');
|
|
||||||
|
|
||||||
if (createJob && (!selectedItemIds || selectedItemIds.length === 0)) {
|
|
||||||
alert('Select at least one item to include in the rework job.'); return;
|
|
||||||
}
|
|
||||||
if (createJob && !pricingRadio) {
|
|
||||||
alert('Select who is responsible for this rework.'); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dto = {
|
const dto = {
|
||||||
jobId: jid,
|
jobId: jid,
|
||||||
jobItemId: createJob ? null : (document.getElementById('rwJobItem').value || null),
|
jobItemId: document.getElementById('rwJobItem').value || null,
|
||||||
reworkType: parseInt(document.getElementById('rwType').value),
|
reworkType: parseInt(document.getElementById('rwType').value),
|
||||||
reason: parseInt(document.getElementById('rwReason').value),
|
reason: parseInt(document.getElementById('rwReason').value),
|
||||||
defectDescription: defect,
|
defectDescription: defect,
|
||||||
@@ -2804,10 +2696,7 @@
|
|||||||
reportedByName: document.getElementById('rwReportedBy').value || null,
|
reportedByName: document.getElementById('rwReportedBy').value || null,
|
||||||
estimatedReworkCost: parseFloat(document.getElementById('rwEstCost').value) || 0,
|
estimatedReworkCost: parseFloat(document.getElementById('rwEstCost').value) || 0,
|
||||||
isBillableToCustomer: document.getElementById('rwBillable').checked,
|
isBillableToCustomer: document.getElementById('rwBillable').checked,
|
||||||
billingNotes: document.getElementById('rwBillingNotes').value || null,
|
billingNotes: document.getElementById('rwBillingNotes').value || null
|
||||||
createReworkJob: createJob,
|
|
||||||
reworkJobItemIds: selectedItemIds,
|
|
||||||
reworkPricingType: pricingRadio ? parseInt(pricingRadio.value) : null
|
|
||||||
};
|
};
|
||||||
const resp = await fetch('/Jobs/AddReworkRecord', {
|
const resp = await fetch('/Jobs/AddReworkRecord', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -2852,7 +2741,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
load();
|
load();
|
||||||
return { load, openAdd, openEdit, save, del, toggleCreateJob };
|
return { load, openAdd, openEdit, save, del };
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -3017,8 +2906,8 @@
|
|||||||
|
|
||||||
function updateTotals(total) {
|
function updateTotals(total) {
|
||||||
const fmt = total > 0 ? total.toFixed(2) + ' hrs' : '—';
|
const fmt = total > 0 ? total.toFixed(2) + ' hrs' : '—';
|
||||||
document.getElementById('totalHoursDisplay').innerHTML = fmt;
|
document.getElementById('totalHoursDisplay').textContent = fmt;
|
||||||
document.getElementById('timeEntriesTotalHours').innerHTML = total > 0 ? total.toFixed(2) : '—';
|
document.getElementById('timeEntriesTotalHours').textContent = total > 0 ? total.toFixed(2) : '—';
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Modal helpers -------------------------------------------------
|
// -- Modal helpers -------------------------------------------------
|
||||||
@@ -3356,4 +3245,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -483,10 +483,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="text-align: center;">@coat.Sequence</td>
|
<td style="text-align: center;">@coat.Sequence</td>
|
||||||
<td><strong>@coat.CoatName</strong></td>
|
<td><strong>@coat.CoatName</strong></td>
|
||||||
<td>@Html.Raw(coat.ColorName ?? "—")</td>
|
<td>@(coat.ColorName ?? "—")</td>
|
||||||
<td>@Html.Raw(coat.ColorCode ?? "—")</td>
|
<td>@(coat.ColorCode ?? "—")</td>
|
||||||
<td>@Html.Raw(coat.Finish ?? "—")</td>
|
<td>@(coat.Finish ?? "—")</td>
|
||||||
<td>@Html.Raw(coat.VendorName ?? "—")</td>
|
<td>@(coat.VendorName ?? "—")</td>
|
||||||
<td>
|
<td>
|
||||||
@if (coat.PowderToOrder.HasValue && coat.PowderToOrder.Value > 0)
|
@if (coat.PowderToOrder.HasValue && coat.PowderToOrder.Value > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -561,7 +561,7 @@
|
|||||||
<tr onclick="window.location='@Url.Action("Details", "Maintenance", new { id = item.Id })'"
|
<tr onclick="window.location='@Url.Action("Details", "Maintenance", new { id = item.Id })'"
|
||||||
style="cursor: pointer;">
|
style="cursor: pointer;">
|
||||||
<td>
|
<td>
|
||||||
<strong>@Html.Raw(item.Equipment?.EquipmentName ?? "—")</strong>
|
<strong>@(item.Equipment?.EquipmentName ?? "—")</strong>
|
||||||
@if (!string.IsNullOrEmpty(item.Equipment?.Location))
|
@if (!string.IsNullOrEmpty(item.Equipment?.Location))
|
||||||
{
|
{
|
||||||
<br /><small class="text-muted"><i class="bi bi-geo-alt me-1"></i>@item.Equipment.Location</small>
|
<br /><small class="text-muted"><i class="bi bi-geo-alt me-1"></i>@item.Equipment.Location</small>
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
<dt class="col-5 text-muted">Posted</dt>
|
<dt class="col-5 text-muted">Posted</dt>
|
||||||
<dd class="col-7 small">
|
<dd class="col-7 small">
|
||||||
@Model.PostedAt.Value.ToLocalTime().ToString("MMM d, yyyy h:mm tt")<br />
|
@Model.PostedAt.Value.ToLocalTime().ToString("MMM d, yyyy h:mm tt")<br />
|
||||||
<span class="text-muted">by @Html.Raw(Model.PostedBy ?? "—")</span>
|
<span class="text-muted">by @(Model.PostedBy ?? "—")</span>
|
||||||
</dd>
|
</dd>
|
||||||
}
|
}
|
||||||
<dt class="col-5 text-muted">Created</dt>
|
<dt class="col-5 text-muted">Created</dt>
|
||||||
|
|||||||
@@ -188,16 +188,16 @@
|
|||||||
@{
|
@{
|
||||||
var firstActivity = row.FirstJobCreatedAt ?? row.FirstQuoteCreatedAt;
|
var firstActivity = row.FirstJobCreatedAt ?? row.FirstQuoteCreatedAt;
|
||||||
}
|
}
|
||||||
@Html.Raw(firstActivity.HasValue ? firstActivity.Value.ToString("MMM d, yyyy") : "—")
|
@(firstActivity.HasValue ? firstActivity.Value.ToString("MMM d, yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">
|
<td class="text-muted small">
|
||||||
@Html.Raw(row.FirstInvoiceCreatedAt.HasValue ? row.FirstInvoiceCreatedAt.Value.ToString("MMM d, yyyy") : "—")
|
@(row.FirstInvoiceCreatedAt.HasValue ? row.FirstInvoiceCreatedAt.Value.ToString("MMM d, yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">
|
<td class="text-muted small">
|
||||||
@Html.Raw(row.FirstWorkflowCompletedAt.HasValue ? row.FirstWorkflowCompletedAt.Value.ToString("MMM d, yyyy") : "—")
|
@(row.FirstWorkflowCompletedAt.HasValue ? row.FirstWorkflowCompletedAt.Value.ToString("MMM d, yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">
|
<td class="text-muted small">
|
||||||
@Html.Raw(row.GuidedActivationDismissedAt.HasValue ? row.GuidedActivationDismissedAt.Value.ToString("MMM d, yyyy") : "—")
|
@(row.GuidedActivationDismissedAt.HasValue ? row.GuidedActivationDismissedAt.Value.ToString("MMM d, yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
@switch (row.Status)
|
@switch (row.Status)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row small mb-0">
|
<dl class="row small mb-0">
|
||||||
<dt class="col-5 text-muted">Company</dt>
|
<dt class="col-5 text-muted">Company</dt>
|
||||||
<dd class="col-7">@Html.Raw(ViewBag.CompanyName ?? (Model.CompanyId > 0 ? $"#{Model.CompanyId}" : "—"))</dd>
|
<dd class="col-7">@(ViewBag.CompanyName ?? (Model.CompanyId > 0 ? $"#{Model.CompanyId}" : "—"))</dd>
|
||||||
<dt class="col-5 text-muted">Type</dt>
|
<dt class="col-5 text-muted">Type</dt>
|
||||||
<dd class="col-7">@Model.NotificationType</dd>
|
<dd class="col-7">@Model.NotificationType</dd>
|
||||||
<dt class="col-5 text-muted">Channel</dt>
|
<dt class="col-5 text-muted">Channel</dt>
|
||||||
|
|||||||
@@ -188,13 +188,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="text-muted">Monthly ID</td>
|
<td class="text-muted">Monthly ID</td>
|
||||||
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdMonthly">
|
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdMonthly">
|
||||||
@Html.Raw(string.IsNullOrEmpty(plan.StripePriceIdMonthly) ? "—" : plan.StripePriceIdMonthly)
|
@(string.IsNullOrEmpty(plan.StripePriceIdMonthly) ? "—" : plan.StripePriceIdMonthly)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-muted">Annual ID</td>
|
<td class="text-muted">Annual ID</td>
|
||||||
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdAnnual">
|
<td class="font-monospace small text-truncate" style="max-width: 120px;" title="@plan.StripePriceIdAnnual">
|
||||||
@Html.Raw(string.IsNullOrEmpty(plan.StripePriceIdAnnual) ? "—" : plan.StripePriceIdAnnual)
|
@(string.IsNullOrEmpty(plan.StripePriceIdAnnual) ? "—" : plan.StripePriceIdAnnual)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
<td class="text-end fw-semibold">@item.CurrentStockLbs.ToString("0.##") lbs</td>
|
<td class="text-end fw-semibold">@item.CurrentStockLbs.ToString("0.##") lbs</td>
|
||||||
<td class="text-end">@item.ScheduledDemandLbs.ToString("0.##") lbs</td>
|
<td class="text-end">@item.ScheduledDemandLbs.ToString("0.##") lbs</td>
|
||||||
<td class="text-end @(item.ShortfallLbs > 0 ? "text-danger fw-bold" : "text-muted")">
|
<td class="text-end @(item.ShortfallLbs > 0 ? "text-danger fw-bold" : "text-muted")">
|
||||||
@Html.Raw(item.ShortfallLbs > 0 ? $"{item.ShortfallLbs:0.##} lbs" : "—")
|
@(item.ShortfallLbs > 0 ? $"{item.ShortfallLbs:0.##} lbs" : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">@item.ActiveJobCount</td>
|
<td class="text-center">@item.ActiveJobCount</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
<br /><small class="text-muted">@w.CoatName</small>
|
<br /><small class="text-muted">@w.CoatName</small>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@(w.InventoryItemName ?? "Custom")</td>
|
<td class="text-muted small">@(w.InventoryItemName ?? "Custom")</td>
|
||||||
<td>@Html.Raw(w.Complexity ?? "—")</td>
|
<td>@(w.Complexity ?? "—")</td>
|
||||||
<td class="text-end">@w.EstimatedLbs.ToString("0.##") lbs</td>
|
<td class="text-end">@w.EstimatedLbs.ToString("0.##") lbs</td>
|
||||||
<td class="text-end">@w.ActualLbs.ToString("0.##") lbs</td>
|
<td class="text-end">@w.ActualLbs.ToString("0.##") lbs</td>
|
||||||
<td class="text-end text-danger fw-bold">+@w.OveragePct.ToString("0.#")%</td>
|
<td class="text-end text-danger fw-bold">+@w.OveragePct.ToString("0.#")%</td>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ else
|
|||||||
<span class="text-muted"> · @coat.ColorName</span>
|
<span class="text-muted"> · @coat.ColorName</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end small">@Html.Raw(coat.EstimatedLbs.HasValue ? $"{coat.EstimatedLbs:0.##}" : "—")</td>
|
<td class="text-end small">@(coat.EstimatedLbs.HasValue ? $"{coat.EstimatedLbs:0.##}" : "—")</td>
|
||||||
<td class="text-end small">
|
<td class="text-end small">
|
||||||
@if (coat.IsRecorded)
|
@if (coat.IsRecorded)
|
||||||
{
|
{
|
||||||
@@ -61,10 +61,10 @@ else
|
|||||||
<td>Total</td>
|
<td>Total</td>
|
||||||
<td class="text-end">@Model.TotalEstimatedLbs.ToString("0.##") lbs</td>
|
<td class="text-end">@Model.TotalEstimatedLbs.ToString("0.##") lbs</td>
|
||||||
<td class="text-end @(Model.TotalActualLbs > 0 ? "text-success" : "")">
|
<td class="text-end @(Model.TotalActualLbs > 0 ? "text-success" : "")">
|
||||||
@Html.Raw(Model.TotalActualLbs > 0 ? $"{Model.TotalActualLbs:0.##} lbs" : "—")
|
@(Model.TotalActualLbs > 0 ? $"{Model.TotalActualLbs:0.##} lbs" : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end @(Model.TotalVarianceLbs > 0 ? "text-danger" : Model.TotalVarianceLbs < 0 ? "text-success" : "")">
|
<td class="text-end @(Model.TotalVarianceLbs > 0 ? "text-danger" : Model.TotalVarianceLbs < 0 ? "text-success" : "")">
|
||||||
@Html.Raw(Model.TotalActualLbs > 0 ? $"{(Model.TotalVarianceLbs > 0 ? "+" : "")}{Model.TotalVarianceLbs:0.##} lbs" : "—")
|
@(Model.TotalActualLbs > 0 ? $"{(Model.TotalVarianceLbs > 0 ? "+" : "")}{Model.TotalVarianceLbs:0.##} lbs" : "—")
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
<strong>@tier.TierName</strong>
|
<strong>@tier.TierName</strong>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="text-muted small">@Html.Raw(tier.Description ?? "—")</span>
|
<span class="text-muted small">@(tier.Description ?? "—")</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
@if (tier.DiscountPercent == 0)
|
@if (tier.DiscountPercent == 0)
|
||||||
|
|||||||
@@ -256,7 +256,7 @@
|
|||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<span class="text-muted">Expected Delivery</span>
|
<span class="text-muted">Expected Delivery</span>
|
||||||
<span class="@(Model.IsOverdue ? "text-danger fw-semibold" : "")">
|
<span class="@(Model.IsOverdue ? "text-danger fw-semibold" : "")">
|
||||||
@Html.Raw(Model.ExpectedDeliveryDate?.ToString("MM/dd/yyyy") ?? "—")
|
@(Model.ExpectedDeliveryDate?.ToString("MM/dd/yyyy") ?? "—")
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if (Model.ReceivedDate.HasValue)
|
@if (Model.ReceivedDate.HasValue)
|
||||||
|
|||||||
@@ -260,7 +260,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>@po.OrderDate.ToString("MM/dd/yyyy")</td>
|
<td>@po.OrderDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td>@Html.Raw(po.ExpectedDeliveryDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
<td>@(po.ExpectedDeliveryDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||||
<td class="text-center">@po.ItemCount</td>
|
<td class="text-center">@po.ItemCount</td>
|
||||||
<td class="text-end fw-semibold">$@po.TotalAmount.ToString("N2")</td>
|
<td class="text-end fw-semibold">$@po.TotalAmount.ToString("N2")</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
|
|||||||
@@ -156,11 +156,11 @@ else
|
|||||||
</a>
|
</a>
|
||||||
<span class="badge bg-secondary ms-1">@vend.Bills.Count bill@(vend.Bills.Count == 1 ? "" : "s")</span>
|
<span class="badge bg-secondary ms-1">@vend.Bills.Count bill@(vend.Bills.Count == 1 ? "" : "s")</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end aging-current">@Html.Raw(vend.TotalCurrent > 0 ? vend.TotalCurrent.ToString("C") : "—")</td>
|
<td class="text-end aging-current">@(vend.TotalCurrent > 0 ? vend.TotalCurrent.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-1-30">@Html.Raw(vend.Total1to30 > 0 ? vend.Total1to30.ToString("C") : "—")</td>
|
<td class="text-end aging-1-30">@(vend.Total1to30 > 0 ? vend.Total1to30.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-31-60">@Html.Raw(vend.Total31to60 > 0 ? vend.Total31to60.ToString("C") : "—")</td>
|
<td class="text-end aging-31-60">@(vend.Total31to60 > 0 ? vend.Total31to60.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-61-90">@Html.Raw(vend.Total61to90 > 0 ? vend.Total61to90.ToString("C") : "—")</td>
|
<td class="text-end aging-61-90">@(vend.Total61to90 > 0 ? vend.Total61to90.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-over90">@Html.Raw(vend.TotalOver90 > 0 ? vend.TotalOver90.ToString("C") : "—")</td>
|
<td class="text-end aging-over90">@(vend.TotalOver90 > 0 ? vend.TotalOver90.ToString("C") : "—")</td>
|
||||||
<td class="text-end fw-semibold">@vend.TotalBalance.ToString("C")</td>
|
<td class="text-end fw-semibold">@vend.TotalBalance.ToString("C")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ else
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@bill.BillDate.ToString("MM/dd/yyyy")</td>
|
<td class="text-muted small">@bill.BillDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td class="text-muted small">@Html.Raw(bill.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
<td class="text-muted small">@(bill.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||||
<td class="text-end fw-semibold @(bill.DaysOverdue > 30 ? "text-danger" : "")">@bill.BalanceDue.ToString("C")</td>
|
<td class="text-end fw-semibold @(bill.DaysOverdue > 30 ? "text-danger" : "")">@bill.BalanceDue.ToString("C")</td>
|
||||||
<td class="text-end"><span class="badge @ageBadge">@ageLabel</span></td>
|
<td class="text-end"><span class="badge @ageBadge">@ageLabel</span></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|||||||
@@ -156,11 +156,11 @@ else
|
|||||||
</a>
|
</a>
|
||||||
<span class="badge bg-secondary ms-1">@cust.Invoices.Count inv.</span>
|
<span class="badge bg-secondary ms-1">@cust.Invoices.Count inv.</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end aging-current">@Html.Raw(cust.TotalCurrent > 0 ? cust.TotalCurrent.ToString("C") : "—")</td>
|
<td class="text-end aging-current">@(cust.TotalCurrent > 0 ? cust.TotalCurrent.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-1-30">@Html.Raw(cust.Total1to30 > 0 ? cust.Total1to30.ToString("C") : "—")</td>
|
<td class="text-end aging-1-30">@(cust.Total1to30 > 0 ? cust.Total1to30.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-31-60">@Html.Raw(cust.Total31to60 > 0 ? cust.Total31to60.ToString("C") : "—")</td>
|
<td class="text-end aging-31-60">@(cust.Total31to60 > 0 ? cust.Total31to60.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-61-90">@Html.Raw(cust.Total61to90 > 0 ? cust.Total61to90.ToString("C") : "—")</td>
|
<td class="text-end aging-61-90">@(cust.Total61to90 > 0 ? cust.Total61to90.ToString("C") : "—")</td>
|
||||||
<td class="text-end aging-over90">@Html.Raw(cust.TotalOver90 > 0 ? cust.TotalOver90.ToString("C") : "—")</td>
|
<td class="text-end aging-over90">@(cust.TotalOver90 > 0 ? cust.TotalOver90.ToString("C") : "—")</td>
|
||||||
<td class="text-end fw-semibold">@cust.TotalBalance.ToString("C")</td>
|
<td class="text-end fw-semibold">@cust.TotalBalance.ToString("C")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ else
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
<td class="text-muted small">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td class="text-muted small">@Html.Raw(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
<td class="text-muted small">@(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||||
<td class="text-end fw-semibold @(inv.DaysOverdue > 30 ? "text-danger" : "")">@inv.BalanceDue.ToString("C")</td>
|
<td class="text-end fw-semibold @(inv.DaysOverdue > 30 ? "text-danger" : "")">@inv.BalanceDue.ToString("C")</td>
|
||||||
<td class="text-end"><span class="badge @ageBadge">@ageLabel</span></td>
|
<td class="text-end"><span class="badge @ageBadge">@ageLabel</span></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|||||||
@@ -101,11 +101,11 @@
|
|||||||
@item.CustomerName
|
@item.CustomerName
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="small">@Html.Raw(item.Email ?? "—")</td>
|
<td class="small">@(item.Email ?? "—")</td>
|
||||||
<td class="small">@Html.Raw(item.Phone ?? "—")</td>
|
<td class="small">@(item.Phone ?? "—")</td>
|
||||||
<td class="text-end">@item.TotalJobs</td>
|
<td class="text-end">@item.TotalJobs</td>
|
||||||
<td class="text-end">@item.LifetimeRevenue.ToString("C")</td>
|
<td class="text-end">@item.LifetimeRevenue.ToString("C")</td>
|
||||||
<td>@Html.Raw(item.LastJobDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
<td>@(item.LastJobDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
@if (item.DaysSinceLastJob < 0)
|
@if (item.DaysSinceLastJob < 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1604,7 +1604,7 @@
|
|||||||
<div class="fw-medium">@color.DisplayLabel</div>
|
<div class="fw-medium">@color.DisplayLabel</div>
|
||||||
<div class="text-muted small">@color.SKU</div>
|
<div class="text-muted small">@color.SKU</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@Html.Raw(color.Manufacturer ?? "—")</td>
|
<td class="text-muted small">@(color.Manufacturer ?? "—")</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div>@color.TotalLbsUsed.ToString("N1") lbs</div>
|
<div>@color.TotalLbsUsed.ToString("N1") lbs</div>
|
||||||
<div class="progress mt-1" style="height:4px; min-width:80px;">
|
<div class="progress mt-1" style="height:4px; min-width:80px;">
|
||||||
@@ -1749,7 +1749,7 @@
|
|||||||
@c.BalanceDue.ToString("C")
|
@c.BalanceDue.ToString("C")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end text-muted">@c.AvgInvoiceValue.ToString("C")</td>
|
<td class="text-end text-muted">@c.AvgInvoiceValue.ToString("C")</td>
|
||||||
<td class="text-muted small">@Html.Raw(c.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
<td class="text-muted small">@(c.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||||
<td class="pe-3">
|
<td class="pe-3">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<div class="progress flex-grow-1" style="height:6px;">
|
<div class="progress flex-grow-1" style="height:6px;">
|
||||||
@@ -1896,10 +1896,10 @@
|
|||||||
<span class="badge @badgeClass">@r.RetentionStatus</span>
|
<span class="badge @badgeClass">@r.RetentionStatus</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center small">
|
<td class="text-center small">
|
||||||
@Html.Raw(r.LastJobDate.HasValue ? r.LastJobDate.Value.ToString("MMM d, yyyy") : "—")
|
@(r.LastJobDate.HasValue ? r.LastJobDate.Value.ToString("MMM d, yyyy") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center small">
|
<td class="text-center small">
|
||||||
@Html.Raw(r.DaysSinceLastJob >= 0 ? r.DaysSinceLastJob + "d" : "—")
|
@(r.DaysSinceLastJob >= 0 ? r.DaysSinceLastJob + "d" : "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">@r.TotalJobs</td>
|
<td class="text-end">@r.TotalJobs</td>
|
||||||
<td class="text-end pe-3 fw-semibold">@r.LifetimeRevenue.ToString("C0")</td>
|
<td class="text-end pe-3 fw-semibold">@r.LifetimeRevenue.ToString("C0")</td>
|
||||||
@@ -2235,7 +2235,7 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="small">@inv.InvoiceDate.ToString("MMM d, yyyy")</td>
|
<td class="small">@inv.InvoiceDate.ToString("MMM d, yyyy")</td>
|
||||||
<td class="small">@Html.Raw(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MMM d, yyyy") : "—")</td>
|
<td class="small">@(inv.DueDate.HasValue ? inv.DueDate.Value.ToString("MMM d, yyyy") : "—")</td>
|
||||||
<td class="text-end">@inv.Total.ToString("C")</td>
|
<td class="text-end">@inv.Total.ToString("C")</td>
|
||||||
<td class="text-end text-success">@inv.AmountPaid.ToString("C")</td>
|
<td class="text-end text-success">@inv.AmountPaid.ToString("C")</td>
|
||||||
<td class="text-end fw-semibold @(inv.BalanceDue > 0 ? "text-danger" : "")">@inv.BalanceDue.ToString("C")</td>
|
<td class="text-end fw-semibold @(inv.BalanceDue > 0 ? "text-danger" : "")">@inv.BalanceDue.ToString("C")</td>
|
||||||
@@ -2349,7 +2349,7 @@
|
|||||||
<div class="small text-muted">@p.ColorCode @(!string.IsNullOrEmpty(p.SKU) ? $"· {p.SKU}" : "")</div>
|
<div class="small text-muted">@p.ColorCode @(!string.IsNullOrEmpty(p.SKU) ? $"· {p.SKU}" : "")</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="small text-muted">@Html.Raw(p.Manufacturer ?? "—")</td>
|
<td class="small text-muted">@(p.Manufacturer ?? "—")</td>
|
||||||
<td class="text-end">@p.TotalPurchasedLbs.ToString("N1")</td>
|
<td class="text-end">@p.TotalPurchasedLbs.ToString("N1")</td>
|
||||||
<td class="text-end">@p.TotalConsumedLbs.ToString("N1")</td>
|
<td class="text-end">@p.TotalConsumedLbs.ToString("N1")</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<div class="small text-muted">@item.SKU</div>
|
<div class="small text-muted">@item.SKU</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(item.ColorName ?? "—")</td>
|
<td>@(item.ColorName ?? "—")</td>
|
||||||
<td class="text-end">@item.CurrentStockLbs.ToString("N1")</td>
|
<td class="text-end">@item.CurrentStockLbs.ToString("N1")</td>
|
||||||
<td class="text-end">@item.TotalConsumedLbs.ToString("N1")</td>
|
<td class="text-end">@item.TotalConsumedLbs.ToString("N1")</td>
|
||||||
<td class="text-end">@item.TotalPurchasedLbs.ToString("N1")</td>
|
<td class="text-end">@item.TotalPurchasedLbs.ToString("N1")</td>
|
||||||
|
|||||||
@@ -85,12 +85,12 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@item.InvoiceDate.ToString("MMM d, yyyy")</td>
|
<td>@item.InvoiceDate.ToString("MMM d, yyyy")</td>
|
||||||
<td>@Html.Raw(item.DueDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
<td>@(item.DueDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||||
<td class="text-end">@item.Total.ToString("C")</td>
|
<td class="text-end">@item.Total.ToString("C")</td>
|
||||||
<td class="text-end text-success">@item.AmountPaid.ToString("C")</td>
|
<td class="text-end text-success">@item.AmountPaid.ToString("C")</td>
|
||||||
<td class="text-end fw-semibold">@item.BalanceDue.ToString("C")</td>
|
<td class="text-end fw-semibold">@item.BalanceDue.ToString("C")</td>
|
||||||
<td class="text-end @bucketClass">
|
<td class="text-end @bucketClass">
|
||||||
@Html.Raw(item.DaysOverdue > 0 ? item.DaysOverdue.ToString() : "—")
|
@(item.DaysOverdue > 0 ? item.DaysOverdue.ToString() : "—")
|
||||||
</td>
|
</td>
|
||||||
<td><span class="badge @bucketClass bg-opacity-10 border">@item.AgingBucket</span></td>
|
<td><span class="badge @bucketClass bg-opacity-10 border">@item.AgingBucket</span></td>
|
||||||
<td><span class="badge bg-secondary-subtle text-secondary">@item.StatusDisplay</span></td>
|
<td><span class="badge bg-secondary-subtle text-secondary">@item.StatusDisplay</span></td>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
{
|
{
|
||||||
<tr class="@(i.QuantityOnHand == 0 ? "table-danger" : "table-warning")">
|
<tr class="@(i.QuantityOnHand == 0 ? "table-danger" : "table-warning")">
|
||||||
<td>@i.Name</td>
|
<td>@i.Name</td>
|
||||||
<td>@Html.Raw(i.ColorName ?? "—")</td>
|
<td>@(i.ColorName ?? "—")</td>
|
||||||
<td class="text-end fw-semibold text-danger">@i.QuantityOnHand.ToString("N1")</td>
|
<td class="text-end fw-semibold text-danger">@i.QuantityOnHand.ToString("N1")</td>
|
||||||
<td class="text-end">@i.ReorderPoint.ToString("N1")</td>
|
<td class="text-end">@i.ReorderPoint.ToString("N1")</td>
|
||||||
<td class="text-muted small">@i.UnitOfMeasure</td>
|
<td class="text-muted small">@i.UnitOfMeasure</td>
|
||||||
|
|||||||
@@ -60,13 +60,13 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@Html.Raw(item.ColorName ?? "—")
|
@(item.ColorName ?? "—")
|
||||||
@if (!string.IsNullOrEmpty(item.ColorCode))
|
@if (!string.IsNullOrEmpty(item.ColorCode))
|
||||||
{
|
{
|
||||||
<span class="badge bg-secondary-subtle text-secondary ms-1">@item.ColorCode</span>
|
<span class="badge bg-secondary-subtle text-secondary ms-1">@item.ColorCode</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted">@Html.Raw(item.Manufacturer ?? "—")</td>
|
<td class="text-muted">@(item.Manufacturer ?? "—")</td>
|
||||||
<td class="text-end">@item.TotalPurchasedLbs.ToString("N1")</td>
|
<td class="text-end">@item.TotalPurchasedLbs.ToString("N1")</td>
|
||||||
<td class="text-end">@item.TotalConsumedLbs.ToString("N1")</td>
|
<td class="text-end">@item.TotalConsumedLbs.ToString("N1")</td>
|
||||||
<td class="text-end fw-semibold @varianceClass">@item.VarianceLbs.ToString("N1")</td>
|
<td class="text-end fw-semibold @varianceClass">@item.VarianceLbs.ToString("N1")</td>
|
||||||
|
|||||||
@@ -79,8 +79,8 @@
|
|||||||
<td>
|
<td>
|
||||||
@item.DisplayLabel
|
@item.DisplayLabel
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@Html.Raw(item.SKU ?? "—")</td>
|
<td class="text-muted small">@(item.SKU ?? "—")</td>
|
||||||
<td class="text-muted small">@Html.Raw(item.Manufacturer ?? "—")</td>
|
<td class="text-muted small">@(item.Manufacturer ?? "—")</td>
|
||||||
<td class="text-end fw-semibold">@item.TotalLbsUsed.ToString("N1")</td>
|
<td class="text-end fw-semibold">@item.TotalLbsUsed.ToString("N1")</td>
|
||||||
<td class="text-end">@item.TotalCost.ToString("C")</td>
|
<td class="text-end">@item.TotalCost.ToString("C")</td>
|
||||||
<td class="text-end">@item.JobCount</td>
|
<td class="text-end">@item.JobCount</td>
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
||||||
<td class="text-end">@line.Amount.ToString("C")</td>
|
<td class="text-end">@line.Amount.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
<tr class="report-subtotal-row">
|
<tr class="report-subtotal-row">
|
||||||
@@ -169,18 +169,18 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
||||||
<td class="text-end">@line.Amount.ToString("C")</td>
|
<td class="text-end">@line.Amount.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
<tr class="report-subtotal-row">
|
<tr class="report-subtotal-row">
|
||||||
<td class="ps-4 fw-semibold">Total COGS</td>
|
<td class="ps-4 fw-semibold">Total COGS</td>
|
||||||
<td class="text-end fw-semibold text-warning">(@Model.TotalCogs.ToString("C"))</td>
|
<td class="text-end fw-semibold text-warning">(@Model.TotalCogs.ToString("C"))</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : (Model.TotalCogs / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.TotalCogs / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="report-subtotal-row">
|
<tr class="report-subtotal-row">
|
||||||
<td class="ps-2 fw-semibold">Gross Profit</td>
|
<td class="ps-2 fw-semibold">Gross Profit</td>
|
||||||
<td class="text-end fw-semibold @(Model.GrossProfit >= 0 ? "text-success" : "text-danger")">@Model.GrossProfit.ToString("C")</td>
|
<td class="text-end fw-semibold @(Model.GrossProfit >= 0 ? "text-success" : "text-danger")">@Model.GrossProfit.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : Model.GrossMarginPercent.ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : Model.GrossMarginPercent.ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,20 +198,20 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
<td class="ps-4">@line.AccountNumber <span class="text-muted">@line.AccountName</span></td>
|
||||||
<td class="text-end">@line.Amount.ToString("C")</td>
|
<td class="text-end">@line.Amount.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (line.Amount / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
<tr class="report-subtotal-row">
|
<tr class="report-subtotal-row">
|
||||||
<td class="ps-4 fw-semibold">Total Expenses</td>
|
<td class="ps-4 fw-semibold">Total Expenses</td>
|
||||||
<td class="text-end fw-semibold text-danger">(@Model.TotalExpenses.ToString("C"))</td>
|
<td class="text-end fw-semibold text-danger">(@Model.TotalExpenses.ToString("C"))</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : (Model.TotalExpenses / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.TotalExpenses / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr class="report-net-row @(Model.NetIncome < 0 ? "report-net-negative" : "")">
|
<tr class="report-net-row @(Model.NetIncome < 0 ? "report-net-negative" : "")">
|
||||||
<td class="ps-2">Net Income</td>
|
<td class="ps-2">Net Income</td>
|
||||||
<td class="text-end @(Model.NetIncome >= 0 ? "text-success" : "text-danger")">@Model.NetIncome.ToString("C")</td>
|
<td class="text-end @(Model.NetIncome >= 0 ? "text-success" : "text-danger")">@Model.NetIncome.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(Model.TotalRevenue == 0 ? "—" : (Model.NetIncome / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small">@(Model.TotalRevenue == 0 ? "—" : (Model.NetIncome / Model.TotalRevenue * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ else
|
|||||||
<td class="text-end fw-semibold">@c.TotalInvoiced.ToString("C")</td>
|
<td class="text-end fw-semibold">@c.TotalInvoiced.ToString("C")</td>
|
||||||
<td class="text-end text-success">@c.TotalPaid.ToString("C")</td>
|
<td class="text-end text-success">@c.TotalPaid.ToString("C")</td>
|
||||||
<td class="text-end @(c.BalanceDue > 0 ? "text-warning" : "text-muted")">@c.BalanceDue.ToString("C")</td>
|
<td class="text-end @(c.BalanceDue > 0 ? "text-warning" : "text-muted")">@c.BalanceDue.ToString("C")</td>
|
||||||
<td class="text-end text-muted small no-print">@Html.Raw(Model.TotalInvoiced == 0 ? "—" : (c.TotalInvoiced / Model.TotalInvoiced * 100).ToString("F1") + "%")</td>
|
<td class="text-end text-muted small no-print">@(Model.TotalInvoiced == 0 ? "—" : (c.TotalInvoiced / Model.TotalInvoiced * 100).ToString("F1") + "%")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -263,9 +263,9 @@ else
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@inv.CustomerName</td>
|
<td class="text-muted small">@inv.CustomerName</td>
|
||||||
<td class="text-muted small">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
<td class="text-muted small">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td class="text-muted small">@Html.Raw(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
<td class="text-muted small">@(inv.DueDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||||
<td class="text-end">@inv.SubTotal.ToString("C")</td>
|
<td class="text-end">@inv.SubTotal.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(inv.TaxAmount > 0 ? inv.TaxAmount.ToString("C") : "—")</td>
|
<td class="text-end text-muted small">@(inv.TaxAmount > 0 ? inv.TaxAmount.ToString("C") : "—")</td>
|
||||||
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
|
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
|
||||||
<td class="text-end text-success">@inv.AmountPaid.ToString("C")</td>
|
<td class="text-end text-success">@inv.AmountPaid.ToString("C")</td>
|
||||||
<td><span class="badge @statusBadge">@inv.Status</span></td>
|
<td><span class="badge @statusBadge">@inv.Status</span></td>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
@item.BalanceDue.ToString("C")
|
@item.BalanceDue.ToString("C")
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">@item.AvgInvoiceValue.ToString("C")</td>
|
<td class="text-end">@item.AvgInvoiceValue.ToString("C")</td>
|
||||||
<td>@Html.Raw(item.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
<td>@(item.LastInvoiceDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -284,11 +284,11 @@ else
|
|||||||
<td class="small text-muted">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
<td class="small text-muted">@inv.InvoiceDate.ToString("MM/dd/yyyy")</td>
|
||||||
<td><span class="badge @statusBadge">@inv.Status</span></td>
|
<td><span class="badge @statusBadge">@inv.Status</span></td>
|
||||||
<td class="text-end">@inv.SubTotal.ToString("C")</td>
|
<td class="text-end">@inv.SubTotal.ToString("C")</td>
|
||||||
<td class="text-end text-muted small">@Html.Raw(isTaxable ? inv.TaxPercent.ToString("F2") + "%" : "—")</td>
|
<td class="text-end text-muted small">@(isTaxable ? inv.TaxPercent.ToString("F2") + "%" : "—")</td>
|
||||||
<td class="text-end @Html.Raw(isTaxable ? "fw-semibold text-primary" : "text-muted")">@Html.Raw(isTaxable ? inv.TaxAmount.ToString("C") : "—")</td>
|
<td class="text-end @(isTaxable ? "fw-semibold text-primary" : "text-muted")">@(isTaxable ? inv.TaxAmount.ToString("C") : "—")</td>
|
||||||
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
|
<td class="text-end fw-semibold">@inv.Total.ToString("C")</td>
|
||||||
<td class="text-end text-success no-print">@inv.AmountPaid.ToString("C")</td>
|
<td class="text-end text-success no-print">@inv.AmountPaid.ToString("C")</td>
|
||||||
<td class="small text-muted">@Html.Raw(string.IsNullOrEmpty(inv.TaxAccountName) ? "—" : inv.TaxAccountName)</td>
|
<td class="small text-muted">@(string.IsNullOrEmpty(inv.TaxAccountName) ? "—" : inv.TaxAccountName)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ else
|
|||||||
<span class="text-danger small"><i class="bi bi-exclamation-triangle me-1"></i>Missing</span>
|
<span class="text-danger small"><i class="bi bi-exclamation-triangle me-1"></i>Missing</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="small">@Html.Raw(row.Address ?? "—")</td>
|
<td class="small">@(row.Address ?? "—")</td>
|
||||||
<td class="text-end">@row.BillsPaid.ToString("C")</td>
|
<td class="text-end">@row.BillsPaid.ToString("C")</td>
|
||||||
<td class="text-end">@row.ExpensesPaid.ToString("C")</td>
|
<td class="text-end">@row.ExpensesPaid.ToString("C")</td>
|
||||||
<td class="text-end fw-bold @(row.NeedsForm ? "text-danger" : "")">@row.TotalPaid.ToString("C")</td>
|
<td class="text-end fw-bold @(row.NeedsForm ? "text-danger" : "")">@row.TotalPaid.ToString("C")</td>
|
||||||
|
|||||||
@@ -210,7 +210,7 @@
|
|||||||
<td class="small fw-semibold">@alert.CompanyName</td>
|
<td class="small fw-semibold">@alert.CompanyName</td>
|
||||||
<td class="small">@alert.PlanName</td>
|
<td class="small">@alert.PlanName</td>
|
||||||
<td><span class="badge bg-@statusClass">@alert.Status</span></td>
|
<td><span class="badge bg-@statusClass">@alert.Status</span></td>
|
||||||
<td class="small">@Html.Raw(alert.EndDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
<td class="small">@(alert.EndDate?.ToString("MM/dd/yyyy") ?? "—")</td>
|
||||||
<td class="small @(alert.DaysUntilExpiry < 0 ? "text-danger" : "text-warning")">@daysText</td>
|
<td class="small @(alert.DaysUntilExpiry < 0 ? "text-danger" : "text-warning")">@daysText</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-controller="SubscriptionManagement" asp-action="Manage"
|
<a asp-controller="SubscriptionManagement" asp-action="Manage"
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
<div class="mobile-card-body">
|
<div class="mobile-card-body">
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">End Date</span>
|
<span class="mobile-card-label">End Date</span>
|
||||||
<span class="mobile-card-value">@Html.Raw(alert.EndDate?.ToString("MM/dd/yyyy") ?? "—")</span>
|
<span class="mobile-card-value">@(alert.EndDate?.ToString("MM/dd/yyyy") ?? "—")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Days</span>
|
<span class="mobile-card-label">Days</span>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<dd class="col-7"><span class="badge bg-light text-dark border">@Model.EventType</span></dd>
|
<dd class="col-7"><span class="badge bg-light text-dark border">@Model.EventType</span></dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">Company ID</dt>
|
<dt class="col-5 text-muted">Company ID</dt>
|
||||||
<dd class="col-7">@Html.Raw(Model.CompanyId.HasValue ? Model.CompanyId.ToString() : "—")</dd>
|
<dd class="col-7">@(Model.CompanyId.HasValue ? Model.CompanyId.ToString() : "—")</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">Status</dt>
|
<dt class="col-5 text-muted">Status</dt>
|
||||||
<dd class="col-7"><span class="badge bg-@statusClass">@Model.Status</span></dd>
|
<dd class="col-7"><span class="badge bg-@statusClass">@Model.Status</span></dd>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<dd class="col-7">@Model.ReceivedAt.ToString("MM/dd/yyyy HH:mm:ss") UTC</dd>
|
<dd class="col-7">@Model.ReceivedAt.ToString("MM/dd/yyyy HH:mm:ss") UTC</dd>
|
||||||
|
|
||||||
<dt class="col-5 text-muted">Processed At</dt>
|
<dt class="col-5 text-muted">Processed At</dt>
|
||||||
<dd class="col-7">@Html.Raw(Model.ProcessedAt.HasValue ? Model.ProcessedAt.Value.ToString("MM/dd/yyyy HH:mm:ss") + " UTC" : "—")</dd>
|
<dd class="col-7">@(Model.ProcessedAt.HasValue ? Model.ProcessedAt.Value.ToString("MM/dd/yyyy HH:mm:ss") + " UTC" : "—")</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -145,12 +145,12 @@
|
|||||||
<td class="small">
|
<td class="small">
|
||||||
<span class="badge bg-secondary-subtle text-body border">@evt.EventType</span>
|
<span class="badge bg-secondary-subtle text-body border">@evt.EventType</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="small">@Html.Raw(evt.CompanyId.HasValue ? $"#{evt.CompanyId}" : "—")</td>
|
<td class="small">@(evt.CompanyId.HasValue ? $"#{evt.CompanyId}" : "—")</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-@statusClass">@evt.Status</span>
|
<span class="badge bg-@statusClass">@evt.Status</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="small">
|
<td class="small">
|
||||||
@Html.Raw(evt.ProcessedAt.HasValue ? evt.ProcessedAt.Value.ToString("HH:mm:ss") : "—")
|
@(evt.ProcessedAt.HasValue ? evt.ProcessedAt.Value.ToString("HH:mm:ss") : "—")
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-action="Details" asp-route-id="@evt.Id" class="btn btn-outline-secondary btn-sm py-0">View</a>
|
<a asp-action="Details" asp-route-id="@evt.Id" class="btn btn-outline-secondary btn-sm py-0">View</a>
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Company</span>
|
<span class="mobile-card-label">Company</span>
|
||||||
<span class="mobile-card-value">@Html.Raw(evt.CompanyId.HasValue ? $"#{evt.CompanyId}" : "—")</span>
|
<span class="mobile-card-value">@(evt.CompanyId.HasValue ? $"#{evt.CompanyId}" : "—")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Event ID</span>
|
<span class="mobile-card-label">Event ID</span>
|
||||||
|
|||||||
@@ -66,9 +66,9 @@
|
|||||||
<dt class="col-7 text-muted">Users</dt>
|
<dt class="col-7 text-muted">Users</dt>
|
||||||
<dd class="col-5 fw-semibold">@ViewBag.UserCount</dd>
|
<dd class="col-5 fw-semibold">@ViewBag.UserCount</dd>
|
||||||
<dt class="col-7 text-muted">Stripe Customer</dt>
|
<dt class="col-7 text-muted">Stripe Customer</dt>
|
||||||
<dd class="col-5"><code class="small">@Html.Raw(Model.StripeCustomerId ?? "—")</code></dd>
|
<dd class="col-5"><code class="small">@(Model.StripeCustomerId ?? "—")</code></dd>
|
||||||
<dt class="col-7 text-muted">Stripe Sub</dt>
|
<dt class="col-7 text-muted">Stripe Sub</dt>
|
||||||
<dd class="col-5"><code class="small">@Html.Raw(Model.StripeSubscriptionId ?? "—")</code></dd>
|
<dd class="col-5"><code class="small">@(Model.StripeSubscriptionId ?? "—")</code></dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-4">Last Migration</th>
|
<th class="ps-4">Last Migration</th>
|
||||||
<td><small class="text-muted font-monospace">@Html.Raw(Model.LastAppliedMigration ?? "—")</small></td>
|
<td><small class="text-muted font-monospace">@(Model.LastAppliedMigration ?? "—")</small></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="ps-4">Last Seed Run</th>
|
<th class="ps-4">Last Seed Run</th>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
<td class="text-nowrap small text-muted">@row.Timestamp.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd HH:mm:ss")</td>
|
<td class="text-nowrap small text-muted">@row.Timestamp.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd HH:mm:ss")</td>
|
||||||
<td><span class="badge @LevelBadge(row.Level)">@row.Level</span></td>
|
<td><span class="badge @LevelBadge(row.Level)">@row.Level</span></td>
|
||||||
<td class="small text-truncate" style="max-width:180px" title="@row.SourceContext">
|
<td class="small text-truncate" style="max-width:180px" title="@row.SourceContext">
|
||||||
@Html.Raw(row.SourceContext?.Split('.').LastOrDefault() ?? "—")
|
@(row.SourceContext?.Split('.').LastOrDefault() ?? "—")
|
||||||
</td>
|
</td>
|
||||||
<td class="small text-truncate" style="max-width:400px">
|
<td class="small text-truncate" style="max-width:400px">
|
||||||
@row.Message
|
@row.Message
|
||||||
@@ -162,8 +162,8 @@
|
|||||||
<i class="bi bi-bug text-danger ms-1" title="Has exception"></i>
|
<i class="bi bi-bug text-danger ms-1" title="Has exception"></i>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="small text-muted">@Html.Raw(row.UserName ?? "—")</td>
|
<td class="small text-muted">@(row.UserName ?? "—")</td>
|
||||||
<td class="small text-muted text-center">@Html.Raw(row.CompanyId?.ToString() ?? "—")</td>
|
<td class="small text-muted text-center">@(row.CompanyId?.ToString() ?? "—")</td>
|
||||||
<td class="text-center"><i class="bi bi-zoom-in text-muted small"></i></td>
|
<td class="text-center"><i class="bi bi-zoom-in text-muted small"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
<div class="mobile-card-header">
|
<div class="mobile-card-header">
|
||||||
<div class="mobile-card-icon @cardIconBg"><i class="bi @cardIcon"></i></div>
|
<div class="mobile-card-icon @cardIconBg"><i class="bi @cardIcon"></i></div>
|
||||||
<div class="mobile-card-title">
|
<div class="mobile-card-title">
|
||||||
<h6>@Html.Raw(msgTruncated)</h6>
|
<h6>@msgTruncated</h6>
|
||||||
<small class="text-muted">@row.Timestamp.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd HH:mm:ss")</small>
|
<small class="text-muted">@row.Timestamp.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd HH:mm:ss")</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Source</span>
|
<span class="mobile-card-label">Source</span>
|
||||||
<span class="mobile-card-value small">@Html.Raw(row.SourceContext?.Split('.').LastOrDefault() ?? "—")</span>
|
<span class="mobile-card-value small">@(row.SourceContext?.Split('.').LastOrDefault() ?? "—")</span>
|
||||||
</div>
|
</div>
|
||||||
@if (row.UserName != null)
|
@if (row.UserName != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,8 +67,8 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@rate.Rate.ToString("0.##")%</td>
|
<td>@rate.Rate.ToString("0.##")%</td>
|
||||||
<td>@Html.Raw(rate.State ?? "—")</td>
|
<td>@(rate.State ?? "—")</td>
|
||||||
<td class="text-muted small">@Html.Raw(rate.Description ?? "—")</td>
|
<td class="text-muted small">@(rate.Description ?? "—")</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
@if (rate.IsDefault)
|
@if (rate.IsDefault)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,23 +220,23 @@
|
|||||||
<div class="mobile-card-body">
|
<div class="mobile-card-body">
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Users</span>
|
<span class="mobile-card-label">Users</span>
|
||||||
<span class="mobile-card-value">@row.Users / @Html.Raw(LimitDisplay(row.MaxUsers))</span>
|
<span class="mobile-card-value">@row.Users / @LimitDisplay(row.MaxUsers)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Active Jobs</span>
|
<span class="mobile-card-label">Active Jobs</span>
|
||||||
<span class="mobile-card-value">@row.ActiveJobs / @Html.Raw(LimitDisplay(row.MaxActiveJobs))</span>
|
<span class="mobile-card-value">@row.ActiveJobs / @LimitDisplay(row.MaxActiveJobs)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Customers</span>
|
<span class="mobile-card-label">Customers</span>
|
||||||
<span class="mobile-card-value">@row.Customers / @Html.Raw(LimitDisplay(row.MaxCustomers))</span>
|
<span class="mobile-card-value">@row.Customers / @LimitDisplay(row.MaxCustomers)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Active Quotes</span>
|
<span class="mobile-card-label">Active Quotes</span>
|
||||||
<span class="mobile-card-value">@row.ActiveQuotes / @Html.Raw(LimitDisplay(row.MaxActiveQuotes))</span>
|
<span class="mobile-card-value">@row.ActiveQuotes / @LimitDisplay(row.MaxActiveQuotes)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Catalog Items</span>
|
<span class="mobile-card-label">Catalog Items</span>
|
||||||
<span class="mobile-card-value">@row.CatalogItems / @Html.Raw(LimitDisplay(row.MaxCatalogItems))</span>
|
<span class="mobile-card-value">@row.CatalogItems / @LimitDisplay(row.MaxCatalogItems)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-footer">
|
<div class="mobile-card-footer">
|
||||||
|
|||||||
@@ -123,13 +123,13 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="path-badge text-muted" title="@u.CurrentPath">@Html.Raw(u.CurrentPath ?? "—")</span>
|
<span class="path-badge text-muted" title="@u.CurrentPath">@(u.CurrentPath ?? "—")</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="mb-1 small text-muted">@secsAgo s ago</div>
|
<div class="mb-1 small text-muted">@secsAgo s ago</div>
|
||||||
<div class="last-seen-bar" style="width:@barPct%"></div>
|
<div class="last-seen-bar" style="width:@barPct%"></div>
|
||||||
</td>
|
</td>
|
||||||
<td class="small text-muted">@Html.Raw(u.IpAddress ?? "—")</td>
|
<td class="small text-muted">@(u.IpAddress ?? "—")</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<div class="mobile-card-body">
|
<div class="mobile-card-body">
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Company</span>
|
<span class="mobile-card-label">Company</span>
|
||||||
<span class="mobile-card-value">@Html.Raw(u.CompanyName ?? "—")</span>
|
<span class="mobile-card-value">@(u.CompanyName ?? "—")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Role</span>
|
<span class="mobile-card-label">Role</span>
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Page</span>
|
<span class="mobile-card-label">Page</span>
|
||||||
<span class="mobile-card-value small text-muted" style="font-family:monospace">@Html.Raw(u.CurrentPath ?? "—")</span>
|
<span class="mobile-card-value small text-muted" style="font-family:monospace">@(u.CurrentPath ?? "—")</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Last Seen</span>
|
<span class="mobile-card-label">Last Seen</span>
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a asp-controller="Bills" asp-action="Details" asp-route-id="@bill.Id">@bill.BillNumber</a>
|
<a asp-controller="Bills" asp-action="Details" asp-route-id="@bill.Id">@bill.BillNumber</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted small">@Html.Raw(bill.DueDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
<td class="text-muted small">@(bill.DueDate?.ToString("MMM d, yyyy") ?? "—")</td>
|
||||||
<td class="text-end">@bill.BalanceDue.ToString("C")</td>
|
<td class="text-end">@bill.BalanceDue.ToString("C")</td>
|
||||||
<td class="text-end" style="width:150px">
|
<td class="text-end" style="width:150px">
|
||||||
<form asp-action="Apply" method="post" class="d-inline">
|
<form asp-action="Apply" method="post" class="d-inline">
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
<div class="mobile-card-row">
|
<div class="mobile-card-row">
|
||||||
<span class="mobile-card-label">Remaining</span>
|
<span class="mobile-card-label">Remaining</span>
|
||||||
<span class="mobile-card-value @(vc.RemainingAmount > 0 ? "text-success fw-semibold" : "text-muted")">
|
<span class="mobile-card-value @(vc.RemainingAmount > 0 ? "text-success fw-semibold" : "text-muted")">
|
||||||
@Html.Raw(vc.RemainingAmount > 0 ? vc.RemainingAmount.ToString("C") : "—")
|
@(vc.RemainingAmount > 0 ? vc.RemainingAmount.ToString("C") : "—")
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(vc.Memo))
|
@if (!string.IsNullOrWhiteSpace(vc.Memo))
|
||||||
|
|||||||
@@ -70,9 +70,9 @@
|
|||||||
<td>
|
<td>
|
||||||
<strong>@vendor.CompanyName</strong>
|
<strong>@vendor.CompanyName</strong>
|
||||||
</td>
|
</td>
|
||||||
<td>@Html.Raw(vendor.ContactName ?? "—")</td>
|
<td>@(vendor.ContactName ?? "—")</td>
|
||||||
<td>@Html.Raw(vendor.Phone ?? "—")</td>
|
<td>@(vendor.Phone ?? "—")</td>
|
||||||
<td>@Html.Raw(vendor.Email ?? "—")</td>
|
<td>@(vendor.Email ?? "—")</td>
|
||||||
<td>
|
<td>
|
||||||
@if (vendor.InventoryItemCount > 0)
|
@if (vendor.InventoryItemCount > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,18 +150,13 @@ public class QuoteAndReworkControllerFlowTests
|
|||||||
DefectDescription = "Thin coverage on one edge",
|
DefectDescription = "Thin coverage on one edge",
|
||||||
DiscoveredBy = ReworkDiscoveredBy.Internal,
|
DiscoveredBy = ReworkDiscoveredBy.Internal,
|
||||||
DiscoveredDate = new DateTime(2026, 5, 9),
|
DiscoveredDate = new DateTime(2026, 5, 9),
|
||||||
EstimatedReworkCost = 65m,
|
EstimatedReworkCost = 65m
|
||||||
CreateReworkJob = true,
|
|
||||||
ReworkJobItemIds = [10],
|
|
||||||
ReworkPricingType = ReworkPricingType.CustomerFull
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.IsType<JsonResult>(result);
|
Assert.IsType<JsonResult>(result);
|
||||||
|
|
||||||
var reworkJob = await context.Jobs.SingleAsync(j => j.IsReworkJob);
|
var reworkJob = await context.Jobs.SingleAsync(j => j.IsReworkJob);
|
||||||
Assert.Equal(1, reworkJob.OriginalJobId);
|
Assert.Equal(1, reworkJob.OriginalJobId);
|
||||||
Assert.Equal("JOB-2605-0001-R1", reworkJob.JobNumber);
|
|
||||||
Assert.Equal(2, reworkJob.JobStatusId); // first non-Pending status
|
|
||||||
|
|
||||||
var reworkItem = await context.JobItems.SingleAsync(i => i.JobId == reworkJob.Id);
|
var reworkItem = await context.JobItems.SingleAsync(i => i.JobId == reworkJob.Id);
|
||||||
Assert.True(reworkItem.IsSalesItem);
|
Assert.True(reworkItem.IsSalesItem);
|
||||||
@@ -289,9 +284,13 @@ public class QuoteAndReworkControllerFlowTests
|
|||||||
CompanyName = "Acme Fabrication"
|
CompanyName = "Acme Fabrication"
|
||||||
});
|
});
|
||||||
|
|
||||||
context.JobStatusLookups.AddRange(
|
context.JobStatusLookups.Add(new JobStatusLookup
|
||||||
new JobStatusLookup { Id = 1, CompanyId = 1, StatusCode = "PENDING", DisplayName = "Pending", DisplayOrder = 1 },
|
{
|
||||||
new JobStatusLookup { Id = 2, CompanyId = 1, StatusCode = "IN_PREPARATION", DisplayName = "In Preparation", DisplayOrder = 2 });
|
Id = 1,
|
||||||
|
CompanyId = 1,
|
||||||
|
StatusCode = "PENDING",
|
||||||
|
DisplayName = "Pending"
|
||||||
|
});
|
||||||
|
|
||||||
context.JobPriorityLookups.Add(new JobPriorityLookup
|
context.JobPriorityLookups.Add(new JobPriorityLookup
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user