Patch export/import for missing fields; add CustomerContacts export
- DataExportController + AccountDataExportController: add ProjectName to Jobs, Quotes, Invoices (XLSX + CSV); add LeadSource + ShipTo fields to Customers (XLSX + CSV); add CustomerContacts sheet/CSV (new) - Both export views: add Customer Contacts checkbox (checked by default) - CustomerImportDto: add LeadSource + ShipTo* fields - JobImportDto: add ProjectName - QuoteImportDto: add ProjectName - InvoiceImportDto: add Project Name (dual-name alias for round-trip) - CsvImportService: wire all new import fields to entity creation; also patch invoice update path for ProjectName - Add scripts/purge_imported_data.sql (dry-run T-SQL for data cleanup) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -115,10 +115,11 @@ public class DataExportController : Controller
|
||||
{
|
||||
switch (sheet)
|
||||
{
|
||||
case "Customers": await AddCustomersSheet(package, companyId, headerColor); break;
|
||||
case "Jobs": await AddJobsSheet(package, companyId, headerColor); break;
|
||||
case "Quotes": await AddQuotesSheet(package, companyId, headerColor); break;
|
||||
case "Invoices": await AddInvoicesSheet(package, companyId, headerColor); break;
|
||||
case "Customers": await AddCustomersSheet(package, companyId, headerColor); break;
|
||||
case "CustomerContacts": await AddCustomerContactsSheet(package, companyId, headerColor); break;
|
||||
case "Jobs": await AddJobsSheet(package, companyId, headerColor); break;
|
||||
case "Quotes": await AddQuotesSheet(package, companyId, headerColor); break;
|
||||
case "Invoices": await AddInvoicesSheet(package, companyId, headerColor); break;
|
||||
case "Inventory": await AddInventorySheet(package, companyId, headerColor); break;
|
||||
case "Equipment": await AddEquipmentSheet(package, companyId, headerColor); break;
|
||||
case "Vendors": await AddVendorsSheet(package, companyId, headerColor); break;
|
||||
@@ -164,10 +165,11 @@ public class DataExportController : Controller
|
||||
{
|
||||
switch (sheet)
|
||||
{
|
||||
case "Customers": WriteCsvEntry(zip, "Customers.csv", await BuildCustomersCsv(companyId)); break;
|
||||
case "Jobs": WriteCsvEntry(zip, "Jobs.csv", await BuildJobsCsv(companyId)); break;
|
||||
case "Quotes": WriteCsvEntry(zip, "Quotes.csv", await BuildQuotesCsv(companyId)); break;
|
||||
case "Invoices": WriteCsvEntry(zip, "Invoices.csv", await BuildInvoicesCsv(companyId)); break;
|
||||
case "Customers": WriteCsvEntry(zip, "Customers.csv", await BuildCustomersCsv(companyId)); break;
|
||||
case "CustomerContacts": WriteCsvEntry(zip, "CustomerContacts.csv", await BuildCustomerContactsCsv(companyId)); break;
|
||||
case "Jobs": WriteCsvEntry(zip, "Jobs.csv", await BuildJobsCsv(companyId)); break;
|
||||
case "Quotes": WriteCsvEntry(zip, "Quotes.csv", await BuildQuotesCsv(companyId)); break;
|
||||
case "Invoices": WriteCsvEntry(zip, "Invoices.csv", await BuildInvoicesCsv(companyId)); break;
|
||||
case "Inventory": WriteCsvEntry(zip, "Inventory.csv", await BuildInventoryCsv(companyId)); break;
|
||||
case "Equipment": WriteCsvEntry(zip, "Equipment.csv", await BuildEquipmentCsv(companyId)); break;
|
||||
case "Vendors": WriteCsvEntry(zip, "Vendors.csv", await BuildVendorsCsv(companyId)); break;
|
||||
@@ -240,7 +242,9 @@ public class DataExportController : Controller
|
||||
var ws = pkg.Workbook.Worksheets.Add("Customers");
|
||||
var headers = new[] { "ID", "Company Name", "First Name", "Last Name", "Email", "Phone",
|
||||
"Commercial", "City", "State", "Active", "Credit Limit",
|
||||
"Current Balance", "Created At" };
|
||||
"Current Balance", "Lead Source",
|
||||
"Ship-To Address", "Ship-To City", "Ship-To State", "Ship-To Zip", "Ship-To Country",
|
||||
"Created At" };
|
||||
WriteHeader(ws, headers, hdr);
|
||||
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
@@ -259,7 +263,13 @@ public class DataExportController : Controller
|
||||
ws.Cells[r, 10].Value = c.IsActive ? "Yes" : "No";
|
||||
ws.Cells[r, 11].Value = c.CreditLimit;
|
||||
ws.Cells[r, 12].Value = c.CurrentBalance;
|
||||
ws.Cells[r, 13].Value = c.CreatedAt.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 13].Value = c.LeadSource;
|
||||
ws.Cells[r, 14].Value = c.ShipToAddress;
|
||||
ws.Cells[r, 15].Value = c.ShipToCity;
|
||||
ws.Cells[r, 16].Value = c.ShipToState;
|
||||
ws.Cells[r, 17].Value = c.ShipToZipCode;
|
||||
ws.Cells[r, 18].Value = c.ShipToCountry;
|
||||
ws.Cells[r, 19].Value = c.CreatedAt.ToString("yyyy-MM-dd");
|
||||
}
|
||||
AutoFit(ws, headers.Length);
|
||||
}
|
||||
@@ -282,22 +292,23 @@ public class DataExportController : Controller
|
||||
|
||||
var ws = pkg.Workbook.Worksheets.Add("Jobs");
|
||||
var headers = new[] { "ID", "Job Number", "Customer", "Status", "Priority",
|
||||
"Description", "Due Date", "Final Price", "Created At" };
|
||||
"Description", "Project Name", "Due Date", "Final Price", "Created At" };
|
||||
WriteHeader(ws, headers, hdr);
|
||||
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
var r = i + 2;
|
||||
var j = data[i];
|
||||
ws.Cells[r, 1].Value = j.Id;
|
||||
ws.Cells[r, 2].Value = j.JobNumber;
|
||||
ws.Cells[r, 3].Value = j.Customer?.CompanyName ?? $"{j.Customer?.ContactFirstName} {j.Customer?.ContactLastName}".Trim();
|
||||
ws.Cells[r, 4].Value = j.JobStatus?.DisplayName ?? j.JobStatusId.ToString();
|
||||
ws.Cells[r, 5].Value = j.JobPriority?.DisplayName ?? j.JobPriorityId.ToString();
|
||||
ws.Cells[r, 6].Value = j.Description;
|
||||
ws.Cells[r, 7].Value = j.DueDate?.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 8].Value = j.FinalPrice;
|
||||
ws.Cells[r, 9].Value = j.CreatedAt.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 1].Value = j.Id;
|
||||
ws.Cells[r, 2].Value = j.JobNumber;
|
||||
ws.Cells[r, 3].Value = j.Customer?.CompanyName ?? $"{j.Customer?.ContactFirstName} {j.Customer?.ContactLastName}".Trim();
|
||||
ws.Cells[r, 4].Value = j.JobStatus?.DisplayName ?? j.JobStatusId.ToString();
|
||||
ws.Cells[r, 5].Value = j.JobPriority?.DisplayName ?? j.JobPriorityId.ToString();
|
||||
ws.Cells[r, 6].Value = j.Description;
|
||||
ws.Cells[r, 7].Value = j.ProjectName;
|
||||
ws.Cells[r, 8].Value = j.DueDate?.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 9].Value = j.FinalPrice;
|
||||
ws.Cells[r, 10].Value = j.CreatedAt.ToString("yyyy-MM-dd");
|
||||
}
|
||||
AutoFit(ws, headers.Length);
|
||||
}
|
||||
@@ -318,22 +329,23 @@ public class DataExportController : Controller
|
||||
|
||||
var ws = pkg.Workbook.Worksheets.Add("Quotes");
|
||||
var headers = new[] { "ID", "Quote Number", "Customer / Prospect", "Status",
|
||||
"Quote Date", "Expiration Date", "Subtotal", "Tax", "Total" };
|
||||
"Quote Date", "Expiration Date", "Project Name", "Subtotal", "Tax", "Total" };
|
||||
WriteHeader(ws, headers, hdr);
|
||||
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
var r = i + 2;
|
||||
var q = data[i];
|
||||
ws.Cells[r, 1].Value = q.Id;
|
||||
ws.Cells[r, 2].Value = q.QuoteNumber;
|
||||
ws.Cells[r, 3].Value = string.IsNullOrEmpty(q.ProspectCompanyName) ? $"Customer #{q.CustomerId}" : q.ProspectCompanyName;
|
||||
ws.Cells[r, 4].Value = q.QuoteStatus?.DisplayName ?? q.QuoteStatusId.ToString();
|
||||
ws.Cells[r, 5].Value = q.QuoteDate.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 6].Value = q.ExpirationDate?.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 7].Value = q.SubTotal;
|
||||
ws.Cells[r, 8].Value = q.TaxAmount;
|
||||
ws.Cells[r, 9].Value = q.Total;
|
||||
ws.Cells[r, 1].Value = q.Id;
|
||||
ws.Cells[r, 2].Value = q.QuoteNumber;
|
||||
ws.Cells[r, 3].Value = string.IsNullOrEmpty(q.ProspectCompanyName) ? $"Customer #{q.CustomerId}" : q.ProspectCompanyName;
|
||||
ws.Cells[r, 4].Value = q.QuoteStatus?.DisplayName ?? q.QuoteStatusId.ToString();
|
||||
ws.Cells[r, 5].Value = q.QuoteDate.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 6].Value = q.ExpirationDate?.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 7].Value = q.ProjectName;
|
||||
ws.Cells[r, 8].Value = q.SubTotal;
|
||||
ws.Cells[r, 9].Value = q.TaxAmount;
|
||||
ws.Cells[r, 10].Value = q.Total;
|
||||
}
|
||||
AutoFit(ws, headers.Length);
|
||||
}
|
||||
@@ -456,7 +468,7 @@ public class DataExportController : Controller
|
||||
|
||||
var ws = pkg.Workbook.Worksheets.Add("Invoices");
|
||||
var headers = new[] { "ID", "Invoice #", "Customer", "Status", "Invoice Date",
|
||||
"Due Date", "Subtotal", "Tax", "Total", "Amount Paid", "Balance Due" };
|
||||
"Due Date", "Project Name", "Subtotal", "Tax", "Total", "Amount Paid", "Balance Due" };
|
||||
WriteHeader(ws, headers, hdr);
|
||||
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
@@ -472,11 +484,12 @@ public class DataExportController : Controller
|
||||
ws.Cells[r, 4].Value = inv.Status.ToString();
|
||||
ws.Cells[r, 5].Value = inv.InvoiceDate.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 6].Value = inv.DueDate?.ToString("yyyy-MM-dd");
|
||||
ws.Cells[r, 7].Value = inv.SubTotal;
|
||||
ws.Cells[r, 8].Value = inv.TaxAmount;
|
||||
ws.Cells[r, 9].Value = inv.Total;
|
||||
ws.Cells[r, 10].Value = inv.AmountPaid;
|
||||
ws.Cells[r, 11].Value = inv.BalanceDue;
|
||||
ws.Cells[r, 7].Value = inv.ProjectName;
|
||||
ws.Cells[r, 8].Value = inv.SubTotal;
|
||||
ws.Cells[r, 9].Value = inv.TaxAmount;
|
||||
ws.Cells[r, 10].Value = inv.Total;
|
||||
ws.Cells[r, 11].Value = inv.AmountPaid;
|
||||
ws.Cells[r, 12].Value = inv.BalanceDue;
|
||||
}
|
||||
AutoFit(ws, headers.Length);
|
||||
}
|
||||
@@ -530,11 +543,11 @@ public class DataExportController : Controller
|
||||
.Include(c => c.PricingTier)
|
||||
.Where(c => c.CompanyId == companyId && !c.IsDeleted).OrderBy(c => c.CompanyName).ToListAsync();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("CompanyName,ContactFirstName,ContactLastName,Email,Phone,MobilePhone,Address,City,State,ZipCode,Country,CustomerType,PricingTierCode,CreditLimit,PaymentTerms,TaxExempt,TaxId,IsActive,Notes");
|
||||
sb.AppendLine("CompanyName,ContactFirstName,ContactLastName,Email,Phone,MobilePhone,Address,City,State,ZipCode,Country,CustomerType,PricingTierCode,CreditLimit,PaymentTerms,TaxExempt,TaxId,IsActive,Notes,LeadSource,ShipToAddress,ShipToCity,ShipToState,ShipToZipCode,ShipToCountry");
|
||||
foreach (var c in data)
|
||||
{
|
||||
var customerType = c.IsCommercial ? "Commercial" : "Non-Commercial";
|
||||
sb.AppendLine($"{CsvEscape(c.CompanyName)},{CsvEscape(c.ContactFirstName)},{CsvEscape(c.ContactLastName)},{CsvEscape(c.Email)},{CsvEscape(c.Phone)},{CsvEscape(c.MobilePhone)},{CsvEscape(c.Address)},{CsvEscape(c.City)},{CsvEscape(c.State)},{CsvEscape(c.ZipCode)},{CsvEscape(c.Country)},{customerType},{CsvEscape(c.PricingTier?.TierName)},{c.CreditLimit},{CsvEscape(c.PaymentTerms)},{c.IsTaxExempt.ToString().ToLower()},{CsvEscape(c.TaxId)},{c.IsActive.ToString().ToLower()},{CsvEscape(c.GeneralNotes)}");
|
||||
sb.AppendLine($"{CsvEscape(c.CompanyName)},{CsvEscape(c.ContactFirstName)},{CsvEscape(c.ContactLastName)},{CsvEscape(c.Email)},{CsvEscape(c.Phone)},{CsvEscape(c.MobilePhone)},{CsvEscape(c.Address)},{CsvEscape(c.City)},{CsvEscape(c.State)},{CsvEscape(c.ZipCode)},{CsvEscape(c.Country)},{customerType},{CsvEscape(c.PricingTier?.TierName)},{c.CreditLimit},{CsvEscape(c.PaymentTerms)},{c.IsTaxExempt.ToString().ToLower()},{CsvEscape(c.TaxId)},{c.IsActive.ToString().ToLower()},{CsvEscape(c.GeneralNotes)},{CsvEscape(c.LeadSource)},{CsvEscape(c.ShipToAddress)},{CsvEscape(c.ShipToCity)},{CsvEscape(c.ShipToState)},{CsvEscape(c.ShipToZipCode)},{CsvEscape(c.ShipToCountry)}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -552,13 +565,13 @@ public class DataExportController : Controller
|
||||
.Include(j => j.Customer).Include(j => j.JobStatus).Include(j => j.JobPriority)
|
||||
.OrderByDescending(j => j.CreatedAt).ToListAsync();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("JobNumber,CustomerEmail,CustomerName,Status,Priority,ScheduledDate,DueDate,FinalPrice,CustomerPO,SpecialInstructions,Notes");
|
||||
sb.AppendLine("JobNumber,CustomerEmail,CustomerName,Status,Priority,ScheduledDate,DueDate,ProjectName,FinalPrice,CustomerPO,SpecialInstructions,Notes");
|
||||
foreach (var j in data)
|
||||
{
|
||||
var customerName = !string.IsNullOrWhiteSpace(j.Customer?.CompanyName)
|
||||
? j.Customer.CompanyName
|
||||
: $"{j.Customer?.ContactFirstName} {j.Customer?.ContactLastName}".Trim();
|
||||
sb.AppendLine($"{CsvEscape(j.JobNumber)},{CsvEscape(j.Customer?.Email)},{CsvEscape(customerName)},{CsvEscape(j.JobStatus?.DisplayName)},{CsvEscape(j.JobPriority?.DisplayName)},{j.ScheduledDate?.ToString("yyyy-MM-dd")},{j.DueDate?.ToString("yyyy-MM-dd")},{j.FinalPrice},{CsvEscape(j.CustomerPO)},{CsvEscape(j.SpecialInstructions)},{CsvEscape(j.InternalNotes)}");
|
||||
sb.AppendLine($"{CsvEscape(j.JobNumber)},{CsvEscape(j.Customer?.Email)},{CsvEscape(customerName)},{CsvEscape(j.JobStatus?.DisplayName)},{CsvEscape(j.JobPriority?.DisplayName)},{j.ScheduledDate?.ToString("yyyy-MM-dd")},{j.DueDate?.ToString("yyyy-MM-dd")},{CsvEscape(j.ProjectName)},{j.FinalPrice},{CsvEscape(j.CustomerPO)},{CsvEscape(j.SpecialInstructions)},{CsvEscape(j.InternalNotes)}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -574,13 +587,13 @@ public class DataExportController : Controller
|
||||
.Where(q => q.CompanyId == companyId && !q.IsDeleted)
|
||||
.Include(q => q.Customer).Include(q => q.QuoteStatus).OrderByDescending(q => q.QuoteDate).ToListAsync();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("QuoteNumber,CustomerEmail,CustomerName,ProspectCompany,ProspectContact,ProspectEmail,ProspectPhone,Status,QuoteDate,ExpirationDate,Subtotal,TaxAmount,Total,Notes,TermsAndConditions");
|
||||
sb.AppendLine("QuoteNumber,CustomerEmail,CustomerName,ProspectCompany,ProspectContact,ProspectEmail,ProspectPhone,Status,QuoteDate,ExpirationDate,ProjectName,Subtotal,TaxAmount,Total,Notes,TermsAndConditions");
|
||||
foreach (var q in data)
|
||||
{
|
||||
var customerName = !string.IsNullOrWhiteSpace(q.Customer?.CompanyName)
|
||||
? q.Customer.CompanyName
|
||||
: $"{q.Customer?.ContactFirstName} {q.Customer?.ContactLastName}".Trim();
|
||||
sb.AppendLine($"{CsvEscape(q.QuoteNumber)},{CsvEscape(q.Customer?.Email)},{CsvEscape(customerName)},{CsvEscape(q.ProspectCompanyName)},{CsvEscape(q.ProspectContactName)},{CsvEscape(q.ProspectEmail)},{CsvEscape(q.ProspectPhone)},{CsvEscape(q.QuoteStatus?.DisplayName)},{q.QuoteDate:yyyy-MM-dd},{q.ExpirationDate?.ToString("yyyy-MM-dd")},{q.SubTotal},{q.TaxAmount},{q.Total},{CsvEscape(q.Notes)},{CsvEscape(q.Terms)}");
|
||||
sb.AppendLine($"{CsvEscape(q.QuoteNumber)},{CsvEscape(q.Customer?.Email)},{CsvEscape(customerName)},{CsvEscape(q.ProspectCompanyName)},{CsvEscape(q.ProspectContactName)},{CsvEscape(q.ProspectEmail)},{CsvEscape(q.ProspectPhone)},{CsvEscape(q.QuoteStatus?.DisplayName)},{q.QuoteDate:yyyy-MM-dd},{q.ExpirationDate?.ToString("yyyy-MM-dd")},{CsvEscape(q.ProjectName)},{q.SubTotal},{q.TaxAmount},{q.Total},{CsvEscape(q.Notes)},{CsvEscape(q.Terms)}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -596,17 +609,68 @@ public class DataExportController : Controller
|
||||
.Where(i => i.CompanyId == companyId && !i.IsDeleted)
|
||||
.Include(i => i.Customer).OrderByDescending(i => i.InvoiceDate).ToListAsync();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("ID,Invoice #,Customer,Status,Invoice Date,Due Date,Subtotal,Tax,Total,Amount Paid,Balance Due");
|
||||
sb.AppendLine("ID,Invoice #,Customer,Status,Invoice Date,Due Date,Project Name,Subtotal,Tax,Total,Amount Paid,Balance Due");
|
||||
foreach (var inv in data)
|
||||
{
|
||||
var cust = inv.Customer != null
|
||||
? (inv.Customer.CompanyName ?? $"{inv.Customer.ContactFirstName} {inv.Customer.ContactLastName}".Trim())
|
||||
: $"Customer #{inv.CustomerId}";
|
||||
sb.AppendLine($"{inv.Id},{CsvEscape(inv.InvoiceNumber)},{CsvEscape(cust)},{inv.Status},{inv.InvoiceDate:yyyy-MM-dd},{inv.DueDate?.ToString("yyyy-MM-dd")},{inv.SubTotal},{inv.TaxAmount},{inv.Total},{inv.AmountPaid},{inv.BalanceDue}");
|
||||
sb.AppendLine($"{inv.Id},{CsvEscape(inv.InvoiceNumber)},{CsvEscape(cust)},{inv.Status},{inv.InvoiceDate:yyyy-MM-dd},{inv.DueDate?.ToString("yyyy-MM-dd")},{CsvEscape(inv.ProjectName)},{inv.SubTotal},{inv.TaxAmount},{inv.Total},{inv.AmountPaid},{inv.BalanceDue}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a CustomerContacts worksheet: one row per additional contact linked to the company's customers.
|
||||
/// CustomerEmail is the join key used by the importer to re-link contacts to their parent customer.
|
||||
/// </summary>
|
||||
private async Task AddCustomerContactsSheet(ExcelPackage pkg, int companyId, Color hdr)
|
||||
{
|
||||
var data = await _db.CustomerContacts.AsNoTracking().IgnoreQueryFilters()
|
||||
.Include(cc => cc.Customer)
|
||||
.Where(cc => cc.CompanyId == companyId && !cc.IsDeleted)
|
||||
.OrderBy(cc => cc.Customer!.CompanyName).ThenBy(cc => cc.LastName).ThenBy(cc => cc.FirstName)
|
||||
.ToListAsync();
|
||||
|
||||
var ws = pkg.Workbook.Worksheets.Add("CustomerContacts");
|
||||
var headers = new[] { "CustomerEmail", "FirstName", "LastName", "Title", "ContactRole", "Email", "Phone", "MobilePhone", "Notes" };
|
||||
WriteHeader(ws, headers, hdr);
|
||||
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
var r = i + 2;
|
||||
var cc = data[i];
|
||||
ws.Cells[r, 1].Value = cc.Customer?.Email;
|
||||
ws.Cells[r, 2].Value = cc.FirstName;
|
||||
ws.Cells[r, 3].Value = cc.LastName;
|
||||
ws.Cells[r, 4].Value = cc.Title;
|
||||
ws.Cells[r, 5].Value = cc.ContactRole;
|
||||
ws.Cells[r, 6].Value = cc.Email;
|
||||
ws.Cells[r, 7].Value = cc.Phone;
|
||||
ws.Cells[r, 8].Value = cc.MobilePhone;
|
||||
ws.Cells[r, 9].Value = cc.Notes;
|
||||
}
|
||||
AutoFit(ws, headers.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the customer contacts CSV string for the specified company.
|
||||
/// CustomerEmail is the join key used by the importer to re-link contacts to their parent customer.
|
||||
/// </summary>
|
||||
private async Task<string> BuildCustomerContactsCsv(int companyId)
|
||||
{
|
||||
var data = await _db.CustomerContacts.AsNoTracking().IgnoreQueryFilters()
|
||||
.Include(cc => cc.Customer)
|
||||
.Where(cc => cc.CompanyId == companyId && !cc.IsDeleted)
|
||||
.OrderBy(cc => cc.Customer!.CompanyName).ThenBy(cc => cc.LastName).ThenBy(cc => cc.FirstName)
|
||||
.ToListAsync();
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("CustomerEmail,FirstName,LastName,Title,ContactRole,Email,Phone,MobilePhone,Notes");
|
||||
foreach (var cc in data)
|
||||
sb.AppendLine($"{CsvEscape(cc.Customer?.Email)},{CsvEscape(cc.FirstName)},{CsvEscape(cc.LastName)},{CsvEscape(cc.Title)},{CsvEscape(cc.ContactRole)},{CsvEscape(cc.Email)},{CsvEscape(cc.Phone)},{CsvEscape(cc.MobilePhone)},{CsvEscape(cc.Notes)}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the inventory CSV string for the specified company, ordered alphabetically by name.
|
||||
/// Column names match <see cref="InventoryItemImportDto"/> exactly so the file can be re-imported.
|
||||
|
||||
Reference in New Issue
Block a user