ReportService.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. using Serilog;
  2. using YZWater.Core.Models;
  3. namespace YZWater.Core.Services;
  4. /// <summary>
  5. /// 鎶ヨ〃鏈嶅姟 - 鐢熸垚鏃ユ姤/鏈堟姤
  6. /// </summary>
  7. public static class ReportService
  8. {
  9. /// <summary>
  10. /// 鐢熸垚鏃ユ姤锛圚TML 鏍煎紡锛
  11. /// </summary>
  12. public static async Task<string> GenerateDailyReportAsync(DateTime date)
  13. {
  14. try
  15. {
  16. var start = date.Date;
  17. var end = start.AddDays(1);
  18. // 鏌ヨ娴侀噺鏁版嵁
  19. var flowRecords = await DatabaseService.Db.Queryable<FlowRecord>()
  20. .Where(r => r.RecordTime >= start && r.RecordTime < end)
  21. .OrderBy(r => r.RecordTime)
  22. .ToListAsync();
  23. // 鏌ヨ鎶ヨ鏁版嵁
  24. var alarmRecords = await DatabaseService.Db.Queryable<AlarmRecord>()
  25. .Where(r => r.AlarmTime >= start && r.AlarmTime < end)
  26. .OrderBy(r => r.AlarmTime)
  27. .ToListAsync();
  28. // 璁$畻缁熻
  29. var avgInflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.InflowRate) : 0;
  30. var avgOutflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.OutflowRate) : 0;
  31. var maxInflow = flowRecords.Count > 0 ? flowRecords.Max(r => r.InflowRate) : 0;
  32. var maxOutflow = flowRecords.Count > 0 ? flowRecords.Max(r => r.OutflowRate) : 0;
  33. var totalInflow = flowRecords.Count > 0 ? flowRecords.Last().TotalInflow : 0;
  34. var totalOutflow = flowRecords.Count > 0 ? flowRecords.Last().TotalOutflow : 0;
  35. var config = ConfigService.GetConfig();
  36. var companyName = config?.CompanyName ?? "姹℃按澶勭悊鍘";
  37. var html = $@"<!DOCTYPE html>
  38. <html>
  39. <head>
  40. <meta charset='utf-8'>
  41. <title>鏃ユ姤 - {date:yyyy-MM-dd}</title>
  42. <style>
  43. body {{ font-family: 'Microsoft YaHei', sans-serif; margin: 20px; }}
  44. h1 {{ color: #1976D2; border-bottom: 2px solid #1976D2; padding-bottom: 8px; }}
  45. h2 {{ color: #424242; margin-top: 24px; }}
  46. table {{ border-collapse: collapse; width: 100%; margin: 12px 0; }}
  47. th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
  48. th {{ background: #f5f5f5; }}
  49. .summary {{ background: #E3F2FD; padding: 16px; border-radius: 8px; margin: 16px 0; }}
  50. .alarm {{ color: #E53935; }}
  51. .footer {{ color: #999; font-size: 12px; margin-top: 32px; border-top: 1px solid #eee; padding-top: 8px; }}
  52. </style>
  53. </head>
  54. <body>
  55. <h1>{companyName} - 鏃ユ姤</h1>
  56. <p>鏃ユ湡: {date:yyyy骞碝M鏈坉d鏃</p>
  57. <h2>娴侀噺缁熻</h2>
  58. <div class='summary'>
  59. <p><b>绱杩涙按閲:</b> {totalInflow:F1} m鲁 | <b>绱鍑烘按閲:</b> {totalOutflow:F1} m鲁</p>
  60. <p><b>骞冲潎杩涙按娴侀噺:</b> {avgInflow:F1} m鲁/h | <b>骞冲潎鍑烘按娴侀噺:</b> {avgOutflow:F1} m鲁/h</p>
  61. <p><b>鏈澶ц繘姘存祦閲:</b> {maxInflow:F1} m鲁/h | <b>鏈澶у嚭姘存祦閲:</b> {maxOutflow:F1} m鲁/h</p>
  62. <p><b>璁板綍鏁:</b> {flowRecords.Count} 鏉</p>
  63. </div>
  64. <h2>鎶ヨ缁熻</h2>
  65. <div class='summary'>
  66. <p><b>鎶ヨ鎬绘暟:</b> {alarmRecords.Count} 鏉</p>
  67. <p><b>鏈‘璁:</b> {alarmRecords.Count(r => !r.IsConfirmed)} 鏉</p>
  68. <p><b>宸茬‘璁:</b> {alarmRecords.Count(r => r.IsConfirmed)} 鏉</p>
  69. </div>
  70. {(alarmRecords.Count > 0 ? $@"
  71. <h2>鎶ヨ璇︽儏</h2>
  72. <table>
  73. <tr><th>鏃堕棿</th><th>绫诲瀷</th><th>鍐呭</th><th>绾у埆</th><th>鐘舵</th></tr>
  74. {string.Join("", alarmRecords.Take(50).Select(a => $@"
  75. <tr>
  76. <td>{a.AlarmTime:HH:mm:ss}</td>
  77. <td>{a.AlarmType}</td>
  78. <td>{a.AlarmMessage}</td>
  79. <td>{a.AlarmLevel}</td>
  80. <td>{(a.IsConfirmed ? "宸茬‘璁" : "<span class='alarm'>鏈‘璁</span>")}</td>
  81. </tr>"))}
  82. </table>" : "")}
  83. <div class='footer'>
  84. <p>鎶ヨ〃鐢熸垚鏃堕棿: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | YZWater 姹℃按澶勭悊鐩戞帶绯荤粺</p>
  85. </div>
  86. </body>
  87. </html>";
  88. // 淇濆瓨鏂囦欢
  89. var reportsDir = "Reports";
  90. Directory.CreateDirectory(reportsDir);
  91. var filePath = Path.Combine(reportsDir, $"鏃ユ姤_{date:yyyyMMdd}.html");
  92. await File.WriteAllTextAsync(filePath, html);
  93. Log.Information("鏃ユ姤宸茬敓鎴: {Path}", filePath);
  94. AuditService.Log("绯荤粺", "Report", $"鐢熸垚鏃ユ姤: {filePath}");
  95. return filePath;
  96. }
  97. catch (Exception ex)
  98. {
  99. Log.Error(ex, "鐢熸垚鏃ユ姤澶辫触");
  100. return string.Empty;
  101. }
  102. }
  103. /// <summary>
  104. /// 鐢熸垚鏈堟姤
  105. /// </summary>
  106. public static async Task<string> GenerateMonthlyReportAsync(int year, int month)
  107. {
  108. try
  109. {
  110. var start = new DateTime(year, month, 1);
  111. var end = start.AddMonths(1);
  112. var flowRecords = await DatabaseService.Db.Queryable<FlowRecord>()
  113. .Where(r => r.RecordTime >= start && r.RecordTime < end)
  114. .ToListAsync();
  115. var alarmRecords = await DatabaseService.Db.Queryable<AlarmRecord>()
  116. .Where(r => r.AlarmTime >= start && r.AlarmTime < end)
  117. .ToListAsync();
  118. var config = ConfigService.GetConfig();
  119. var companyName = config?.CompanyName ?? "姹℃按澶勭悊鍘";
  120. var avgInflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.InflowRate) : 0;
  121. var avgOutflow = flowRecords.Count > 0 ? flowRecords.Average(r => r.OutflowRate) : 0;
  122. var totalInflow = flowRecords.Count > 0 ? flowRecords.Last().TotalInflow : 0;
  123. var totalOutflow = flowRecords.Count > 0 ? flowRecords.Last().TotalOutflow : 0;
  124. // 鎸夋棩缁熻
  125. var dailyStats = flowRecords
  126. .GroupBy(r => r.RecordTime.Date)
  127. .Select(g => new
  128. {
  129. Date = g.Key,
  130. AvgInflow = g.Average(r => r.InflowRate),
  131. AvgOutflow = g.Average(r => r.OutflowRate),
  132. Count = g.Count()
  133. })
  134. .OrderBy(d => d.Date)
  135. .ToList();
  136. var html = $@"<!DOCTYPE html>
  137. <html>
  138. <head>
  139. <meta charset='utf-8'>
  140. <title>鏈堟姤 - {year}骞磠month}鏈</title>
  141. <style>
  142. body {{ font-family: 'Microsoft YaHei', sans-serif; margin: 20px; }}
  143. h1 {{ color: #1976D2; border-bottom: 2px solid #1976D2; padding-bottom: 8px; }}
  144. h2 {{ color: #424242; margin-top: 24px; }}
  145. table {{ border-collapse: collapse; width: 100%; margin: 12px 0; }}
  146. th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
  147. th {{ background: #f5f5f5; }}
  148. .summary {{ background: #E3F2FD; padding: 16px; border-radius: 8px; margin: 16px 0; }}
  149. .footer {{ color: #999; font-size: 12px; margin-top: 32px; border-top: 1px solid #eee; padding-top: 8px; }}
  150. </style>
  151. </head>
  152. <body>
  153. <h1>{companyName} - 鏈堟姤</h1>
  154. <p>鏈熼棿: {year}骞磠month}鏈</p>
  155. <h2>姹囨荤粺璁</h2>
  156. <div class='summary'>
  157. <p><b>绱杩涙按閲:</b> {totalInflow:F1} m鲁 | <b>绱鍑烘按閲:</b> {totalOutflow:F1} m鲁</p>
  158. <p><b>骞冲潎杩涙按娴侀噺:</b> {avgInflow:F1} m鲁/h | <b>骞冲潎鍑烘按娴侀噺:</b> {avgOutflow:F1} m鲁/h</p>
  159. <p><b>鎶ヨ鎬绘暟:</b> {alarmRecords.Count} 鏉</p>
  160. <p><b>鏈夋晥璁板綍澶╂暟:</b> {dailyStats.Count} 澶</p>
  161. </div>
  162. <h2>姣忔棩缁熻</h2>
  163. <table>
  164. <tr><th>鏃ユ湡</th><th>骞冲潎杩涙按 (m鲁/h)</th><th>骞冲潎鍑烘按 (m鲁/h)</th><th>璁板綍鏁</th></tr>
  165. {string.Join("", dailyStats.Select(d => $@"
  166. <tr>
  167. <td>{d.Date:MM-dd}</td>
  168. <td>{d.AvgInflow:F1}</td>
  169. <td>{d.AvgOutflow:F1}</td>
  170. <td>{d.Count}</td>
  171. </tr>"))}
  172. </table>
  173. <div class='footer'>
  174. <p>鎶ヨ〃鐢熸垚鏃堕棿: {DateTime.Now:yyyy-MM-dd HH:mm:ss} | YZWater 姹℃按澶勭悊鐩戞帶绯荤粺</p>
  175. </div>
  176. </body>
  177. </html>";
  178. var reportsDir = "Reports";
  179. Directory.CreateDirectory(reportsDir);
  180. var filePath = Path.Combine(reportsDir, $"鏈堟姤_{year}{month:D2}.html");
  181. await File.WriteAllTextAsync(filePath, html);
  182. Log.Information("鏈堟姤宸茬敓鎴: {Path}", filePath);
  183. AuditService.Log("绯荤粺", "Report", $"鐢熸垚鏈堟姤: {filePath}");
  184. return filePath;
  185. }
  186. catch (Exception ex)
  187. {
  188. Log.Error(ex, "鐢熸垚鏈堟姤澶辫触");
  189. return string.Empty;
  190. }
  191. }
  192. }