Compare commits
6 Commits
03b425a12f
...
7cbae31916
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cbae31916 | |||
| 9367e358d9 | |||
| 9f1460c9c0 | |||
| 94e536178c | |||
| 456d054229 | |||
| f38a1e3273 |
@@ -57,6 +57,7 @@ public class InvoiceDto
|
|||||||
public string? InternalNotes { get; set; }
|
public string? InternalNotes { get; set; }
|
||||||
public string? Terms { get; set; }
|
public string? Terms { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public string? ExternalReference { get; set; }
|
public string? ExternalReference { get; set; }
|
||||||
public int? SalesTaxAccountId { get; set; }
|
public int? SalesTaxAccountId { get; set; }
|
||||||
public string? SalesTaxAccountName { get; set; }
|
public string? SalesTaxAccountName { get; set; }
|
||||||
@@ -88,6 +89,7 @@ public class CreateInvoiceDto
|
|||||||
public string? InternalNotes { get; set; }
|
public string? InternalNotes { get; set; }
|
||||||
public string? Terms { get; set; }
|
public string? Terms { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
/// <summary>Early-payment discount percentage parsed from the customer's payment terms (e.g., 2.0 for "2/10 Net 30"). Informational — does not auto-apply.</summary>
|
/// <summary>Early-payment discount percentage parsed from the customer's payment terms (e.g., 2.0 for "2/10 Net 30"). Informational — does not auto-apply.</summary>
|
||||||
public decimal EarlyPaymentDiscountPercent { get; set; }
|
public decimal EarlyPaymentDiscountPercent { get; set; }
|
||||||
/// <summary>Number of days within which the early-payment discount applies (e.g., 10 for "2/10 Net 30").</summary>
|
/// <summary>Number of days within which the early-payment discount applies (e.g., 10 for "2/10 Net 30").</summary>
|
||||||
@@ -105,6 +107,7 @@ public class UpdateInvoiceDto
|
|||||||
public string? InternalNotes { get; set; }
|
public string? InternalNotes { get; set; }
|
||||||
public string? Terms { get; set; }
|
public string? Terms { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public List<CreateInvoiceItemDto> InvoiceItems { get; set; } = new();
|
public List<CreateInvoiceItemDto> InvoiceItems { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public class JobDto
|
|||||||
public decimal DiscountValue { get; set; }
|
public decimal DiscountValue { get; set; }
|
||||||
public string? DiscountReason { get; set; }
|
public string? DiscountReason { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public string? SpecialInstructions { get; set; }
|
public string? SpecialInstructions { get; set; }
|
||||||
public string? InternalNotes { get; set; }
|
public string? InternalNotes { get; set; }
|
||||||
public string? Tags { get; set; }
|
public string? Tags { get; set; }
|
||||||
@@ -114,6 +115,7 @@ public class JobListDto
|
|||||||
public string? CustomerEmail { get; set; }
|
public string? CustomerEmail { get; set; }
|
||||||
public bool CustomerNotifyByEmail { get; set; } = true;
|
public bool CustomerNotifyByEmail { get; set; } = true;
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public DateTime? ScheduledDate { get; set; }
|
public DateTime? ScheduledDate { get; set; }
|
||||||
public DateTime? DueDate { get; set; }
|
public DateTime? DueDate { get; set; }
|
||||||
public decimal FinalPrice { get; set; }
|
public decimal FinalPrice { get; set; }
|
||||||
@@ -167,6 +169,7 @@ public class CreateJobDto
|
|||||||
[StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")]
|
[StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")]
|
||||||
[Display(Name = "Customer PO")]
|
[Display(Name = "Customer PO")]
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
|
|
||||||
[StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")]
|
[StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")]
|
||||||
[Display(Name = "Special Instructions")]
|
[Display(Name = "Special Instructions")]
|
||||||
@@ -252,6 +255,7 @@ public class UpdateJobDto
|
|||||||
[StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")]
|
[StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")]
|
||||||
[Display(Name = "Customer PO")]
|
[Display(Name = "Customer PO")]
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
|
|
||||||
[StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")]
|
[StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")]
|
||||||
[Display(Name = "Special Instructions")]
|
[Display(Name = "Special Instructions")]
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public class QuoteDto
|
|||||||
public string? Terms { get; set; }
|
public string? Terms { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public string? Tags { get; set; }
|
public string? Tags { get; set; }
|
||||||
|
|
||||||
// Items
|
// Items
|
||||||
@@ -234,6 +235,7 @@ public class CreateQuoteDto
|
|||||||
[Display(Name = "Customer PO Number")]
|
[Display(Name = "Customer PO Number")]
|
||||||
[StringLength(50)]
|
[StringLength(50)]
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Tags")]
|
[Display(Name = "Tags")]
|
||||||
[StringLength(500)]
|
[StringLength(500)]
|
||||||
@@ -376,6 +378,7 @@ public class UpdateQuoteDto
|
|||||||
[Display(Name = "Customer PO Number")]
|
[Display(Name = "Customer PO Number")]
|
||||||
[StringLength(50)]
|
[StringLength(50)]
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Tags")]
|
[Display(Name = "Tags")]
|
||||||
[StringLength(500)]
|
[StringLength(500)]
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class InvoiceProfile : Profile
|
|||||||
|
|
||||||
CreateMap<Invoice, InvoiceDto>()
|
CreateMap<Invoice, InvoiceDto>()
|
||||||
.ForMember(d => d.JobNumber, o => o.MapFrom(s => s.Job != null ? s.Job.JobNumber : string.Empty))
|
.ForMember(d => d.JobNumber, o => o.MapFrom(s => s.Job != null ? s.Job.JobNumber : string.Empty))
|
||||||
|
.ForMember(d => d.ProjectName, o => o.MapFrom(s => s.ProjectName ?? (s.Job != null ? s.Job.ProjectName : null)))
|
||||||
.ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer != null
|
.ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer != null
|
||||||
? (s.Customer.IsCommercial
|
? (s.Customer.IsCommercial
|
||||||
? s.Customer.CompanyName
|
? s.Customer.CompanyName
|
||||||
|
|||||||
@@ -217,6 +217,8 @@ public class PdfService : IPdfService
|
|||||||
c.Item().Text($"Job #: {invoice.JobNumber}");
|
c.Item().Text($"Job #: {invoice.JobNumber}");
|
||||||
if (!string.IsNullOrWhiteSpace(invoice.CustomerPO))
|
if (!string.IsNullOrWhiteSpace(invoice.CustomerPO))
|
||||||
c.Item().Text($"PO #: {invoice.CustomerPO}");
|
c.Item().Text($"PO #: {invoice.CustomerPO}");
|
||||||
|
if (!string.IsNullOrWhiteSpace(invoice.ProjectName))
|
||||||
|
c.Item().Text($"Project: {invoice.ProjectName}");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -609,6 +611,15 @@ public class PdfService : IPdfService
|
|||||||
row.RelativeItem().Text(quote.CustomerPO).FontSize(9);
|
row.RelativeItem().Text(quote.CustomerPO).FontSize(9);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(quote.ProjectName))
|
||||||
|
{
|
||||||
|
column.Item().Row(row =>
|
||||||
|
{
|
||||||
|
row.ConstantItem(80).Text("Project:").FontSize(9);
|
||||||
|
row.RelativeItem().Text(quote.ProjectName).FontSize(9);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class Invoice : BaseEntity
|
|||||||
public string? InternalNotes { get; set; }
|
public string? InternalNotes { get; set; }
|
||||||
public string? Terms { get; set; }
|
public string? Terms { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Early payment discount percentage (e.g., 2 means 2% discount).
|
/// Early payment discount percentage (e.g., 2 means 2% discount).
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public class Job : BaseEntity
|
|||||||
|
|
||||||
// Additional Information
|
// Additional Information
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public string? SpecialInstructions { get; set; }
|
public string? SpecialInstructions { get; set; }
|
||||||
public string? InternalNotes { get; set; } // Internal notes from quote
|
public string? InternalNotes { get; set; } // Internal notes from quote
|
||||||
public string? Tags { get; set; }
|
public string? Tags { get; set; }
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ public class Quote : BaseEntity
|
|||||||
public string? Terms { get; set; }
|
public string? Terms { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public string? CustomerPO { get; set; }
|
public string? CustomerPO { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
public string? Tags { get; set; }
|
public string? Tags { get; set; }
|
||||||
|
|
||||||
// Conversion tracking
|
// Conversion tracking
|
||||||
|
|||||||
src/PowderCoating.Infrastructure/Migrations/20260608182208_AddProjectNameToQuotesAndJobs.Designer.cs
Generated
+11165
File diff suppressed because it is too large
Load Diff
+81
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PowderCoating.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddProjectNameToQuotesAndJobs : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ProjectName",
|
||||||
|
table: "Quotes",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ProjectName",
|
||||||
|
table: "Jobs",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 8, 18, 22, 3, 652, DateTimeKind.Utc).AddTicks(7640));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 2,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 8, 18, 22, 3, 652, DateTimeKind.Utc).AddTicks(7646));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 3,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 8, 18, 22, 3, 652, DateTimeKind.Utc).AddTicks(7647));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProjectName",
|
||||||
|
table: "Quotes");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProjectName",
|
||||||
|
table: "Jobs");
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 1, 12, 29, 35, 841, DateTimeKind.Utc).AddTicks(9377));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 2,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 1, 12, 29, 35, 841, DateTimeKind.Utc).AddTicks(9381));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 3,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 1, 12, 29, 35, 841, DateTimeKind.Utc).AddTicks(9382));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+11168
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PowderCoating.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddInvoiceProjectName : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ProjectName",
|
||||||
|
table: "Invoices",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 9, 12, 48, 23, 21, DateTimeKind.Utc).AddTicks(2471));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 2,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 9, 12, 48, 23, 21, DateTimeKind.Utc).AddTicks(2477));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 3,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 9, 12, 48, 23, 21, DateTimeKind.Utc).AddTicks(2478));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProjectName",
|
||||||
|
table: "Invoices");
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 1,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 8, 18, 22, 3, 652, DateTimeKind.Utc).AddTicks(7640));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 2,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 8, 18, 22, 3, 652, DateTimeKind.Utc).AddTicks(7646));
|
||||||
|
|
||||||
|
migrationBuilder.UpdateData(
|
||||||
|
table: "PricingTiers",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: 3,
|
||||||
|
column: "CreatedAt",
|
||||||
|
value: new DateTime(2026, 6, 8, 18, 22, 3, 652, DateTimeKind.Utc).AddTicks(7647));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4269,6 +4269,9 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
b.Property<string>("PreparedById")
|
b.Property<string>("PreparedById")
|
||||||
.HasColumnType("nvarchar(450)");
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.Property<string>("ProjectName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("PublicViewToken")
|
b.Property<string>("PublicViewToken")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
@@ -4560,6 +4563,9 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
b.Property<string>("PricingBreakdownJson")
|
b.Property<string>("PricingBreakdownJson")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ProjectName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<int?>("QuoteId")
|
b.Property<int?>("QuoteId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -7053,7 +7059,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
CompanyId = 0,
|
CompanyId = 0,
|
||||||
CreatedAt = new DateTime(2026, 6, 1, 12, 29, 35, 841, DateTimeKind.Utc).AddTicks(9377),
|
CreatedAt = new DateTime(2026, 6, 9, 12, 48, 23, 21, DateTimeKind.Utc).AddTicks(2471),
|
||||||
Description = "Standard pricing for regular customers",
|
Description = "Standard pricing for regular customers",
|
||||||
DiscountPercent = 0m,
|
DiscountPercent = 0m,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
@@ -7064,7 +7070,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
CompanyId = 0,
|
CompanyId = 0,
|
||||||
CreatedAt = new DateTime(2026, 6, 1, 12, 29, 35, 841, DateTimeKind.Utc).AddTicks(9381),
|
CreatedAt = new DateTime(2026, 6, 9, 12, 48, 23, 21, DateTimeKind.Utc).AddTicks(2477),
|
||||||
Description = "5% discount for preferred customers",
|
Description = "5% discount for preferred customers",
|
||||||
DiscountPercent = 5m,
|
DiscountPercent = 5m,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
@@ -7075,7 +7081,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
{
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
CompanyId = 0,
|
CompanyId = 0,
|
||||||
CreatedAt = new DateTime(2026, 6, 1, 12, 29, 35, 841, DateTimeKind.Utc).AddTicks(9382),
|
CreatedAt = new DateTime(2026, 6, 9, 12, 48, 23, 21, DateTimeKind.Utc).AddTicks(2478),
|
||||||
Description = "10% discount for premium customers",
|
Description = "10% discount for premium customers",
|
||||||
DiscountPercent = 10m,
|
DiscountPercent = 10m,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
@@ -7385,6 +7391,9 @@ namespace PowderCoating.Infrastructure.Migrations
|
|||||||
b.Property<decimal>("ProfitPercent")
|
b.Property<decimal>("ProfitPercent")
|
||||||
.HasColumnType("decimal(18,2)");
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<string>("ProjectName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("ProspectAddress")
|
b.Property<string>("ProspectAddress")
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class NotificationService : INotificationService
|
|||||||
quote.CompanyId, NotificationType.QuoteSent, values,
|
quote.CompanyId, NotificationType.QuoteSent, values,
|
||||||
$"Your Quote {quote.QuoteNumber} from {companyName}");
|
$"Your Quote {quote.QuoteNumber} from {companyName}");
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, token: null, company, baseUrl);
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, token: null, company, baseUrl, replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error) = await _emailService.SendEmailAsync(
|
var (success, error) = await _emailService.SendEmailAsync(
|
||||||
@@ -137,7 +137,7 @@ public class NotificationService : INotificationService
|
|||||||
quote.CompanyId, NotificationType.QuoteSent, values,
|
quote.CompanyId, NotificationType.QuoteSent, values,
|
||||||
$"Your Quote {quote.QuoteNumber} from {companyName}");
|
$"Your Quote {quote.QuoteNumber} from {companyName}");
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, baseUrl);
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, baseUrl, replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
||||||
@@ -300,7 +300,7 @@ public class NotificationService : INotificationService
|
|||||||
quote.CompanyId, NotificationType.QuoteApproved, values,
|
quote.CompanyId, NotificationType.QuoteApproved, values,
|
||||||
$"Quote {quote.QuoteNumber} Approved — {companyName}");
|
$"Quote {quote.QuoteNumber} Approved — {companyName}");
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
||||||
@@ -383,7 +383,7 @@ public class NotificationService : INotificationService
|
|||||||
var (subject, htmlBody) = await GetRenderedEmailAsync(
|
var (subject, htmlBody) = await GetRenderedEmailAsync(
|
||||||
job.CompanyId, notifType, values, defaultSubject);
|
job.CompanyId, notifType, values, defaultSubject);
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
||||||
@@ -451,7 +451,7 @@ public class NotificationService : INotificationService
|
|||||||
job.CompanyId, NotificationType.JobCompleted, values,
|
job.CompanyId, NotificationType.JobCompleted, values,
|
||||||
$"Job {job.JobNumber} Complete — {companyName}");
|
$"Job {job.JobNumber} Complete — {companyName}");
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
||||||
@@ -674,7 +674,7 @@ public class NotificationService : INotificationService
|
|||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = !string.IsNullOrEmpty(paymentUrl)
|
var plainText = !string.IsNullOrEmpty(paymentUrl)
|
||||||
? StripHtml(htmlBody) + $"\r\n\r\nPay online: {paymentUrl}"
|
? StripHtml(htmlBody) + $"\r\n\r\nPay online: {paymentUrl}"
|
||||||
: StripHtml(fullHtml);
|
: StripHtml(fullHtml);
|
||||||
@@ -793,7 +793,7 @@ public class NotificationService : INotificationService
|
|||||||
invoice.CompanyId, NotificationType.PaymentReceived, values,
|
invoice.CompanyId, NotificationType.PaymentReceived, values,
|
||||||
$"Payment Received — Invoice {invoice.InvoiceNumber}");
|
$"Payment Received — Invoice {invoice.InvoiceNumber}");
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
||||||
@@ -867,7 +867,7 @@ public class NotificationService : INotificationService
|
|||||||
invoice.CompanyId, NotificationType.PaymentReminder, values,
|
invoice.CompanyId, NotificationType.PaymentReminder, values,
|
||||||
$"Payment Reminder — Invoice {invoice.InvoiceNumber} ({daysOverdue} days overdue)");
|
$"Payment Reminder — Invoice {invoice.InvoiceNumber} ({daysOverdue} days overdue)");
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, customer.UnsubscribeToken, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
var (success, error, recipientsLog) = await SendToEmailListAsync(
|
||||||
@@ -971,7 +971,7 @@ public class NotificationService : INotificationService
|
|||||||
var (subject, htmlBody) = await GetRenderedEmailAsync(
|
var (subject, htmlBody) = await GetRenderedEmailAsync(
|
||||||
quote.CompanyId, notificationType, values, defaultSubject);
|
quote.CompanyId, notificationType, values, defaultSubject);
|
||||||
|
|
||||||
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, token: null, company, await GetBaseUrlAsync());
|
var fullHtml = AppendUnsubscribeFooterHtml(htmlBody, token: null, company, await GetBaseUrlAsync(), replyToEmail);
|
||||||
var plainText = StripHtml(fullHtml);
|
var plainText = StripHtml(fullHtml);
|
||||||
|
|
||||||
var (success, error) = await _emailService.SendEmailAsync(
|
var (success, error) = await _emailService.SendEmailAsync(
|
||||||
@@ -1218,7 +1218,7 @@ public class NotificationService : INotificationService
|
|||||||
var (custSubject, custHtml) = await GetRenderedEmailAsync(
|
var (custSubject, custHtml) = await GetRenderedEmailAsync(
|
||||||
appointment.CompanyId, NotificationType.AppointmentReminder, customerValues, defaultSubject);
|
appointment.CompanyId, NotificationType.AppointmentReminder, customerValues, defaultSubject);
|
||||||
|
|
||||||
var custFullHtml = AppendUnsubscribeFooterHtml(custHtml, customer.UnsubscribeToken, company, baseUrl);
|
var custFullHtml = AppendUnsubscribeFooterHtml(custHtml, customer.UnsubscribeToken, company, baseUrl, replyToEmail);
|
||||||
var custPlainText = StripHtml(custFullHtml);
|
var custPlainText = StripHtml(custFullHtml);
|
||||||
|
|
||||||
var (custOk, custErr, custLog) = await SendToEmailListAsync(
|
var (custOk, custErr, custLog) = await SendToEmailListAsync(
|
||||||
@@ -1388,17 +1388,25 @@ public class NotificationService : INotificationService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends CAN-SPAM required footer as HTML.
|
/// Appends CAN-SPAM required footer as HTML.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string AppendUnsubscribeFooterHtml(string htmlBody, string? token, Company? company = null, string? baseUrl = null)
|
private static string AppendUnsubscribeFooterHtml(string htmlBody, string? token, Company? company = null, string? baseUrl = null, string? replyToEmail = null)
|
||||||
{
|
{
|
||||||
var hasUnsubscribeUrl = !string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(baseUrl);
|
var hasUnsubscribeUrl = !string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(baseUrl);
|
||||||
var hasAddress = company != null && !string.IsNullOrWhiteSpace(company.Address);
|
var hasAddress = company != null && !string.IsNullOrWhiteSpace(company.Address);
|
||||||
|
var hasReplyTo = !string.IsNullOrWhiteSpace(replyToEmail);
|
||||||
|
|
||||||
if (!hasUnsubscribeUrl && !hasAddress)
|
if (!hasUnsubscribeUrl && !hasAddress && !hasReplyTo)
|
||||||
return htmlBody;
|
return htmlBody;
|
||||||
|
|
||||||
var footer = "<hr style=\"border: none; border-top: 1px solid #eee; margin: 24px 0;\">" +
|
var footer = "<hr style=\"border: none; border-top: 1px solid #eee; margin: 24px 0;\">" +
|
||||||
"<p style=\"font-size: 0.8em; color: #888; margin: 0;\">";
|
"<p style=\"font-size: 0.8em; color: #888; margin: 0;\">";
|
||||||
|
|
||||||
|
if (hasReplyTo)
|
||||||
|
{
|
||||||
|
var encodedEmail = WebUtility.HtmlEncode(replyToEmail!);
|
||||||
|
footer += $"Questions? Reply to this email or contact us at <a href=\"mailto:{encodedEmail}\" style=\"color: #888;\">{encodedEmail}</a>";
|
||||||
|
if (hasAddress || hasUnsubscribeUrl) footer += "<br>";
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAddress)
|
if (hasAddress)
|
||||||
{
|
{
|
||||||
var addressLine = BuildAddressLine(company!);
|
var addressLine = BuildAddressLine(company!);
|
||||||
@@ -1535,7 +1543,15 @@ public class NotificationService : INotificationService
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(p => p.CompanyId == companyId && !p.IsDeleted);
|
.FirstOrDefaultAsync(p => p.CompanyId == companyId && !p.IsDeleted);
|
||||||
|
|
||||||
return (prefs?.EmailFromAddress, prefs?.EmailFromName);
|
var email = prefs?.EmailFromAddress;
|
||||||
|
var name = prefs?.EmailFromName;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
|
_logger.LogWarning("No Reply-To email configured for company {CompanyId} — outgoing emails will show platform sender as reply address", companyId);
|
||||||
|
else
|
||||||
|
_logger.LogDebug("Reply-To for company {CompanyId}: {ReplyToEmail}", companyId, email);
|
||||||
|
|
||||||
|
return (email, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -372,6 +372,7 @@ public class InvoicesController : Controller
|
|||||||
dto.JobId = job.Id;
|
dto.JobId = job.Id;
|
||||||
dto.CustomerId = job.CustomerId;
|
dto.CustomerId = job.CustomerId;
|
||||||
dto.CustomerPO = job.CustomerPO;
|
dto.CustomerPO = job.CustomerPO;
|
||||||
|
dto.ProjectName = job.ProjectName;
|
||||||
|
|
||||||
// Resolve catalog item revenue accounts for pre-population
|
// Resolve catalog item revenue accounts for pre-population
|
||||||
var catalogItemIds = job.JobItems
|
var catalogItemIds = job.JobItems
|
||||||
@@ -710,6 +711,7 @@ public class InvoicesController : Controller
|
|||||||
InternalNotes = dto.InternalNotes,
|
InternalNotes = dto.InternalNotes,
|
||||||
Terms = dto.Terms,
|
Terms = dto.Terms,
|
||||||
CustomerPO = dto.CustomerPO,
|
CustomerPO = dto.CustomerPO,
|
||||||
|
ProjectName = dto.ProjectName,
|
||||||
CompanyId = currentUser.CompanyId,
|
CompanyId = currentUser.CompanyId,
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTime.UtcNow,
|
||||||
CreatedBy = currentUser.Email
|
CreatedBy = currentUser.Email
|
||||||
@@ -901,6 +903,7 @@ public class InvoicesController : Controller
|
|||||||
InternalNotes = invoice.InternalNotes,
|
InternalNotes = invoice.InternalNotes,
|
||||||
Terms = invoice.Terms,
|
Terms = invoice.Terms,
|
||||||
CustomerPO = invoice.CustomerPO,
|
CustomerPO = invoice.CustomerPO,
|
||||||
|
ProjectName = invoice.ProjectName ?? invoice.Job?.ProjectName,
|
||||||
InvoiceItems = invoice.InvoiceItems
|
InvoiceItems = invoice.InvoiceItems
|
||||||
.Where(i => !i.IsDeleted)
|
.Where(i => !i.IsDeleted)
|
||||||
.OrderBy(i => i.DisplayOrder)
|
.OrderBy(i => i.DisplayOrder)
|
||||||
@@ -1036,6 +1039,7 @@ public class InvoicesController : Controller
|
|||||||
invoice.InternalNotes = dto.InternalNotes;
|
invoice.InternalNotes = dto.InternalNotes;
|
||||||
invoice.Terms = dto.Terms;
|
invoice.Terms = dto.Terms;
|
||||||
invoice.CustomerPO = dto.CustomerPO;
|
invoice.CustomerPO = dto.CustomerPO;
|
||||||
|
invoice.ProjectName = dto.ProjectName;
|
||||||
invoice.UpdatedAt = DateTime.UtcNow;
|
invoice.UpdatedAt = DateTime.UtcNow;
|
||||||
invoice.UpdatedBy = currentUser?.Email;
|
invoice.UpdatedBy = currentUser?.Email;
|
||||||
|
|
||||||
|
|||||||
@@ -1957,12 +1957,10 @@ public class QuotesController : Controller
|
|||||||
if (dto.SmsConsent)
|
if (dto.SmsConsent)
|
||||||
await _notificationService.NotifySmsConsentGrantedAsync(customer);
|
await _notificationService.NotifySmsConsentGrantedAsync(customer);
|
||||||
|
|
||||||
// Get "Converted" status (cached)
|
// Update quote to link to new customer.
|
||||||
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
// Do NOT set "Converted" status here — that status is reserved for when a job is
|
||||||
var statuses = await _lookupCache.GetQuoteStatusLookupsAsync(companyId);
|
// actually created via CreateJobFromQuote. Keeping the quote at "Approved" lets the
|
||||||
var convertedStatus = statuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Quote.Converted);
|
// user immediately click "Create Job from Quote" on the next screen.
|
||||||
|
|
||||||
// Update quote to link to new customer
|
|
||||||
quote.CustomerId = customer.Id;
|
quote.CustomerId = customer.Id;
|
||||||
|
|
||||||
// Clear prospect fields
|
// Clear prospect fields
|
||||||
@@ -1977,14 +1975,11 @@ public class QuotesController : Controller
|
|||||||
quote.ProspectSmsConsent = false;
|
quote.ProspectSmsConsent = false;
|
||||||
quote.ProspectSmsConsentedAt = null;
|
quote.ProspectSmsConsentedAt = null;
|
||||||
|
|
||||||
// Update status to converted
|
|
||||||
quote.QuoteStatusId = convertedStatus?.Id ?? quote.QuoteStatusId;
|
|
||||||
|
|
||||||
await _unitOfWork.Quotes.UpdateAsync(quote);
|
await _unitOfWork.Quotes.UpdateAsync(quote);
|
||||||
await _unitOfWork.CompleteAsync();
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
this.ToastSuccess($"Prospect/Walk-In successfully converted to customer! Quote {quote.QuoteNumber} has been updated.");
|
this.ToastSuccess($"Customer record created! You can now create a job from quote {quote.QuoteNumber}.");
|
||||||
return RedirectToAction("Details", "Customers", new { id = customer.Id });
|
return RedirectToAction(nameof(Details), new { id = dto.QuoteId });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -2958,6 +2953,7 @@ public class QuotesController : Controller
|
|||||||
Total = quote.Total
|
Total = quote.Total
|
||||||
}),
|
}),
|
||||||
CustomerPO = quote.CustomerPO,
|
CustomerPO = quote.CustomerPO,
|
||||||
|
ProjectName = quote.ProjectName,
|
||||||
InternalNotes = quote.Notes, // Copy internal notes from quote
|
InternalNotes = quote.Notes, // Copy internal notes from quote
|
||||||
IsCustomerApproved = true,
|
IsCustomerApproved = true,
|
||||||
IsRushJob = quote.IsRushJob,
|
IsRushJob = quote.IsRushJob,
|
||||||
|
|||||||
@@ -44,19 +44,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card border-0 shadow-sm">
|
<a asp-action="Index" asp-route-lowStockOnly="true" class="text-decoration-none"
|
||||||
<div class="card-body">
|
title="Click to filter list to low stock items">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
@{ var _lowStockActive = (bool)(ViewBag.LowStockOnly ?? false); }
|
||||||
<div>
|
<div class="card border-0 shadow-sm @(_lowStockActive ? "border-danger border" : "")"
|
||||||
<p class="text-muted mb-1" style="font-size: 0.875rem;">Low Stock Items</p>
|
style="cursor:pointer;transition:box-shadow .15s;">
|
||||||
<h3 class="mb-0 fw-bold @(lowStockCount > 0 ? "text-danger" : "")">@lowStockCount</h3>
|
<div class="card-body">
|
||||||
</div>
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="rounded-circle p-3" style="background: #fee2e2;">
|
<div>
|
||||||
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 1.5rem;"></i>
|
<p class="text-muted mb-1" style="font-size: 0.875rem;">
|
||||||
|
Low Stock Items
|
||||||
|
@if (lowStockCount > 0)
|
||||||
|
{
|
||||||
|
<i class="bi bi-funnel-fill ms-1 text-danger" style="font-size:.7rem;" title="Click to filter"></i>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<h3 class="mb-0 fw-bold @(lowStockCount > 0 ? "text-danger" : "")">@lowStockCount</h3>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-circle p-3" style="background: #fee2e2;">
|
||||||
|
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
@@ -102,11 +113,13 @@
|
|||||||
<div class="stat-value">@Model.TotalCount</div>
|
<div class="stat-value">@Model.TotalCount</div>
|
||||||
<div class="stat-label">Total</div>
|
<div class="stat-label">Total</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<a asp-action="Index" asp-route-lowStockOnly="true" class="text-decoration-none">
|
||||||
|
<div class="stat-item" style="cursor:pointer;">
|
||||||
<div class="stat-icon"><i class="bi bi-exclamation-triangle text-danger"></i></div>
|
<div class="stat-icon"><i class="bi bi-exclamation-triangle text-danger"></i></div>
|
||||||
<div class="stat-value @(lowStockCount > 0 ? "text-danger" : "")">@lowStockCount</div>
|
<div class="stat-value @(lowStockCount > 0 ? "text-danger" : "")">@lowStockCount</div>
|
||||||
<div class="stat-label">Low Stock</div>
|
<div class="stat-label">Low Stock</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-icon"><i class="bi bi-check-circle text-success"></i></div>
|
<div class="stat-icon"><i class="bi bi-check-circle text-success"></i></div>
|
||||||
<div class="stat-value">@activeCount</div>
|
<div class="stat-value">@activeCount</div>
|
||||||
|
|||||||
@@ -170,6 +170,12 @@
|
|||||||
<input asp-for="CustomerPO" class="form-control" placeholder="Optional" />
|
<input asp-for="CustomerPO" class="form-control" placeholder="Optional" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row g-3 mt-1">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label asp-for="ProjectName" class="form-label fw-semibold mb-0">Project Name</label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" placeholder="Optional — prints on invoice" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row g-3 mt-1">
|
<div class="row g-3 mt-1">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="d-flex align-items-center gap-1">
|
<div class="d-flex align-items-center gap-1">
|
||||||
|
|||||||
@@ -193,6 +193,13 @@
|
|||||||
<p class="mb-0">@Model.CustomerPO</p>
|
<p class="mb-0">@Model.CustomerPO</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.ProjectName))
|
||||||
|
{
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="text-muted small mb-1">Project Name</label>
|
||||||
|
<p class="mb-0">@Model.ProjectName</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.ExternalReference))
|
@if (!string.IsNullOrWhiteSpace(Model.ExternalReference))
|
||||||
{
|
{
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|||||||
@@ -62,6 +62,12 @@
|
|||||||
<input asp-for="CustomerPO" class="form-control" placeholder="Optional" />
|
<input asp-for="CustomerPO" class="form-control" placeholder="Optional" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row g-3 mt-1">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label asp-for="ProjectName" class="form-label fw-semibold">Project Name</label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" placeholder="Optional — prints on invoice" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row g-3 mt-1">
|
<div class="row g-3 mt-1">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label asp-for="Terms" class="form-label fw-semibold">Payment Terms</label>
|
<label asp-for="Terms" class="form-label fw-semibold">Payment Terms</label>
|
||||||
|
|||||||
@@ -124,6 +124,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<input asp-for="CustomerPO" class="form-control" placeholder="Enter PO number" />
|
<input asp-for="CustomerPO" class="form-control" placeholder="Enter PO number" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="ProjectName" class="form-label">Project Name</label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" placeholder="e.g. Kitchen Remodel, Fleet Vehicle #3…" />
|
||||||
|
</div>
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div class="d-flex align-items-center gap-1">
|
<div class="d-flex align-items-center gap-1">
|
||||||
<label asp-for="SpecialInstructions" class="form-label mb-0">Special Instructions</label>
|
<label asp-for="SpecialInstructions" class="form-label mb-0">Special Instructions</label>
|
||||||
|
|||||||
@@ -172,6 +172,13 @@
|
|||||||
<label class="text-muted small mb-1">Customer PO</label>
|
<label class="text-muted small mb-1">Customer PO</label>
|
||||||
<p class="mb-0">@(Model.CustomerPO ?? "Not provided")</p>
|
<p class="mb-0">@(Model.CustomerPO ?? "Not provided")</p>
|
||||||
</div>
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(Model.ProjectName))
|
||||||
|
{
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="text-muted small mb-1">Project</label>
|
||||||
|
<p class="mb-0">@Model.ProjectName</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label class="text-muted small mb-1">Description</label>
|
<label class="text-muted small mb-1">Description</label>
|
||||||
<p class="mb-0">@Model.Description</p>
|
<p class="mb-0">@Model.Description</p>
|
||||||
|
|||||||
@@ -101,6 +101,10 @@
|
|||||||
<label asp-for="CustomerPO" class="form-label">Customer PO</label>
|
<label asp-for="CustomerPO" class="form-label">Customer PO</label>
|
||||||
<input asp-for="CustomerPO" class="form-control" placeholder="Enter PO number" />
|
<input asp-for="CustomerPO" class="form-control" placeholder="Enter PO number" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="ProjectName" class="form-label">Project Name</label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" placeholder="e.g. Kitchen Remodel, Fleet Vehicle #3…" />
|
||||||
|
</div>
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<label asp-for="SpecialInstructions" class="form-label">Special Instructions</label>
|
<label asp-for="SpecialInstructions" class="form-label">Special Instructions</label>
|
||||||
<textarea asp-for="SpecialInstructions" class="form-control" rows="3" placeholder="Any special instructions"></textarea>
|
<textarea asp-for="SpecialInstructions" class="form-control" rows="3" placeholder="Any special instructions"></textarea>
|
||||||
|
|||||||
@@ -357,6 +357,13 @@
|
|||||||
<div class="info-value">@Model.CustomerPO</div>
|
<div class="info-value">@Model.CustomerPO</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.ProjectName))
|
||||||
|
{
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-label">Project</div>
|
||||||
|
<div class="info-value">@Model.ProjectName</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
|
|||||||
@@ -187,6 +187,12 @@
|
|||||||
<input asp-for="CustomerPO" class="form-control" />
|
<input asp-for="CustomerPO" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="ProjectName" class="form-label"></label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" placeholder="e.g. Kitchen Remodel, Fleet Vehicle #3…" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label asp-for="Notes" class="form-label"></label>
|
<label asp-for="Notes" class="form-label"></label>
|
||||||
|
|||||||
@@ -183,6 +183,10 @@
|
|||||||
{
|
{
|
||||||
<p><strong>Customer PO:</strong> @Model.CustomerPO</p>
|
<p><strong>Customer PO:</strong> @Model.CustomerPO</p>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.ProjectName))
|
||||||
|
{
|
||||||
|
<p><strong>Project:</strong> @Model.ProjectName</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(Model.Description))
|
@if (!string.IsNullOrEmpty(Model.Description))
|
||||||
|
|||||||
@@ -150,6 +150,12 @@
|
|||||||
<input asp-for="CustomerPO" class="form-control" />
|
<input asp-for="CustomerPO" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label asp-for="ProjectName" class="form-label"></label>
|
||||||
|
<input asp-for="ProjectName" class="form-control" placeholder="e.g. Kitchen Remodel, Fleet Vehicle #3…" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label asp-for="Notes" class="form-label"></label>
|
<label asp-for="Notes" class="form-label"></label>
|
||||||
|
|||||||
Reference in New Issue
Block a user