生成销售表

This commit is contained in:
earo.lau 2025-09-07 15:41:31 +08:00
parent 24831a0661
commit db87d00ead
3 changed files with 298 additions and 35 deletions

View File

@ -34,7 +34,7 @@
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
<InstallUrl /> <InstallUrl />
<TargetCulture>zh-chs</TargetCulture> <TargetCulture>zh-chs</TargetCulture>
<ApplicationVersion>1.0.0.1</ApplicationVersion> <ApplicationVersion>1.0.0.2</ApplicationVersion>
<AutoIncrementApplicationRevision>true</AutoIncrementApplicationRevision> <AutoIncrementApplicationRevision>true</AutoIncrementApplicationRevision>
<UpdateEnabled>true</UpdateEnabled> <UpdateEnabled>true</UpdateEnabled>
<UpdateInterval>7</UpdateInterval> <UpdateInterval>7</UpdateInterval>

View File

@ -10,6 +10,7 @@ namespace KellyReport_D.Model
internal class ImportContext internal class ImportContext
{ {
public Workbook ImportWorkbook { get; set; } public Workbook ImportWorkbook { get; set; }
public List<Worksheet> SalesWorksheets { get; set; }
public Microsoft.Office.Interop.Excel.Application Application { get; set; } public Microsoft.Office.Interop.Excel.Application Application { get; set; }
public IList<ClientContact> ClientContactList { get; set; } public IList<ClientContact> ClientContactList { get; set; }
public IList<SalesDetail> SalesDetails { get; set; } public IList<SalesDetail> SalesDetails { get; set; }
@ -19,6 +20,7 @@ namespace KellyReport_D.Model
public string ClientName { get; set; } public string ClientName { get; set; }
public string ProductNameEN { get; set; } public string ProductNameEN { get; set; }
public decimal TotalAmount { get; set; } public decimal TotalAmount { get; set; }
public decimal Quantity { get; set; }
public List<SalesDetail> Details { get; set; } public List<SalesDetail> Details { get; set; }
} }
@ -28,18 +30,18 @@ namespace KellyReport_D.Model
{ {
SalesName = grouped.Key, SalesName = grouped.Key,
TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0), TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0),
Quantity = grouped.Sum(x => x.Quantity ?? 0),
Details = grouped.ToList() Details = grouped.ToList()
}); });
public IEnumerable<GroupedSalesDetailModel> GetGroupedSalesDetailByClientName(IList<SalesDetail> salesDetails) public IEnumerable<GroupedSalesDetailModel> GetGroupedSalesDetailByClientName(IList<SalesDetail> salesDetails)
{ {
return salesDetails return salesDetails
.GroupBy(x => new { SalesName = x.SalesName.ToUpper(), x.ClientName, x.ProductNameEN }) .GroupBy(x => new { SalesName = x.SalesName.ToUpper(), x.ClientName })
.Select(grouped => new GroupedSalesDetailModel .Select(grouped => new GroupedSalesDetailModel
{ {
SalesName = grouped.Key.SalesName, SalesName = grouped.Key.SalesName,
ClientName = grouped.Key.ClientName, ClientName = grouped.Key.ClientName,
ProductNameEN = grouped.Key.ProductNameEN,
TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0), TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0),
Details = grouped.ToList() Details = grouped.ToList()
}); });

View File

