diff --git a/KellyReport_D.csproj b/KellyReport_D.csproj index 8363d7e..cb2fa5f 100644 --- a/KellyReport_D.csproj +++ b/KellyReport_D.csproj @@ -34,7 +34,7 @@ publish\ zh-chs - 1.0.0.1 + 1.0.0.2 true true 7 diff --git a/Model/ImportContext.cs b/Model/ImportContext.cs index 7e9221c..6dbfa3d 100644 --- a/Model/ImportContext.cs +++ b/Model/ImportContext.cs @@ -10,6 +10,7 @@ namespace KellyReport_D.Model internal class ImportContext { public Workbook ImportWorkbook { get; set; } + public List SalesWorksheets { get; set; } public Microsoft.Office.Interop.Excel.Application Application { get; set; } public IList ClientContactList { get; set; } public IList SalesDetails { get; set; } @@ -19,6 +20,7 @@ namespace KellyReport_D.Model public string ClientName { get; set; } public string ProductNameEN { get; set; } public decimal TotalAmount { get; set; } + public decimal Quantity { get; set; } public List Details { get; set; } } @@ -28,18 +30,18 @@ namespace KellyReport_D.Model { SalesName = grouped.Key, TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0), + Quantity = grouped.Sum(x => x.Quantity ?? 0), Details = grouped.ToList() }); public IEnumerable GetGroupedSalesDetailByClientName(IList 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 { SalesName = grouped.Key.SalesName, ClientName = grouped.Key.ClientName, - ProductNameEN = grouped.Key.ProductNameEN, TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0), Details = grouped.ToList() }); diff --git a/Utils/WorkBookUtils.cs b/Utils/WorkBookUtils.cs index 6b76981..17d79b4 100644 --- a/Utils/WorkBookUtils.cs +++ b/Utils/WorkBookUtils.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using KellyReport_D.Model; -using System.Windows.Input; namespace KellyReport_D.Utils { @@ -18,11 +17,12 @@ namespace KellyReport_D.Utils var importContext = new ImportContext() { Application = Globals.ThisAddIn.Application, - ImportWorkbook = workbook + ImportWorkbook = workbook, + SalesWorksheets = new List(), }; // 获取「客户资料」工作表 - readClientContact(importContext); + ReadClientContact(importContext); // 读取「销售明细」工作表 ReadSalesDetails(importContext); foreach (var userGroupedSalesDetail in importContext.GroupedSalesDetailBySalesName) @@ -30,7 +30,146 @@ namespace KellyReport_D.Utils var userSheet = GenUserSalesDetailSheet(importContext, userGroupedSalesDetail); PrepareTotalHeader(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( @@ -40,15 +179,79 @@ namespace KellyReport_D.Utils ) { int rowIndex = 4; // 从第四行开始写入数据 - var clientGroupedSalesDetail = context.GetGroupedSalesDetailByClientName(userGroupedSalesDetail.Details); - foreach (var salesDetail in clientGroupedSalesDetail) + var clientSalesList = context.GetGroupedSalesDetailByClientName(userGroupedSalesDetail.Details); + 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() + { + new ImportContext.GroupedSalesDetailModel + { + SalesName = clientSalesDetail.SalesName, + ClientName = clientSalesDetail.ClientName, + ProductNameEN = "總銷售額", + TotalAmount = clientSalesDetail.TotalAmount, + Quantity = clientSalesDetail.Quantity, + Details = clientSalesDetail.Details.Aggregate( + new List(), + (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 salesDetailList) + { + foreach (var productSalesDetail in salesDetailList) + { + userSheet.Cells[rowIndex, 1].Value2 = productSalesDetail.SalesName; userSheet.Cells[rowIndex, 2].Value2 = clientContact.Region; userSheet.Cells[rowIndex, 3].Value2 = clientContact.Industry; 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, 9].Value2 = clientContact.PurchaserPhone; 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++; } + + return rowIndex; } public static void PrepareTotalHeader(Worksheet userSheet) @@ -126,36 +377,46 @@ namespace KellyReport_D.Utils private static Worksheet GenUserSalesDetailSheet( - Model.ImportContext context, - Model.ImportContext.GroupedSalesDetailModel groupedSalesDetail + ImportContext context, + ImportContext.GroupedSalesDetailModel groupedSalesDetail ) { var app = context.Application; var salesName = groupedSalesDetail.SalesName ?? "UNKONW"; string sheetName = $"JQ Total({salesName.ToUpper()})"; - Worksheet userSheet = null; - - try + Worksheet userSheet = CreateSheetIfNotExisted(app, sheetName); + Range usedRange = userSheet.UsedRange; + if (usedRange != null && usedRange.Cells.Count > 1) { - // 新建工作表并命名 - userSheet = app.Worksheets.Add(); - userSheet.Name = sheetName; + usedRange.Clear(); } - catch + + 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) { - // 已存在则获取 - userSheet = app.Worksheets[sheetName] as Worksheet; try { - Range usedRange = userSheet.UsedRange; - if (usedRange != null && usedRange.Cells.Count > 1) - { - usedRange.Clear(); - } + // 新建工作表并命名 + userSheet = app.Worksheets.Add(); + userSheet.Name = sheetName; } catch { - // 无 UsedRange 可忽略 + throw new FormatException("创建或获取用户工作表失败,请检查是否存在同名工作表。"); } } @@ -176,7 +437,7 @@ namespace KellyReport_D.Utils { SalesDetail item = new SalesDetail { - SalesName = rawSalesDetailData[i, 1]?.ToString() ?? "", + SalesName = rawSalesDetailData[i, 1]?.ToString() ?? "UNKNOWN", Month = rawSalesDetailData[i, 2]?.ToString() ?? "", InvoiceDate = rawSalesDetailData[i, 3] is double invoiceDate ? DateTime.FromOADate(invoiceDate) : (DateTime?)null, OrderNumber = rawSalesDetailData[i, 4]?.ToString() ?? "", @@ -233,9 +494,9 @@ namespace KellyReport_D.Utils try { // 获取「銷售年度預估」工作表 - readSalesForecastData(context); + ReadSalesForecastData(context); // 获取「產品年度預估」工作表 - readProductForecastData(context); + ReadProductForecastData(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; @@ -396,7 +657,7 @@ namespace KellyReport_D.Utils context.ClientContactList = clientContactList; } - private static void readProductForecastData(GenForecaseContext context) + private static void ReadProductForecastData(GenForecaseContext context) { var app = context.Application; @@ -434,7 +695,7 @@ namespace KellyReport_D.Utils context.ProductForecastList = productForecastList; } - private static void readSalesForecastData(GenForecaseContext context) + private static void ReadSalesForecastData(GenForecaseContext context) { var app = context.Application;