自动生成客户/产品年度统计,新增日志功能

- 新增 FileLogger 日志工具类,增强异常记录
- 自动生成“客户年度预估”“产品年度销量统计”表
- 升级版本号至 1.0.0.7
This commit is contained in:
earo.lau 2025-12-30 01:05:26 +08:00
parent aedbd788be
commit f839573712
5 changed files with 414 additions and 81 deletions

View File

@ -34,7 +34,7 @@
<PublishUrl>publish\</PublishUrl>
<InstallUrl />
<TargetCulture>zh-chs</TargetCulture>
<ApplicationVersion>1.0.0.4</ApplicationVersion>
<ApplicationVersion>1.0.0.7</ApplicationVersion>
<AutoIncrementApplicationRevision>true</AutoIncrementApplicationRevision>
<UpdateEnabled>true</UpdateEnabled>
<UpdateInterval>7</UpdateInterval>
@ -227,6 +227,7 @@
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Utils\FileLogger.cs" />
<Compile Include="Utils\WorkBookUtils.cs" />
<EmbeddedResource Include="JQSalesSummaryPanel.resx">
<DependentUpon>JQSalesSummaryPanel.cs</DependentUpon>

View File

@ -34,5 +34,5 @@ using System.Security;
// 方法是按如下所示使用“*”: :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.6")]

View File

@ -3,7 +3,9 @@ using Microsoft.Office.Tools;
using Microsoft.Office.Tools.Ribbon;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace KellyReport_D
@ -12,6 +14,20 @@ namespace KellyReport_D
{
private CustomTaskPane jqSalesSummaryPanel;
private string GetAppVersion()
{
var asm = Assembly.GetExecutingAssembly();
try
{
var fvi = FileVersionInfo.GetVersionInfo(asm.Location);
if (!string.IsNullOrWhiteSpace(fvi.FileVersion))
return fvi.FileVersion;
}
catch { }
return asm.GetName().Version.ToString();
}
private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
{
this.button1.Label = Resources.APP_NAME;
@ -23,7 +39,7 @@ namespace KellyReport_D
if (jqSalesSummaryPanel == null)
{
var panel = new JQSalesSummaryPanel();
jqSalesSummaryPanel = Globals.ThisAddIn.CustomTaskPanes.Add(panel, Resources.APP_NAME);
jqSalesSummaryPanel = Globals.ThisAddIn.CustomTaskPanes.Add(panel, $"{Resources.APP_NAME} v{GetAppVersion()}");
jqSalesSummaryPanel.Width = 600;
}

47
Utils/FileLogger.cs Normal file
View File

@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Text;
namespace KellyReport_D.Utils
{
internal static class FileLogger
{
private static readonly object _sync = new object();
private static string LogDirectory =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "KellyReport", "logs");
private static string GetLogFilePath()
{
string fileName = $"kellyreport_{DateTime.Now:yyyyMMdd}.log";
return Path.Combine(LogDirectory, fileName);
}
private static void WriteInternal(string level, string message, Exception ex = null)
{
try
{
lock (_sync)
{
Directory.CreateDirectory(LogDirectory);
var path = GetLogFilePath();
using (var sw = new StreamWriter(path, true, Encoding.UTF8))
{
sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{level}] {message}");
if (ex != null)
{
sw.WriteLine(ex.ToString());
}
}
}
}
catch
{
// 日志写入不应影响主流程,忽略任何异常
}
}
public static void Info(string message) => WriteInternal("INFO", message);
public static void Debug(string message) => WriteInternal("DEBUG", message);
public static void Error(string message, Exception ex = null) => WriteInternal("ERROR", message, ex);
}
}

View File