@ -7,7 +7,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using KellyReport_D.Model; using KellyReport_D.Model;
using System.Windows.Input;
namespace KellyReport_D.Utils namespace KellyReport_D.Utils
{ {
@ -18,11 +17,12 @@ namespace KellyReport_D.Utils
var importContext = new ImportContext() var importContext = new ImportContext()
{ {
Application = Globals.ThisAddIn.Application, Application = Globals.ThisAddIn.Application,
ImportWorkbook = workbook ImportWorkbook = workbook,
SalesWorksheets = new List<Worksheet>(),
}; };
// 获取「客户资料」工作表 // 获取「客户资料」工作表
readClientContact(importContext); ReadClientContact(importContext);
// 读取「销售明细」工作表 // 读取「销售明细」工作表
ReadSalesDetails(importContext); ReadSalesDetails(importContext);
foreach (var userGroupedSalesDetail in importContext.GroupedSalesDetailBySalesName) foreach (var userGroupedSalesDetail in importContext.GroupedSalesDetailBySalesName)
@ -30,7 +30,146 @@ namespace KellyReport_D.Utils
var userSheet = GenUserSalesDetailSheet(importContext, userGroupedSalesDetail); var userSheet = GenUserSalesDetailSheet(importContext, userGroupedSalesDetail);
PrepareTotalHeader(userSheet); PrepareTotalHeader(userSheet);
WriteSalesDetailsData(importContext, userGroupedSalesDetail, userSheet); WriteSalesDetailsData(importContext, userGroupedSalesDetail, userSheet);
importContext.SalesWorksheets.Add(userSheet);
} }
GenSalesTotal(importContext);
}
private static void GenSalesTotal(ImportContext context)
{
var app = context.Application;
var totalWorksheet = CreateSheetIfNotExisted(app, "JQ Total(Total)");
PrepareTotalHeader(totalWorksheet);
int totalRowIndex = 4;
foreach (var userSheet in context.SalesWorksheets)
{
Range usedRange = userSheet.UsedRange;
int rowIndexStart = 4;
int rowIndexEnd = usedRange.Rows.Count;
int rowCount = rowIndexEnd - rowIndexStart + 1;
if (rowCount > 1)
{
Range sourceRange = userSheet.Range[
userSheet.Cells[rowIndexStart, 1],
userSheet.Cells[rowIndexEnd, usedRange.Columns.Count]
];
int totalRowIndexEnd = totalRowIndex + rowCount;
Range targetRange = totalWorksheet.Range[
totalWorksheet.Cells[totalRowIndex, 1],
totalWorksheet.Cells[totalRowIndexEnd, usedRange.Columns.Count]
];
sourceRange.Copy(targetRange);
totalRowIndex = totalRowIndexEnd;
}
}
totalWorksheet.Cells[totalRowIndex, 1].Value = "總結";
var sumRange = totalWorksheet.Range[
totalWorksheet.Cells[totalRowIndex, 1],
totalWorksheet.Cells[totalRowIndex + 4, 5]
];
sumRange.Merge();
int sumColStart = 13; // L列是第12列
totalWorksheet.Cells[totalRowIndex, 11].Value2 = "總銷量及金額(未稅)";
for (int i = 0; i < 12; i++)
{
int quantityColIndex = sumColStart + i * 3 + 1;
string quantityColLetter = GetColumnLetter(quantityColIndex);
totalWorksheet.Cells[totalRowIndex, quantityColIndex].formula = $"=(SUM({quantityColLetter}4:{quantityColLetter}{totalRowIndex - 1})/2)";
int amountColIndex = sumColStart + i * 3 + 2;
string amountColLetter = GetColumnLetter(amountColIndex);
totalWorksheet.Cells[totalRowIndex, amountColIndex].formula = $"=((SUM({amountColLetter}4:{amountColLetter}{totalRowIndex - 1})/2) - {amountColLetter}{totalRowIndex + 2})/1.13";
}
string totalQuantityColLetter = GetColumnLetter(49);
string totalAmountColLetter = GetColumnLetter(50);
totalWorksheet.Cells[totalRowIndex, 49].formula = $"=(SUM({totalQuantityColLetter}4:{totalAmountColLetter}{totalRowIndex - 1})/2)";
totalWorksheet.Cells[totalRowIndex, 50].formula = $"=((SUM({totalAmountColLetter}4:{totalAmountColLetter}{totalRowIndex - 1})/2) - {totalAmountColLetter}{totalRowIndex + 2})/1.13";
totalRowIndex++;
totalWorksheet.Cells[totalRowIndex, 11].Value2 = "總銷量及金額(含稅)";
for (int i = 0; i < 12; i++)
{
int quantityColIndex = sumColStart + i * 3 + 1;
string quantityColLetter = GetColumnLetter(quantityColIndex);
totalWorksheet.Cells[totalRowIndex, quantityColIndex].formula = $"=(SUM({quantityColLetter}4:{quantityColLetter}{totalRowIndex - 1})/2)";
int amountColIndex = sumColStart + i * 3 + 2;
string amountColLetter = GetColumnLetter(amountColIndex);
totalWorksheet.Cells[totalRowIndex, amountColIndex].formula = $"=(SUM({amountColLetter}4:{amountColLetter}{totalRowIndex - 1})/2)";
}
totalWorksheet.Cells[totalRowIndex, 49].formula = $"=(SUM({totalQuantityColLetter}4:{totalQuantityColLetter}{totalRowIndex - 1})/2)";
totalWorksheet.Cells[totalRowIndex, 50].formula = $"=(SUM({totalAmountColLetter}4:{totalAmountColLetter}{totalRowIndex - 1})/2)";
totalRowIndex++;
totalWorksheet.Cells[totalRowIndex, 11].Value2 = "總銷量及金額(外銷)";
for (int i = 0; i < 12; i++)
{
int quantityColIndex = sumColStart + i * 3 + 1;
totalWorksheet.Cells[totalRowIndex, quantityColIndex].Value2 = 0;
int amountColIndex = sumColStart + i * 3 + 2;
totalWorksheet.Cells[totalRowIndex, amountColIndex].Value2 = 0;
}
totalWorksheet.Cells[totalRowIndex, 49].Value2 = 0;
totalWorksheet.Cells[totalRowIndex, 50].Value2 = 0;
totalRowIndex++;
totalWorksheet.Cells[totalRowIndex, 11].Value2 = "總銷量及金額(內外銷)";
for (int i = 0; i < 12; i++)
{
int quantityColIndex = sumColStart + i * 3 + 1;
string quantityColLetter = GetColumnLetter(quantityColIndex);
totalWorksheet.Cells[totalRowIndex, quantityColIndex].formula = $"={quantityColLetter}{totalRowIndex - 3}+{quantityColLetter}{totalRowIndex - 1}";
int amountColIndex = sumColStart + i * 3 + 2;
string amountColLetter = GetColumnLetter(amountColIndex);
totalWorksheet.Cells[totalRowIndex, amountColIndex].formula = $"={amountColLetter}{totalRowIndex - 3}+{amountColLetter}{totalRowIndex - 1}";
}
totalWorksheet.Cells[totalRowIndex, 49].formula = $"={totalQuantityColLetter}{totalRowIndex - 3}+{totalQuantityColLetter}{totalRowIndex - 1}";
totalWorksheet.Cells[totalRowIndex, 50].formula = $"={totalAmountColLetter}{totalRowIndex - 3}+{totalAmountColLetter}{totalRowIndex - 1}";
// 自适应所有已用列宽
FormatSheetStyle(totalWorksheet);
}
private static void FormatSheetStyle(Worksheet worksheet)
{
// 自适应所有已用列宽
Range usedRange2 = worksheet.UsedRange;
if (usedRange2 != null)
{
usedRange2.Columns.AutoFit();
usedRange2.Borders.LineStyle = XlLineStyle.xlContinuous;
usedRange2.Borders.Weight = XlBorderWeight.xlThin;
}
worksheet.Cells[4, 12].Select();
//worksheet.Application.ActiveWindow.SplitRow = 3;
//worksheet.Application.ActiveWindow.SplitColumn = 10;
worksheet.Application.ActiveWindow.FreezePanes = true;
}
// 列号转字母方法
private static string GetColumnLetter(int col)
{
string colLetter = "";
while (col > 0)
{
int mod = (col - 1) % 26;
colLetter = (char)(65 + mod) + colLetter;
col = (col - mod - 1) / 26;
}
return colLetter;
} }
public static void WriteSalesDetailsData( public static void WriteSalesDetailsData(
@ -40,15 +179,79 @@ namespace KellyReport_D.Utils
) )
{ {
int rowIndex = 4; // 从第四行开始写入数据 int rowIndex = 4; // 从第四行开始写入数据
var clientGroupedSalesDetail = context.GetGroupedSalesDetailByClientName(userGroupedSalesDetail.Details); var clientSalesList = context.GetGroupedSalesDetailByClientName(userGroupedSalesDetail.Details);
foreach (var salesDetail in clientGroupedSalesDetail) foreach (var clientSalesDetail in clientSalesList)
{ {
ClientContact clientContact = context.ClientContactList.FirstOrDefault(x => x.Name == salesDetail.ClientName) ?? new ClientContact ClientContact clientContact = context.ClientContactList.FirstOrDefault(x => x.Name == clientSalesDetail.ClientName) ?? new ClientContact
{ {
Name = salesDetail.ClientName, Name = clientSalesDetail.ClientName,
}; };
userSheet.Cells[rowIndex, 1].Value2 = salesDetail.SalesName; var productSalesList = clientSalesDetail.Details
.GroupBy(x => x.ProductNameEN)
.Select(grouped => new ImportContext.GroupedSalesDetailModel
{
SalesName = clientSalesDetail.SalesName,
ClientName = clientSalesDetail.ClientName,
ProductNameEN = grouped.Key,
TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0),
Quantity = grouped.Sum(x => x.Quantity ?? 0),
Details = grouped.ToList()
});
rowIndex = WriteSalesDetailToSheet(userSheet, rowIndex, clientContact, productSalesList);
// 客户销量汇总
var totalSales = new List<ImportContext.GroupedSalesDetailModel>()
{
new ImportContext.GroupedSalesDetailModel
{
SalesName = clientSalesDetail.SalesName,
ClientName = clientSalesDetail.ClientName,
ProductNameEN = "總銷售額",
TotalAmount = clientSalesDetail.TotalAmount,
Quantity = clientSalesDetail.Quantity,
Details = clientSalesDetail.Details.Aggregate(
new List<SalesDetail>(),
(accDetail, curDetail) => {
var monthSales = accDetail.FirstOrDefault(x => x.Month == curDetail.Month);
if (monthSales == null)
{
monthSales = new SalesDetail
{
Month = curDetail.Month,
SalesName = curDetail.SalesName,
ProductNameEN = "總銷售額",
Quantity = 0,
Tax = 0,
TotalAmount = 0,
PriceWithTax = 0,
AmountWithoutTax = 0
};
accDetail.Add(monthSales);
}
monthSales.Quantity += curDetail.Quantity;
monthSales.Tax += curDetail.Tax;
monthSales.TotalAmount += curDetail.TotalAmount;
//monthSales.PriceWithTax += curDetail.PriceWithTax;
monthSales.AmountWithoutTax += curDetail.AmountWithoutTax;
return accDetail;
})
}
};
rowIndex = WriteSalesDetailToSheet(userSheet, rowIndex, clientContact, totalSales);
}
FormatSheetStyle(userSheet);
}
private static int WriteSalesDetailToSheet(Worksheet userSheet, int rowIndex, ClientContact clientContact, IEnumerable<ImportContext.GroupedSalesDetailModel> salesDetailList)
{
foreach (var productSalesDetail in salesDetailList)
{
userSheet.Cells[rowIndex, 1].Value2 = productSalesDetail.SalesName;
userSheet.Cells[rowIndex, 2].Value2 = clientContact.Region; userSheet.Cells[rowIndex, 2].Value2 = clientContact.Region;
userSheet.Cells[rowIndex, 3].Value2 = clientContact.Industry; userSheet.Cells[rowIndex, 3].Value2 = clientContact.Industry;
userSheet.Cells[rowIndex, 4].Value2 = clientContact.AnnualCapacity; userSheet.Cells[rowIndex, 4].Value2 = clientContact.AnnualCapacity;
@ -58,9 +261,57 @@ namespace KellyReport_D.Utils
userSheet.Cells[rowIndex, 8].Value2 = clientContact.Purchaser; userSheet.Cells[rowIndex, 8].Value2 = clientContact.Purchaser;
userSheet.Cells[rowIndex, 9].Value2 = clientContact.PurchaserPhone; userSheet.Cells[rowIndex, 9].Value2 = clientContact.PurchaserPhone;
userSheet.Cells[rowIndex, 10].Value2 = clientContact.Address; userSheet.Cells[rowIndex, 10].Value2 = clientContact.Address;
userSheet.Cells[rowIndex, 11].Value2 = salesDetail.ProductNameEN; userSheet.Cells[rowIndex, 11].Value2 = productSalesDetail.ProductNameEN;
// 计算每月的数量和金额
int monthCol = 13; // L列是第12列
for (int i = 1; i < 13; i++)
{
int monthCounter = 0;
var monthDetail = productSalesDetail.Details.Where(x => x.Month == $"{i}月")
.Aggregate(new SalesDetail()
{
Month = $"{i}月",
Quantity = 0,
TotalAmount = 0,
PriceWithTax = 0,
AmountWithoutTax = 0,
Tax = 0
}, (accSalesModel, curSalesModel) =>
{
accSalesModel.Quantity += (curSalesModel.Quantity ?? 0);
accSalesModel.TotalAmount += (curSalesModel.TotalAmount ?? 0);
accSalesModel.PriceWithTax += (curSalesModel.PriceWithTax ?? 0);
monthCounter++;
return accSalesModel;
});
if (monthCounter > 1)
{
monthDetail.PriceWithTax = monthDetail.PriceWithTax / monthCounter;
}
if (monthDetail == null)
{
userSheet.Cells[rowIndex, monthCol + (i - 1) * 3].Value2 = 0; // 單價
userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 1].Value2 = 0; // 數量
userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 2].Value2 = 0; // 金額
}
else
{
userSheet.Cells[rowIndex, monthCol + (i - 1) * 3].Value2 = monthDetail.PriceWithTax ?? 0; // 單價
userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 1].Value2 = monthDetail.Quantity ?? 0; // 數量
userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 2].Value2 = monthDetail.TotalAmount ?? 0; // 总额
}
}
userSheet.Cells[rowIndex, 49].Value = productSalesDetail.Quantity;
userSheet.Cells[rowIndex, 50].Value = productSalesDetail.TotalAmount;
rowIndex++; rowIndex++;
} }
return rowIndex;
} }
public static void PrepareTotalHeader(Worksheet userSheet) public static void PrepareTotalHeader(Worksheet userSheet)
@ -126,15 +377,37 @@ namespace KellyReport_D.Utils
private static Worksheet GenUserSalesDetailSheet( private static Worksheet GenUserSalesDetailSheet(
Model.ImportContext context, ImportContext context,
Model.ImportContext.GroupedSalesDetailModel groupedSalesDetail ImportContext.GroupedSalesDetailModel groupedSalesDetail
) )
{ {
var app = context.Application; var app = context.Application;
var salesName = groupedSalesDetail.SalesName ?? "UNKONW"; var salesName = groupedSalesDetail.SalesName ?? "UNKONW";
string sheetName = $"JQ Total({salesName.ToUpper()})"; string sheetName = $"JQ Total({salesName.ToUpper()})";
Worksheet userSheet = null; Worksheet userSheet = CreateSheetIfNotExisted(app, sheetName);
Range usedRange = userSheet.UsedRange;
if (usedRange != null && usedRange.Cells.Count > 1)
{
usedRange.Clear();
}
return userSheet;
}
private static Worksheet CreateSheetIfNotExisted(Microsoft.Office.Interop.Excel.Application app, string sheetName)
{
Worksheet userSheet = null;
foreach (Worksheet item in app.Worksheets)
{
if (item.Name == sheetName)
{
userSheet = item;
break;
}
};
if (userSheet == null)
{
try try
{ {
// 新建工作表并命名 // 新建工作表并命名
@ -143,19 +416,7 @@ namespace KellyReport_D.Utils
} }
catch catch
{ {
// 已存在则获取 throw new FormatException("创建或获取用户工作表失败,请检查是否存在同名工作表。");
userSheet = app.Worksheets[sheetName] as Worksheet;
try
{
Range usedRange = userSheet.UsedRange;
if (usedRange != null && usedRange.Cells.Count > 1)
{
usedRange.Clear();
}
}
catch
{
// 无 UsedRange 可忽略
} }
} }
@ -176,7 +437,7 @@ namespace KellyReport_D.Utils
{ {
SalesDetail item = new SalesDetail SalesDetail item = new SalesDetail
{ {
SalesName = rawSalesDetailData[i, 1]?.ToString() ?? "", SalesName = rawSalesDetailData[i, 1]?.ToString() ?? "UNKNOWN",
Month = rawSalesDetailData[i, 2]?.ToString() ?? "", Month = rawSalesDetailData[i, 2]?.ToString() ?? "",
InvoiceDate = rawSalesDetailData[i, 3] is double invoiceDate ? DateTime.FromOADate(invoiceDate) : (DateTime?)null, InvoiceDate = rawSalesDetailData[i, 3] is double invoiceDate ? DateTime.FromOADate(invoiceDate) : (DateTime?)null,
OrderNumber = rawSalesDetailData[i, 4]?.ToString() ?? "", OrderNumber = rawSalesDetailData[i, 4]?.ToString() ?? "",
@ -233,9 +494,9 @@ namespace KellyReport_D.Utils
try try
{ {
// 获取「銷售年度預估」工作表 // 获取「銷售年度預估」工作表
readSalesForecastData(context); ReadSalesForecastData(context);
// 获取「產品年度預估」工作表 // 获取「產品年度預估」工作表
readProductForecastData(context); ReadProductForecastData(context);
GenBussenessSheet(context); GenBussenessSheet(context);
} }
@ -361,7 +622,7 @@ namespace KellyReport_D.Utils
} }
private static void readClientContact(ImportContext context) private static void ReadClientContact(ImportContext context)
{ {
var app = context.Application; var app = context.Application;
@ -396,7 +657,7 @@ namespace KellyReport_D.Utils
context.ClientContactList = clientContactList; context.ClientContactList = clientContactList;
} }
private static void readProductForecastData(GenForecaseContext context) private static void ReadProductForecastData(GenForecaseContext context)
{ {
var app = context.Application; var app = context.Application;
@ -434,7 +695,7 @@ namespace KellyReport_D.Utils
context.ProductForecastList = productForecastList; context.ProductForecastList = productForecastList;
} }
private static void readSalesForecastData(GenForecaseContext context) private static void ReadSalesForecastData(GenForecaseContext context)
{ {
var app = context.Application; var app = context.Application;