@ -5,8 +5,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KellyReport_D.Model;
using System.Runtime.InteropServices;
namespace KellyReport_D.Utils
{
@ -40,7 +40,10 @@ namespace KellyReport_D.Utils
UpdateEstimatedTable(importContext);
UpdateDeptartmentSummary(importContext);
// 产品年度销量统计
GenProductSummarySheet(importContext);
// 生成客户年度销量统计
GenClientSummarySheet(importContext);
}
private static void UpdateEstimatedTable(ImportContext importContext)
@ -66,9 +69,12 @@ namespace KellyReport_D.Utils
Range usedRange = bussenessWorksheet.UsedRange;
int maxColIndex = usedRange.Columns.Count;
int yearRowIndex = 1;
FindBussenessYearRow(bussenessWorksheet, importContext.YearList[0].ToString(), ref yearRowIndex);
for (int i = 2; i < maxColIndex; i += 4)
{
String salesName = bussenessWorksheet.Cells[1, i].Value2;
String salesName = bussenessWorksheet.Cells[yearRowIndex, i].Value2;
if (string.IsNullOrWhiteSpace(salesName))
{
continue;
@ -90,7 +96,7 @@ namespace KellyReport_D.Utils
.ToList()
.ForEach(monthlySales =>
{
int startRowIndex = 3;
int startRowIndex = yearRowIndex + 2;
for (int rowIndex = startRowIndex; rowIndex < startRowIndex + 12; rowIndex++)
{
var monthCell = bussenessWorksheet.Cells[rowIndex, 1];
@ -214,7 +220,7 @@ namespace KellyReport_D.Utils
}
catch (Exception ex)
{
Console.Error.WriteLine("delete error %s", ex.Message);
FileLogger.Error("删除Total(All)旧数据时发生错误", ex);
}
finally
{
@ -381,7 +387,7 @@ namespace KellyReport_D.Utils
{
for (var i = 0; i < clientSalesRowCount - curRowCount; i++)
{
userSheet.Rows[clientSalesRowIndex + curRowCount].Insert(XlInsertShiftDirection.xlShiftDown);
userSheet.Rows[clientSalesRowIndex + curRowCount - 1].Insert(XlInsertShiftDirection.xlShiftDown);
summaryRowIndex++;
}
}
@ -455,19 +461,61 @@ namespace KellyReport_D.Utils
private static int WriteSalesDetailToSheet(Worksheet userSheet, int rowIndex, int yearColIndex, ClientContact clientContact, IEnumerable<ImportContext.GroupedSalesDetailModel> salesDetailList)
{
Range productNamesCellRange = userSheet.Range[
userSheet.Cells[rowIndex, 11],
userSheet.Cells[rowIndex + salesDetailList.Count(), 11]
];
foreach (var productSalesDetail in salesDetailList)
{
int currentRowIndex = rowIndex;
// 如果存在相同产品名称,则写入对应行
if (productSalesDetail.ProductNameEN != "總銷售額")
{
var targetProductCell = productNamesCellRange.Find(What: productSalesDetail.ProductNameEN, LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
if (targetProductCell != null)
{
currentRowIndex = targetProductCell.Row;
}
else
{
Range blankCell = null;
try
{
blankCell = productNamesCellRange.SpecialCells(XlCellType.xlCellTypeBlanks);
}
catch (COMException)
{
// 没有空白单元格
//productNamesCellRange.Insert(XlInsertShiftDirection.xlShiftDown);
MessageBox.Show($"写入数据时发生错误,可能是因为产品名称过多导致,错误数据:{productSalesDetail.SalesName}-{productSalesDetail.ClientName}-{productSalesDetail.ProductNameEN}。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
rowIndex++;
continue;
//userSheet.Rows[rowIndex + salesDetailList.Count() - 1].Insert(XlInsertShiftDirection.xlShiftDown);
//blankCell = userSheet.Cells[rowIndex + salesDetailList.Count() - 1, 11];
}
if (blankCell != null)
{
currentRowIndex = blankCell.Row;
}
}
}
//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;
userSheet.Cells[rowIndex, 5].Value2 = clientContact.Name;
userSheet.Cells[rowIndex, 6].Value2 = clientContact.PurchasingManager;
userSheet.Cells[rowIndex, 7].Value2 = clientContact.PurchasingManagerPhone;
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 = productSalesDetail.ProductNameEN;
userSheet.Cells[currentRowIndex, 2].Value2 = clientContact.Region;
userSheet.Cells[currentRowIndex, 3].Value2 = clientContact.Industry;
userSheet.Cells[currentRowIndex, 4].Value2 = clientContact.AnnualCapacity;
userSheet.Cells[currentRowIndex, 5].Value2 = clientContact.Name;
userSheet.Cells[currentRowIndex, 6].Value2 = clientContact.PurchasingManager;
userSheet.Cells[currentRowIndex, 7].Value2 = clientContact.PurchasingManagerPhone;
userSheet.Cells[currentRowIndex, 8].Value2 = clientContact.Purchaser;
userSheet.Cells[currentRowIndex, 9].Value2 = clientContact.PurchaserPhone;
userSheet.Cells[currentRowIndex, 10].Value2 = clientContact.Address;
userSheet.Cells[currentRowIndex, 11].Value2 = productSalesDetail.ProductNameEN;
// 计算每月的数量和金额
int monthCol = yearColIndex;
@ -500,19 +548,19 @@ namespace KellyReport_D.Utils
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; // 金額
userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3].Value2 = 0; // 單價
userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 1].Value2 = 0; // 數量
userSheet.Cells[currentRowIndex, 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[currentRowIndex, monthCol + (i - 1) * 3].Value2 = monthDetail.PriceWithTax ?? 0; // 單價
userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 1].Value2 = monthDetail.Quantity ?? 0; // 數量
userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 2].Value2 = monthDetail.TotalAmount ?? 0; // 总额
}
}
userSheet.Cells[rowIndex, 49].Value = productSalesDetail.Quantity;
userSheet.Cells[rowIndex, 50].Value = productSalesDetail.TotalAmount;
userSheet.Cells[currentRowIndex, 49].Value = productSalesDetail.Quantity;
userSheet.Cells[currentRowIndex, 50].Value = productSalesDetail.TotalAmount;
rowIndex++;
}
@ -656,7 +704,7 @@ namespace KellyReport_D.Utils
}
catch (Exception ex)
{
Console.Error.WriteLine("parse date error:" + ex.Message);
FileLogger.Error($"解析日期错误 '{rawValue}'", ex);
//throw new FormatException($"日期格式错误 '{rawValue}'");
}
@ -741,28 +789,25 @@ namespace KellyReport_D.Utils
// 获取「銷售年度預估」工作表
ReadSalesForecastData(context);
// 获取「產品年度預估」工作表
//ReadProductForecastData(context);
// GenProductForecastData(context);
GenDepartmentSummary(context);
GenBussenessSheet(context);
}
catch (Exception ex)
{
Console.WriteLine("Error generating forecast:" + ex.Message);
FileLogger.Error("生成销售年度预估表失败", ex);
MessageBox.Show("分析预估表失败: " + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
button.Text = originLabel;
}
public static void GenBussenessSheet(GenForecaseContext context)
public static bool FindBussenessYearRow(Worksheet sheet, string targetYear, ref int yearRowIndex)
{
var app = context.Application;
Worksheet sheet = CreateSheetIfNotExisted(app, "業務月達成率");
Range usedRange = sheet.UsedRange;
int totalRows = usedRange?.Rows?.Count ?? 0;
int yearRowIndex = 1;
Boolean updateCurrentBook = false;
while (yearRowIndex < totalRows - 1)
{
@ -777,7 +822,7 @@ namespace KellyReport_D.Utils
yearValue = (rawYearValue).ToString();
}
if (yearValue == context.MonthData[0])
if (yearValue == targetYear)
{
updateCurrentBook = true;
break;
@ -786,6 +831,19 @@ namespace KellyReport_D.Utils
yearRowIndex += 15;
}
return updateCurrentBook;
}
public static void GenBussenessSheet(GenForecaseContext context)
{
var app = context.Application;
Worksheet sheet = CreateSheetIfNotExisted(app, "業務月達成率");
Range usedRange = sheet.UsedRange;
int totalRows = usedRange?.Rows?.Count ?? 0;
int yearRowIndex = 1;
Boolean updateCurrentBook = FindBussenessYearRow(sheet, context.MonthData[0], ref yearRowIndex);
try
{
string rowRanges = "";
@ -808,6 +866,7 @@ namespace KellyReport_D.Utils
}
catch (Exception ex)
{
FileLogger.Error($"更新{context.MonthData[0]}年業務月達成率失败", ex);
throw new Exception($"更新{context.MonthData[0]}年業務月達成率失败", ex);
}
@ -894,6 +953,8 @@ namespace KellyReport_D.Utils
}
catch (Exception ex)
{
FileLogger.Error($"写入{context.MonthData[0]}年業務月達成率失败", ex);
MessageBox.Show("写入业绩月达成率时发生错误:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
@ -934,44 +995,6 @@ namespace KellyReport_D.Utils
context.ClientContactList = clientContactList;
}
private static void ReadProductForecastData(GenForecaseContext context)
{
var app = context.Application;
var productForecastSheet = app.Worksheets["產品年度預估"] as Worksheet;
var productRange = productForecastSheet.UsedRange;
IList<ProductForecastInput> productForecastList = new List<ProductForecastInput>();
if (productRange.Value2 is object[,] rawProductData)
{
int rowCount = rawProductData.GetLength(0);
int colCount = rawProductData.GetLength(1);
if (rawProductData[1, 2] == null)
{
throw new MissingMemberException("「產品年度預估」工作表的(年份)不能为空,请检查数据格式。");
}
var year = Convert.ToInt32(rawProductData[1, 2]);
// 模板第三行开始是数据
for (int i = 3; i <= rowCount; i++)
{
var input = new ProductForecastInput
{
Year = year,
Name = rawProductData[i, 1]?.ToString() ?? "",
Quantity = rawProductData[i, 2] != null ? Convert.ToDecimal(rawProductData[i, 2]) : 0,
AmountTax = rawProductData[i, 3] != null ? Convert.ToDecimal(rawProductData[i, 3]) : 0,
Amount = rawProductData[i, 4] != null ? Convert.ToDecimal(rawProductData[i, 4]) : 0,
PriceAvg = rawProductData[i, 5] != null ? Convert.ToDecimal(rawProductData[i, 5]) : 0,
};
productForecastList.Add(input);
}
}
context.ProductForecastList = productForecastList;
}
private static void ReadSalesForecastData(GenForecaseContext context)
{
var app = context.Application;
@ -1076,6 +1099,7 @@ namespace KellyReport_D.Utils
];
turnOverRange.Merge();
}
usedRange = productSheet.UsedRange;
var productSalesList = context.SalesDetails
.Where(x => x.InvoiceDate.HasValue && x.InvoiceDate.Value.Year == yearNumber)
@ -1089,9 +1113,9 @@ namespace KellyReport_D.Utils
})
.ToList();
Range totalRange = usedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
Range totalRange = productSheet.UsedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
int totalRowCount = usedRange.Rows.Count + 1;
int totalRowCount = productSheet.UsedRange.Rows.Count + 1;
if (totalRange != null)
{
totalRowCount = totalRange.Row;
@ -1110,7 +1134,7 @@ namespace KellyReport_D.Utils
var curProductName = productSalesSummary.ProductNameEN;
// look for the product row
// if not exists then create a new row
Range productRange = usedRange.Find(
Range productRange = productSheet.UsedRange.Find(
What: curProductName,
LookIn: XlFindLookIn.xlValues,
LookAt: XlLookAt.xlWhole,
@ -1159,6 +1183,127 @@ namespace KellyReport_D.Utils
FormatSheetStyle(productSheet);
}
public static void GenClientSummarySheet(ImportContext context)
{
var app = context.Application;
string sheetName = $"客戶年度預估";
Worksheet clientSheet = CreateSheetIfNotExisted(app, sheetName);
clientSheet.Activate();
context.YearList.ForEach(yearNumber =>
{
Range turnOverRange = clientSheet.UsedRange.Find(What: $"{yearNumber}-Turnover", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
var clientSalesList = context.SalesDetails
.Where(x => x.InvoiceDate.HasValue && x.InvoiceDate.Value.Year == yearNumber)
.GroupBy(x => x.ClientName)
.Select(grouped => new ImportContext.GroupedSalesDetailModel()
{
ClientName = grouped.Key,
Quantity = grouped.Sum(x => x.Quantity ?? 0),
TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0),
Details = grouped.ToList()
})
.ToList();
if (turnOverRange == null)
{
int insertCol = 2;
Range insertRange = clientSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"];
insertRange.EntireColumn.Insert(XlInsertShiftDirection.xlShiftToRight);
insertRange = clientSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"];
insertRange.ClearFormats();
clientSheet.Cells[1, insertCol].Value2 = $"{yearNumber}-Turnover";
clientSheet.Cells[2, insertCol].Value2 = "數量";
clientSheet.Cells[2, insertCol + 1].Value2 = "金額(含稅)";
clientSheet.Cells[2, insertCol + 2].Value2 = "金額(未稅)";
clientSheet.Cells[2, insertCol + 3].Value2 = "平均單價";
clientSheet.Cells[2, insertCol + 4].Value2 = "成長率";
var growthRange = clientSheet.Range[
clientSheet.Cells[2, insertCol + 4],
clientSheet.Cells[2, insertCol + 5]
];
growthRange.Merge();
turnOverRange = clientSheet.Range[
clientSheet.Cells[1, insertCol],
clientSheet.Cells[1, insertCol + 5]
];
turnOverRange.Merge();
}
Range totalRange = clientSheet.UsedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
int totalRowCount = clientSheet.UsedRange.Rows.Count + 1;
if (totalRange != null)
{
totalRowCount = totalRange.Row;
}
int startColIndex = turnOverRange.Column;
var quantityColLetter = GetColumnLetter(startColIndex);
var amountColLetter = GetColumnLetter(startColIndex + 1);
var amountNoTaxColLetter = GetColumnLetter(startColIndex + 2);
var prevQuantityColLetter = GetColumnLetter(startColIndex + 6);
var prevAmountColLetter = GetColumnLetter(startColIndex + 7);
var prevAmountNoTaxColLetter = GetColumnLetter(startColIndex + 8);
foreach (var clientSalesGroup in clientSalesList)
{
var curClientName = clientSalesGroup.ClientName;
// look for the Client row
// if not exists then create a new row
Range clientRange = clientSheet.UsedRange.Find(
What: curClientName,
LookIn: XlFindLookIn.xlValues,
LookAt: XlLookAt.xlWhole,
SearchOrder: XlSearchOrder.xlByRows,
SearchDirection: XlSearchDirection.xlNext,
MatchCase: false
);
if (clientRange == null)
{
clientSheet.Rows[totalRowCount].Insert();
clientSheet.Cells[totalRowCount, 1].Value2 = curClientName;
clientRange = clientSheet.Cells[totalRowCount, 1];
totalRowCount += 1;
}
int curRowIndex = clientRange.Row;
clientSheet.Cells[curRowIndex, startColIndex].Value2 = clientSalesGroup.Quantity;
clientSheet.Cells[curRowIndex, startColIndex + 1].Value2 = clientSalesGroup.TotalAmount;
clientSheet.Cells[curRowIndex, startColIndex + 2].formula = $"={amountColLetter}{curRowIndex}/1.13";
clientSheet.Cells[curRowIndex, startColIndex + 3].formula = $"={amountColLetter}{curRowIndex}/${quantityColLetter}{curRowIndex}";
clientSheet.Cells[curRowIndex, startColIndex + 4].formula = $"={quantityColLetter}{curRowIndex}/${prevQuantityColLetter}{curRowIndex}";
clientSheet.Cells[curRowIndex, startColIndex + 5].formula = $"={amountColLetter}{curRowIndex}/${prevAmountColLetter}{curRowIndex}";
clientSheet.Cells[curRowIndex, startColIndex + 4].NumberFormat = "0.00%";
clientSheet.Cells[curRowIndex, startColIndex + 5].NumberFormat = "0.00%";
}
clientSheet.Cells[totalRowCount, 1].Value2 = "總計";
clientSheet.Cells[totalRowCount, startColIndex].formula = $"=SUM({quantityColLetter}5:{quantityColLetter}{totalRowCount - 1})";
clientSheet.Cells[totalRowCount, startColIndex + 1].formula = $"=SUM({amountColLetter}5:{amountColLetter}{totalRowCount - 1})";
clientSheet.Cells[totalRowCount, startColIndex + 2].formula = $"=SUM({amountNoTaxColLetter}5:{amountNoTaxColLetter}{totalRowCount - 1})";
clientSheet.Cells[totalRowCount + 1, 1].Value2 = "備註";
clientSheet.Cells[totalRowCount + 1, startColIndex].formula = $"={quantityColLetter}{totalRowCount}/{prevQuantityColLetter}{totalRowCount}";
clientSheet.Cells[totalRowCount + 1, startColIndex + 1].formula = $"={amountColLetter}{totalRowCount}/{prevAmountColLetter}{totalRowCount}";
clientSheet.Cells[totalRowCount + 1, startColIndex + 2].formula = $"={amountNoTaxColLetter}{totalRowCount}/{prevAmountNoTaxColLetter}{totalRowCount}";
clientSheet.Cells[totalRowCount + 1, startColIndex].NumberFormat = "0.00%";
clientSheet.Cells[totalRowCount + 1, startColIndex + 1].NumberFormat = "0.00%";
clientSheet.Cells[totalRowCount + 1, startColIndex + 2].NumberFormat = "0.00%";
});
FormatSheetStyle(clientSheet);
}
public static void GenDepartmentSummary(GenForecaseContext context)
{
@ -1171,10 +1316,7 @@ namespace KellyReport_D.Utils
deptSheet.Cells[1, 1].Value2 = "鈞全";
deptSheet.Range[deptSheet.Cells[1, 1], deptSheet.Cells[2, 1]].Merge();
var yearList = context.SalesForecastList
.Select(x => x.Year)
.Distinct()
.ToList();
var yearList = context.SalesForecastList.Select(x => x.Year).Distinct().ToList();
Range initUsedRange = deptSheet.UsedRange;
@ -1269,6 +1411,133 @@ namespace KellyReport_D.Utils
FormatSheetStyle(deptSheet);
}
/* public static void GenProductForecastData(GenForecaseContext context)
{
var app = context.Application;
string sheetName = $"產品年度預估";
Worksheet productSheet = CreateSheetIfNotExisted(app, sheetName);
productSheet.Activate();
var yearList = context.SalesForecastList.Select(x => x.Year).Distinct().ToList();
string yearNumber = yearList[0].ToString();
Range estimateRange = productSheet.UsedRange.Find(What: $"{yearNumber}-Estimate", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
int insertCol = 2;
if (estimateRange != null)
{
insertCol = estimateRange.Column;
}
*//*
Range insertRange = productSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"];
insertRange.EntireColumn.Insert(XlInsertShiftDirection.xlShiftToRight);
insertRange = productSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"];
insertRange.ClearFormats();
*//*
productSheet.Cells[1, insertCol].Value2 = $"{yearNumber}-Estimate";
productSheet.Cells[2, insertCol].Value2 = "數量";
productSheet.Cells[2, insertCol + 1].Value2 = "金額(含稅)";
productSheet.Cells[2, insertCol + 2].Value2 = "金額(未稅)";
productSheet.Cells[2, insertCol + 3].Value2 = "平均單價";
productSheet.Cells[2, insertCol + 4].Value2 = "成長率";
var growthRange = productSheet.Range[
productSheet.Cells[2, insertCol + 4],
productSheet.Cells[2, insertCol + 5]
];
growthRange.Merge();
estimateRange = productSheet.Range[
productSheet.Cells[1, insertCol],
productSheet.Cells[1, insertCol + 5]
];
estimateRange.Merge();
var productSalesList = context.SalesForecastList
.Where(x => x..HasValue && x.InvoiceDate.Value.Year == yearNumber)
.GroupBy(x => x.ProductNameEN)
.Select(grouped => new ImportContext.GroupedSalesDetailModel()
{
ProductNameEN = grouped.Key,
Quantity = grouped.Sum(x => x.Quantity ?? 0),
TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0),
Details = grouped.ToList()
})
.ToList();
Range totalRange = clientSheet.UsedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false);
int totalRowCount = clientSheet.UsedRange.Rows.Count + 1;
if (totalRange != null)
{
totalRowCount = totalRange.Row;
}
int startColIndex = turnOverRange.Column;
var quantityColLetter = GetColumnLetter(startColIndex);
var amountColLetter = GetColumnLetter(startColIndex + 1);
var amountNoTaxColLetter = GetColumnLetter(startColIndex + 2);
var prevQuantityColLetter = GetColumnLetter(startColIndex + 6);
var prevAmountColLetter = GetColumnLetter(startColIndex + 7);
var prevAmountNoTaxColLetter = GetColumnLetter(startColIndex + 8);
foreach (var productSalesSummary in productSalesList)
{
var curProductName = productSalesSummary.ProductNameEN;
// look for the product row
// if not exists then create a new row
Range productRange = clientSheet.UsedRange.Find(
What: curProductName,
LookIn: XlFindLookIn.xlValues,
LookAt: XlLookAt.xlWhole,
SearchOrder: XlSearchOrder.xlByRows,
SearchDirection: XlSearchDirection.xlNext,
MatchCase: false
);
if (productRange == null)
{
clientSheet.Rows[totalRowCount].Insert();
clientSheet.Cells[totalRowCount, 1].Value2 = curProductName;
productRange = clientSheet.Cells[totalRowCount, 1];
totalRowCount += 1;
}
int curRowIndex = productRange.Row;
clientSheet.Cells[curRowIndex, startColIndex].Value2 = productSalesSummary.Quantity;
clientSheet.Cells[curRowIndex, startColIndex + 1].Value2 = productSalesSummary.TotalAmount;
clientSheet.Cells[curRowIndex, startColIndex + 2].formula = $"={amountColLetter}{curRowIndex}/1.13";
clientSheet.Cells[curRowIndex, startColIndex + 3].formula = $"={amountColLetter}{curRowIndex}/${quantityColLetter}{curRowIndex}";
clientSheet.Cells[curRowIndex, startColIndex + 4].formula = $"={quantityColLetter}{curRowIndex}/${prevQuantityColLetter}{curRowIndex}";
clientSheet.Cells[curRowIndex, startColIndex + 5].formula = $"={amountColLetter}{curRowIndex}/${prevAmountColLetter}{curRowIndex}";
clientSheet.Cells[curRowIndex, startColIndex + 4].NumberFormat = "0.00%";
clientSheet.Cells[curRowIndex, startColIndex + 5].NumberFormat = "0.00%";
}
clientSheet.Cells[totalRowCount, 1].Value2 = "總計";
clientSheet.Cells[totalRowCount, startColIndex].formula = $"=SUM({quantityColLetter}5:{quantityColLetter}{totalRowCount - 1})";
clientSheet.Cells[totalRowCount, startColIndex + 1].formula = $"=SUM({amountColLetter}5:{amountColLetter}{totalRowCount - 1})";
clientSheet.Cells[totalRowCount, startColIndex + 2].formula = $"=SUM({amountNoTaxColLetter}5:{amountNoTaxColLetter}{totalRowCount - 1})";
clientSheet.Cells[totalRowCount + 1, 1].Value2 = "備註";
clientSheet.Cells[totalRowCount + 1, startColIndex].formula = $"={quantityColLetter}{totalRowCount}/{prevQuantityColLetter}{totalRowCount}";
clientSheet.Cells[totalRowCount + 1, startColIndex + 1].formula = $"={amountColLetter}{totalRowCount}/{prevAmountColLetter}{totalRowCount}";
clientSheet.Cells[totalRowCount + 1, startColIndex + 2].formula = $"={amountNoTaxColLetter}{totalRowCount}/{prevAmountNoTaxColLetter}{totalRowCount}";
clientSheet.Cells[totalRowCount + 1, startColIndex].NumberFormat = "0.00%";
clientSheet.Cells[totalRowCount + 1, startColIndex + 1].NumberFormat = "0.00%";
clientSheet.Cells[totalRowCount + 1, startColIndex + 2].NumberFormat = "0.00%";
});
FormatSheetStyle(clientSheet);
}
*/
public static void UpdateDeptartmentSummary(ImportContext context)
{
var app = context.Application;
@ -1283,7 +1552,7 @@ namespace KellyReport_D.Utils
if (yearRange == null)
{
Console.WriteLine("Cannot find year column in department summary: {0}", yearoNumber);
FileLogger.Info($"Cannot find year column in department summary: {yearoNumber}");
return;
}