SystemDatabaseService.cs 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. using NPOI.Util;
  2. using SqlSugar;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using TeamAAS_VP.Interfaces;
  8. using TeamAAS_VP.Models;
  9. namespace TeamAAS_VP.Data
  10. {
  11. /// <summary>
  12. /// 系统数据库服务,基于 <see cref="ISqlSugarClient"/> 提供对应用数据的访问。
  13. /// 包含用户增删改查、用户身份验证以及生产记录的写入与统计功能。
  14. /// </summary>
  15. public class SystemDatabaseService : ISystemDatabaseService
  16. {
  17. private readonly ISqlSugarClient _db;
  18. private User CurrentUser { get; set; }
  19. /// <summary>
  20. /// 使用指定的 SqlSugar 客户端构造数据库服务实例。
  21. /// </summary>
  22. /// <param name="db">已配置的 <see cref="ISqlSugarClient"/> 实例。</param>
  23. public SystemDatabaseService(ISqlSugarClient db)
  24. {
  25. _db = db;
  26. //GenerateTestDataAsync();
  27. }
  28. #region 用户
  29. // User CRUD
  30. /// <summary>
  31. /// 添加新用户到数据库。
  32. /// 对于 SqlSugar,User.Id 为数据库自增值,因此在此方法中不应设定 Id。
  33. /// </summary>
  34. /// <param name="user">要添加的用户对象,不能为空。</param>
  35. /// <returns>返回传入的用户对象(Id 由数据库生成后会回写到该对象)。</returns>
  36. /// <exception cref="ArgumentNullException">当 <paramref name="user"/> 为 null 时抛出。</exception>
  37. public async Task<User> AddUserAsync(User user)
  38. {
  39. if (user == null) throw new ArgumentNullException(nameof(user));
  40. // for SqlSugar User.Id is int identity, so do not set it here
  41. await _db.Insertable(user).ExecuteCommandAsync();
  42. return user;
  43. }
  44. /// <summary>
  45. /// 根据用户 Id 删除用户。
  46. /// </summary>
  47. /// <param name="userId">要删除的用户 Id。</param>
  48. /// <returns>如果删除了至少一条记录则返回 true,否则返回 false。</returns>
  49. public async Task<bool> DeleteUserAsync(int userId)
  50. {
  51. var r = await _db.Deleteable<User>().In(userId).ExecuteCommandAsync();
  52. return r > 0;
  53. }
  54. /// <summary>
  55. /// 获取所有用户。
  56. /// </summary>
  57. /// <returns>用户列表的异步枚举。</returns>
  58. public async Task<IEnumerable<User>> GetAllUsersAsync()
  59. {
  60. return await _db.Queryable<User>().ToListAsync();
  61. }
  62. /// <summary>
  63. /// 根据用户 Id 查询单个用户。
  64. /// </summary>
  65. /// <param name="userId">用户 Id。</param>
  66. /// <returns>匹配的用户,如果未找到则返回 null(取决于 SqlSugar 的行为)。</returns>
  67. public async Task<User> GetUserByIdAsync(int userId)
  68. {
  69. return await _db.Queryable<User>().InSingleAsync(userId);
  70. }
  71. /// <summary>
  72. /// 更新用户信息并返回更新后的对象。
  73. /// </summary>
  74. /// <param name="user">要更新的用户对象,不能为空。</param>
  75. /// <returns>返回传入的用户对象。</returns>
  76. /// <exception cref="ArgumentNullException">当 <paramref name="user"/> 为 null 时抛出。</exception>
  77. public async Task<User> UpdateUserAsync(User user)
  78. {
  79. if (user == null) throw new ArgumentNullException(nameof(user));
  80. var r = await _db.Updateable(user).ExecuteCommandAsync();
  81. return user;
  82. }
  83. // User authentication
  84. /// <summary>
  85. /// 验证用户名和密码并返回匹配的用户。
  86. /// 注意:当前实现直接以明文密码比较,请根据需要在调用侧或更高层添加哈希/盐策略。
  87. /// </summary>
  88. /// <param name="username">用户名,不能为空或空字符串。</param>
  89. /// <param name="password">密码,不能为空或空字符串。</param>
  90. /// <returns>匹配的用户对象,如果未找到则可能抛出或返回 null(取决于 SqlSugar 的 FirstAsync 行为)。</returns>
  91. /// <exception cref="ArgumentException">当 <paramref name="username"/> 或 <paramref name="password"/> 为空时抛出。</exception>
  92. public async Task<User> AuthenticateUserAsync(string username, string password)
  93. {
  94. if (string.IsNullOrEmpty(username)) throw new ArgumentException("username is required", nameof(username));
  95. if (string.IsNullOrEmpty(password)) throw new ArgumentException("password is required", nameof(password));
  96. return await _db.Queryable<User>().Where(u => u.UserName == username && u.UserPassword == password).FirstAsync();
  97. }
  98. // User login / current user helpers
  99. /// <summary>
  100. /// 将指定用户设为当前用户(写入一条成功的登录记录)。
  101. /// </summary>
  102. /// <param name="userId">要设为当前用户的用户 Id。</param>
  103. public async Task SetCurrentUserAsync(int userId)
  104. {
  105. // 验证用户存在
  106. var user = await GetUserByIdAsync(userId);
  107. if (user == null)
  108. {
  109. throw new ArgumentException($"User with id {userId} not found", nameof(userId));
  110. }
  111. CurrentUser = user;
  112. // 记录一条成功的登录记录
  113. var rec = new UserLoginRecord
  114. {
  115. UserId = userId,
  116. Success = true,
  117. Time = DateTime.Now,
  118. Message = "Set as current user"
  119. };
  120. await RecordUserLoginAsync(rec);
  121. }
  122. /// <summary>
  123. /// 将指定用户设为当前用户(会写入一条登录记录,标记为成功)
  124. /// </summary>
  125. /// <param name="user"></param>
  126. /// <returns></returns>
  127. public async Task SetCurrentUserAsync(User user)
  128. {
  129. CurrentUser = user;
  130. // 记录一条成功的登录记录
  131. var rec = new UserLoginRecord
  132. {
  133. UserId = user.Id,
  134. Success = true,
  135. Time = DateTime.Now,
  136. Message = user.UserName
  137. };
  138. await RecordUserLoginAsync(rec);
  139. }
  140. /// <summary>
  141. /// 获取最近一次成功登录的用户(视为当前用户)。
  142. /// </summary>
  143. /// <returns>若存在成功登录记录则返回对应用户,否则返回 null。</returns>
  144. public Task<User> GetCurrentUserAsync()
  145. {
  146. return Task.FromResult(GetCurrentUser());
  147. }
  148. /// <summary>
  149. /// 获取当前用户
  150. /// </summary>
  151. /// <returns></returns>
  152. public User GetCurrentUser()
  153. {
  154. //var recs = _db.Queryable<UserLoginRecord>()
  155. // .Where(r => r.Success == true)
  156. // .OrderBy(r => r.Time, OrderByType.Desc)
  157. // .Take(1)
  158. // .ToList();
  159. //var rec = recs.FirstOrDefault();
  160. ////如果没有登录记录则返回一个默认的用户,即第一个用户
  161. //if (rec == null) return _db.Queryable<User>().First();
  162. //return _db.Queryable<User>().InSingle(rec.UserId);
  163. if (CurrentUser == null)
  164. {
  165. CurrentUser = _db.Queryable<User>().ToList().FirstOrDefault();
  166. }
  167. return CurrentUser;
  168. }
  169. /// <summary>
  170. /// 记录一条用户登录记录(成功或失败)。
  171. /// </summary>
  172. /// <param name="record">登录记录对象,不能为空。</param>
  173. public async Task RecordUserLoginAsync(UserLoginRecord record)
  174. {
  175. if (record == null) throw new ArgumentNullException(nameof(record));
  176. if (record.Time == default(DateTime)) record.Time = DateTime.Now;
  177. await _db.Insertable(record).ExecuteCommandAsync();
  178. }
  179. /// <summary>
  180. /// 获取指定用户最近的一条登录记录(无论是否成功)。
  181. /// </summary>
  182. /// <param name="userId">用户 Id。</param>
  183. /// <returns>最近的一条登录记录或 null。</returns>
  184. public async Task<UserLoginRecord> GetLastUserLoginRecordAsync(int userId)
  185. {
  186. var recs = await _db.Queryable<UserLoginRecord>()
  187. .Where(r => r.UserId == userId)
  188. .OrderBy(r => r.Time, OrderByType.Desc)
  189. .Take(1)
  190. .ToListAsync();
  191. return recs.FirstOrDefault();
  192. }
  193. /// <summary>
  194. /// 将指定用户名标记为“记住”,并取消其他用户的记住标志。
  195. /// </summary>
  196. /// <param name="username">要记住的用户名。</param>
  197. public async Task SetRememberUserAsync(string username)
  198. {
  199. if (string.IsNullOrEmpty(username)) throw new ArgumentException("username is required", nameof(username));
  200. // 使用事务先清除所有记住标志,再为指定用户设置记住
  201. try
  202. {
  203. _db.Ado.BeginTran();
  204. // 清除所有
  205. await _db.Updateable<User>().SetColumns(u => new User { IsRemember = false }).Where("1=1").ExecuteCommandAsync();
  206. // 设置指定用户为记住
  207. await _db.Updateable<User>().SetColumns(u => new User { IsRemember = true }).Where(u => u.UserName == username).ExecuteCommandAsync();
  208. _db.Ado.CommitTran();
  209. }
  210. catch
  211. {
  212. try { _db.Ado.RollbackTran(); } catch { }
  213. throw;
  214. }
  215. }
  216. /// <summary>
  217. /// 清除所有用户的记住标志。
  218. /// </summary>
  219. public async Task ClearRememberUserAsync()
  220. {
  221. await _db.Updateable<User>().SetColumns(u => new User { IsRemember = false }).ExecuteCommandAsync();
  222. }
  223. /// <summary>
  224. /// 获取当前被标记为记住的用户(如果存在)。
  225. /// </summary>
  226. /// <returns>被记住的用户或 null。</returns>
  227. public async Task<User> GetRememberedUserAsync()
  228. {
  229. return await _db.Queryable<User>().Where(u => u.IsRemember == true).FirstAsync();
  230. }
  231. #endregion
  232. #region 生产记录
  233. // Production records
  234. /// <summary>
  235. /// 记录一次生产事件。如果 <see cref="ProductionRecord.Id"/> 为 <see cref="Guid.Empty"/>
  236. /// 则会自动生成新的 Guid。
  237. /// </summary>
  238. /// <param name="record">生产记录对象,不能为空。</param>
  239. /// <returns>异步任务。</returns>
  240. /// <exception cref="ArgumentNullException">当 <paramref name="record"/> 为 null 时抛出。</exception>
  241. public async Task RecordProductionAsync(ProductionRecord record)
  242. {
  243. if (record == null) throw new ArgumentNullException(nameof(record));
  244. // 如果未指定产生该记录的用户名,尝试使用当前登录用户
  245. if (string.IsNullOrEmpty(record.UserName))
  246. {
  247. try
  248. {
  249. var current = await GetCurrentUserAsync();
  250. if (current != null)
  251. {
  252. record.UserName = current.UserName;
  253. }
  254. }
  255. catch
  256. {
  257. // 忽略获取当前用户时的错误,继续保存记录(保守策略)
  258. }
  259. }
  260. record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
  261. await _db.Insertable(record).ExecuteCommandAsync();
  262. }
  263. /// <summary>
  264. /// 重置生产记录。若未传入 <paramref name="productName"/>
  265. /// 则清空所有生产记录;否则仅删除指定产品名的记录。
  266. /// </summary>
  267. /// <param name="productName">可选的产品名过滤。</param>
  268. /// <returns>异步任务。</returns>
  269. public async Task ResetProductionAsync(string productName = null)
  270. {
  271. if (string.IsNullOrEmpty(productName))
  272. {
  273. await _db.Deleteable<ProductionRecord>().Where("1=1").ExecuteCommandAsync();
  274. }
  275. else
  276. {
  277. await _db.Deleteable<ProductionRecord>().Where(r => r.ProductName == productName).ExecuteCommandAsync();
  278. }
  279. }
  280. /// <summary>
  281. /// 获取指定产品(和可选类别)在数据库中累计的产量总和(仅统计 Quantity &gt; 0 的记录)。
  282. /// </summary>
  283. /// <param name="productName">产品名,不能为空或空字符串。</param>
  284. /// <param name="categories">可选的类别集合,用于过滤。</param>
  285. /// <returns>指定条件下的产量总和(整数)。</returns>
  286. /// <exception cref="ArgumentException">当 <paramref name="productName"/> 为空时抛出。</exception>
  287. public async Task<int> GetTotalProductionAsync(string productName, IEnumerable<string> categories = null)
  288. {
  289. if (string.IsNullOrEmpty(productName)) throw new ArgumentException("productName is required", nameof(productName));
  290. var q = _db.Queryable<ProductionRecord>().Where(r => r.ProductName == productName && r.Quantity > 0);
  291. if (categories != null && categories.Any())
  292. {
  293. q = q.Where(r => categories.Contains(r.Category));
  294. }
  295. var sum = await q.SumAsync<int>(r => r.Quantity);
  296. return sum;
  297. }
  298. /// <summary>
  299. /// 获取生产统计结果,按指定时间范围、产品名和可选类别筛选,
  300. /// 并根据 <see cref="ProductionStatQuery.GroupBy"/>(hour/day/week/month/total)进行分组聚合。
  301. /// </summary>
  302. /// <param name="query">生产统计查询对象,不能为空,必须包含 ProductName/Start/End 等。</param>
  303. /// <returns>按指定分组粒度返回的统计结果集合。</returns>
  304. /// <exception cref="ArgumentNullException">当 <paramref name="query"/> 为 null 时抛出。</exception>
  305. public async Task<IEnumerable<ProductionStatResult>> GetProductionStatsAsync(ProductionStatQuery query)
  306. {
  307. if (query == null) throw new ArgumentNullException(nameof(query));
  308. if (string.IsNullOrEmpty(query.ProductName)) throw new ArgumentException("ProductName is required in query", nameof(query));
  309. var q = _db.Queryable<ProductionRecord>().Where(r => r.ProductName == query.ProductName && r.Timestamp >= query.Start && r.Timestamp < query.End);
  310. if (query.Categories != null && query.Categories.Any())
  311. {
  312. q = q.Where(r => query.Categories.Contains(r.Category));
  313. }
  314. var list = await q.ToListAsync();
  315. var results = new List<ProductionStatResult>();
  316. switch ((query.GroupBy ?? "total").ToLower())
  317. {
  318. case "hour":
  319. // group by hour
  320. var hours = list.GroupBy(r => new DateTime(r.Timestamp.Year, r.Timestamp.Month, r.Timestamp.Day, r.Timestamp.Hour, 0, 0))
  321. .OrderBy(g => g.Key);
  322. foreach (var g in hours)
  323. {
  324. results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddHours(1), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName });
  325. }
  326. break;
  327. case "day":
  328. var days = list.GroupBy(r => r.Timestamp.Date).OrderBy(g => g.Key);
  329. foreach (var g in days)
  330. {
  331. results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddDays(1), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName });
  332. }
  333. break;
  334. case "week":
  335. // ISO week grouping by week start (Monday)
  336. var weeks = list.GroupBy(r => FirstDayOfWeek(r.Timestamp)).OrderBy(g => g.Key);
  337. foreach (var g in weeks)
  338. {
  339. results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddDays(7), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName });
  340. }
  341. break;
  342. case "month":
  343. var months = list.GroupBy(r => new DateTime(r.Timestamp.Year, r.Timestamp.Month, 1)).OrderBy(g => g.Key);
  344. foreach (var g in months)
  345. {
  346. results.Add(new ProductionStatResult { PeriodStart = g.Key, PeriodEnd = g.Key.AddMonths(1), Quantity = g.Sum(x => x.Quantity), ProductName = query.ProductName });
  347. }
  348. break;
  349. default:
  350. results.Add(new ProductionStatResult { PeriodStart = query.Start, PeriodEnd = query.End, Quantity = list.Sum(x => x.Quantity), ProductName = query.ProductName });
  351. break;
  352. }
  353. return results;
  354. }
  355. /// <summary>
  356. /// 获取所有已知分类相加起来的今天的产能
  357. /// </summary>
  358. /// <param name="productName"></param>
  359. /// <returns></returns>
  360. public async Task<int> GetTodayProductionAsync(string productName)
  361. {
  362. //今天的起始时间
  363. var todayStart = DateTime.Now.Date;
  364. //今天的结束时间
  365. var todayEnd = DateTime.Now.Date;
  366. todayEnd = todayEnd.AddDays(1);
  367. //按照产品名称和时间范围查询生产记录的总数
  368. var total = await _db.Queryable<ProductionRecord>()
  369. .Where(r => r.ProductName == productName && r.Timestamp >= todayStart && r.Timestamp < todayEnd)
  370. .SumAsync(r => r.Quantity);
  371. return total;
  372. }
  373. /// <summary>
  374. /// 获取所有已知分类相加起来的当前时间往前的一个小时的产能
  375. /// </summary>
  376. /// <param name="productName"></param>
  377. /// <returns></returns>
  378. public async Task<int> GetLastHourProductionAsync(string productName)
  379. {
  380. var lastHourStart = DateTime.Now.AddHours(-1);
  381. var lastHourEnd = DateTime.Now;
  382. //按照产品名称和时间范围查询生产记录的总数
  383. var total = await _db.Queryable<ProductionRecord>()
  384. .Where(r => r.ProductName == productName && r.Timestamp >= lastHourStart && r.Timestamp < lastHourEnd)
  385. .SumAsync(r => r.Quantity);
  386. return total;
  387. }
  388. /// <summary>
  389. /// 获取今天ng的数量
  390. /// </summary>
  391. /// <param name="productName"></param>
  392. /// <returns></returns>
  393. public async Task<int> GetNGProductionAsync(string productName)
  394. {
  395. //今天的起始时间
  396. var todayStart = DateTime.Now.Date;
  397. //今天的结束时间
  398. var todayEnd = DateTime.Now.Date;
  399. todayEnd = todayEnd.AddDays(1);
  400. //按照产品名称和时间范围查询生产记录的总数
  401. var total = await _db.Queryable<ProductionRecord>()
  402. .Where(r => r.ProductName == productName && r.Timestamp >= todayStart && r.Timestamp < todayEnd&&r.Category=="NG")
  403. .SumAsync(r => r.Quantity);
  404. return total;
  405. }
  406. /// <summary>
  407. /// 获取当前月的产能
  408. /// </summary>
  409. /// <param name="productName"></param>
  410. /// <returns></returns>
  411. public Task<int> GetCurrentMonthProductionAsync(string productName)
  412. {
  413. var now = DateTime.Now;
  414. var monthStart = new DateTime(now.Year, now.Month, 1);
  415. var monthEnd = monthStart.AddMonths(1);
  416. //按照产品名称和时间范围查询生产记录的总数
  417. return _db.Queryable<ProductionRecord>()
  418. .Where(r => r.ProductName == productName && r.Timestamp >= monthStart && r.Timestamp < monthEnd)
  419. .SumAsync(r => r.Quantity);
  420. }
  421. /// <summary>
  422. /// 获取当前周的产能
  423. /// </summary>
  424. /// <param name="productName"></param>
  425. /// <returns></returns>
  426. public Task<int> GetCurrentWeekProductionAsync(string productName)
  427. {
  428. var now = DateTime.Now;
  429. var weekStart = FirstDayOfWeek(now);
  430. var weekEnd = weekStart.AddDays(7);
  431. //按照产品名称和时间范围查询生产记录的总数
  432. return _db.Queryable<ProductionRecord>()
  433. .Where(r => r.ProductName == productName && r.Timestamp >= weekStart && r.Timestamp < weekEnd)
  434. .SumAsync(r => r.Quantity);
  435. }
  436. /// <summary>
  437. /// 计算给定日期所属周的第一天(以星期一为一周的第一天)。
  438. /// 返回结果为日期的 Date 部分(时间为 00:00:00)。
  439. /// </summary>
  440. /// <param name="dt">输入日期。</param>
  441. /// <returns>该日期对应周的周一日期(Date 部分)。</returns>
  442. private DateTime FirstDayOfWeek(DateTime dt)
  443. {
  444. var diff = (7 + (dt.DayOfWeek - DayOfWeek.Monday)) % 7;
  445. return dt.Date.AddDays(-1 * diff);
  446. }
  447. /// <summary>
  448. /// 获取指定产品名称的总产能
  449. /// </summary>
  450. /// <param name="productName"></param>
  451. /// <returns></returns>
  452. public Task<int> GetOverallProductionAsync(string productName)
  453. {
  454. return _db.Queryable<ProductionRecord>()
  455. .Where(r => r.ProductName == productName)
  456. .SumAsync(r => r.Quantity);
  457. }
  458. /// <summary>
  459. /// 获取指定产品的所有已知分类。
  460. /// </summary>
  461. /// <param name="productName">要查询分类的产品名称。</param>
  462. /// <returns>
  463. /// 一个字符串集合,包含该产品的所有分类名称;如果没有分类,返回空集合或 null(建议返回空集合)。
  464. /// </returns>
  465. public async Task<IEnumerable<string>> GetProductCategoriesAsync(string productName)
  466. {
  467. var categories = await _db.Queryable<ProductionRecord>()
  468. .Where(r => r.ProductName == productName)
  469. .Select(r => r.Category)
  470. .Distinct()
  471. .ToListAsync();
  472. return categories;
  473. }
  474. /// <summary>
  475. /// 获取指定产品名称的所有记录按照分类的产能统计
  476. /// </summary>
  477. /// <param name="productName"></param>
  478. /// <returns></returns>
  479. public async Task<Dictionary<string, int>> GetProductionByCategoriesAsync(string productName)
  480. {
  481. var list = await _db.Queryable<ProductionRecord>()
  482. .Where(r => r.ProductName == productName)
  483. .ToListAsync();
  484. var dict = list.GroupBy(r => r.Category)
  485. .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity));
  486. return dict;
  487. }
  488. /// <summary>
  489. /// 获取指定产品名称的一个月内按照每天统计对应的类别和数量
  490. /// </summary>
  491. /// <param name="productName"></param>
  492. /// <returns></returns>
  493. public async Task<Dictionary<DateTime, Dictionary<string, int>>> GetMonthlyProductionByDayAndCategoryAsync(string productName)
  494. {
  495. var now = DateTime.Now;
  496. var monthStart = new DateTime(now.Year, now.Month, 1);
  497. var monthEnd = monthStart.AddMonths(1);
  498. var list = await _db.Queryable<ProductionRecord>()
  499. .Where(r => r.ProductName == productName && (r.Timestamp >= monthStart && r.Timestamp < monthEnd))
  500. .ToListAsync();
  501. var dict = new Dictionary<DateTime, Dictionary<string, int>>();
  502. var days = list.GroupBy(r => r.Timestamp.Date).OrderBy(g => g.Key);
  503. foreach (var dayGroup in days)
  504. {
  505. var categoryDict = dayGroup.GroupBy(r => r.Category)
  506. .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity));
  507. dict[dayGroup.Key] = categoryDict;
  508. }
  509. return dict;
  510. }
  511. /// <summary>
  512. /// 获取指定产品名称的一个星期内按照每天统计对应的类别和数量
  513. /// </summary>
  514. /// <param name="productName"></param>
  515. /// <returns></returns>
  516. public async Task<Dictionary<int, Dictionary<string, int>>> GetWeeklyProductionByDayAndCategoryAsync(string productName)
  517. {
  518. var now = DateTime.Now;
  519. var weekStart = FirstDayOfWeek(now);
  520. var weekEnd = weekStart.AddDays(7);
  521. var list = await _db.Queryable<ProductionRecord>()
  522. .Where(r => r.ProductName == productName && r.Timestamp >= weekStart && r.Timestamp < weekEnd)
  523. .ToListAsync();
  524. var dict = new Dictionary<int, Dictionary<string, int>>();
  525. var days = list.GroupBy(r => r.Timestamp.DayOfWeek).OrderBy(g => g.Key);
  526. foreach (var dayGroup in days)
  527. {
  528. var categoryDict = dayGroup.GroupBy(r => r.Category)
  529. .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity));
  530. dict[(int)dayGroup.Key] = categoryDict;
  531. }
  532. return dict;
  533. }
  534. /// <summary>
  535. /// 获取指定产品名称的当天内按照每小时统计对应的类别和数量
  536. /// </summary>
  537. /// <param name="productName"></param>
  538. /// <returns></returns>
  539. public async Task<Dictionary<int, Dictionary<string, int>>> GetDailyProductionByHourAndCategoryAsync(string productName)
  540. {
  541. var now = DateTime.Now;
  542. var dayStart = now.Date;
  543. var dayEnd = dayStart.AddDays(1);
  544. var list = await _db.Queryable<ProductionRecord>()
  545. .Where(r => r.ProductName == productName && r.Timestamp >= dayStart && r.Timestamp < dayEnd)
  546. .ToListAsync();
  547. var dict = new Dictionary<int, Dictionary<string, int>>();
  548. var hours = list.GroupBy(r => r.Timestamp.Hour).OrderBy(g => g.Key);
  549. foreach (var hourGroup in hours)
  550. {
  551. var categoryDict = hourGroup.GroupBy(r => r.Category)
  552. .ToDictionary(g => g.Key, g => g.Sum(r => r.Quantity));
  553. dict[hourGroup.Key] = categoryDict;
  554. }
  555. return dict;
  556. }
  557. /// <summary>
  558. /// 分页查询生产记录
  559. /// </summary>
  560. public async Task<(IEnumerable<ProductionRecord> Items, int TotalCount)> QueryProductionRecordsPagedAsync(DateTime start, DateTime end, int pageIndex, int pageSize, string productName = null, string category = null)
  561. {
  562. if (pageIndex < 1) pageIndex = 1;
  563. if (pageSize < 1) pageSize = 20;
  564. var q = _db.Queryable<ProductionRecord>().Where(r => r.Timestamp >= start && r.Timestamp < end);
  565. if (!string.IsNullOrEmpty(productName)) q = q.Where(r => r.ProductName == productName);
  566. if (!string.IsNullOrEmpty(category)) q = q.Where(r => r.Category == category);
  567. var total = await q.CountAsync();
  568. var list = await q.OrderBy(r => r.Timestamp, OrderByType.Desc).ToPageListAsync(pageIndex, pageSize);
  569. return (list, total);
  570. }
  571. #endregion
  572. #region 报警记录
  573. // Alarm Records
  574. /// <summary>
  575. /// 记录一条报警记录。
  576. /// </summary>
  577. /// <param name="alarm">报警记录对象,不能为空。</param>
  578. public async Task RecordAlarmAsync(AlarmRecord alarm)
  579. {
  580. if (alarm == null) throw new ArgumentNullException(nameof(alarm));
  581. if (alarm.Time == default(DateTime)) alarm.Time = DateTime.Now;
  582. await _db.Insertable(alarm).ExecuteCommandAsync();
  583. }
  584. /// <summary>
  585. /// 按时间范围和可选条件查询报警记录。
  586. /// </summary>
  587. /// <param name="start">起始时间(含)。</param>
  588. /// <param name="end">结束时间(不含)。</param>
  589. /// <param name="code">可选的报警编号过滤。</param>
  590. /// <param name="source">可选的报警来源过滤。</param>
  591. /// <returns>满足条件的报警记录集合。</returns>
  592. public async Task<IEnumerable<AlarmRecord>> QueryAlarmsAsync(DateTime start, DateTime end, string code = null, string source = null)
  593. {
  594. var q = _db.Queryable<AlarmRecord>().Where(a => a.Time >= start && a.Time < end);
  595. if (!string.IsNullOrEmpty(code)) q = q.Where(a => a.Code == code);
  596. if (!string.IsNullOrEmpty(source)) q = q.Where(a => a.Source == source);
  597. return await q.OrderBy(a => a.Time, OrderByType.Desc).ToListAsync();
  598. }
  599. #endregion
  600. #region 锁付结果记录
  601. // Lock result methods
  602. /// <summary>
  603. /// 记录单次锁付结果(WaveDatas 不写入数据库)
  604. /// </summary>
  605. /// <param name="result"></param>
  606. public async Task RecordLockResultAsync(LockResult result)
  607. {
  608. try
  609. {
  610. if (result == null) throw new ArgumentNullException(nameof(result));
  611. if (result.Timestamp == default(DateTime)) result.Timestamp = DateTime.Now;
  612. await _db.Insertable(result).ExecuteCommandAsync();
  613. }
  614. catch (Exception)
  615. {
  616. }
  617. }
  618. /// <summary>
  619. /// 分页查询锁付结果
  620. /// </summary>
  621. public async Task<(IEnumerable<LockResult> Items, int TotalCount)> QueryLockResultsAsync(DateTime start, DateTime end, int pageIndex, int pageSize, string productNumber = null, string productName = null, int? screwNumber = null, bool? lockPassed = null)
  622. {
  623. if (pageIndex < 1) pageIndex = 1;
  624. if (pageSize < 1) pageSize = 20;
  625. var q = _db.Queryable<LockResult>().Where(r => r.Timestamp >= start && r.Timestamp < end);
  626. if (!string.IsNullOrEmpty(productNumber)) q = q.Where(r => r.ProductNumber == productNumber);
  627. if (screwNumber.HasValue) q = q.Where(r => r.Number == screwNumber.Value);
  628. if (lockPassed.HasValue) q = q.Where(r => r.LockPassed == lockPassed.Value);
  629. if (!string.IsNullOrEmpty(productName)) q = q.Where(r => r.ProductName == productName);
  630. var total = await q.CountAsync();
  631. var list = await q.OrderBy(r => r.Timestamp, OrderByType.Desc).ToPageListAsync(pageIndex, pageSize);
  632. return (list, total);
  633. }
  634. /// <summary>
  635. /// 查询所有符合条件的锁付结果(不分页)。
  636. /// </summary>
  637. public async Task<IEnumerable<LockResult>> QueryAllLockResultsAsync(DateTime start, DateTime end, string productNumber = null, string productName = null, int? screwNumber = null, bool? lockPassed = null)
  638. {
  639. var q = _db.Queryable<LockResult>().Where(r => r.Timestamp >= start && r.Timestamp < end);
  640. if (!string.IsNullOrEmpty(productNumber)) q = q.Where(r => r.ProductNumber == productNumber);
  641. if (screwNumber.HasValue) q = q.Where(r => r.Number == screwNumber.Value);
  642. if (lockPassed.HasValue) q = q.Where(r => r.LockPassed == lockPassed.Value);
  643. if (!string.IsNullOrEmpty(productName)) q = q.Where(r => r.ProductName == productName);
  644. var list = await q.OrderBy(r => r.Timestamp, OrderByType.Desc).ToListAsync();
  645. return list;
  646. }
  647. #endregion
  648. #region 螺丝供料器批次记录
  649. /// <summary>
  650. /// 记录一次螺丝供料器批次更换记录
  651. /// </summary>
  652. /// <param name="record"></param>
  653. /// <returns></returns>
  654. public async Task RecordScrewFeederBatchAsync(ScrewFeederBatchRecord record)
  655. {
  656. if (record == null) throw new ArgumentNullException(nameof(record));
  657. if (record.ChangeTime == default(DateTime)) record.ChangeTime = DateTime.Now;
  658. record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
  659. await _db.Insertable(record).ExecuteCommandAsync();
  660. }
  661. /// <summary>
  662. /// 查询螺丝供料器批次记录。
  663. /// 可按时间段、批次号精确匹配及供料器编号过滤。所有参数均为可选,传入 null 表示不作为过滤条件。
  664. /// </summary>
  665. /// <param name="start">可选:起始时间(包含)</param>
  666. /// <param name="end">可选:结束时间(包含)</param>
  667. /// <param name="batchNumber">可选:批次号(精确匹配)</param>
  668. /// <param name="feederNumber">可选:供料器编号</param>
  669. /// <returns>满足条件的记录集合(按 ChangeTime 降序)。</returns>
  670. public async Task<IEnumerable<ScrewFeederBatchRecord>> QueryScrewFeederBatchRecordsAsync(DateTime? start = null, DateTime? end = null, string batchNumber = null, int? feederNumber = null)
  671. {
  672. var q = _db.Queryable<ScrewFeederBatchRecord>();
  673. if (start.HasValue)
  674. q = q.Where(r => r.ChangeTime >= start.Value);
  675. if (end.HasValue)
  676. q = q.Where(r => r.ChangeTime <= end.Value);
  677. if (!string.IsNullOrEmpty(batchNumber))
  678. q = q.Where(r => r.BatchNumber == batchNumber);
  679. if (feederNumber.HasValue)
  680. q = q.Where(r => r.FeederNumber == feederNumber.Value);
  681. var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToListAsync();
  682. return list;
  683. }
  684. /// <summary>
  685. /// 分页查询螺丝供料器批次记录
  686. /// </summary>
  687. public async Task<(IEnumerable<ScrewFeederBatchRecord> Items, int TotalCount)> QueryScrewFeederBatchRecordsPagedAsync(DateTime? start = null, DateTime? end = null, string batchNumber = null, int? feederNumber = null, int pageIndex = 1, int pageSize = 20)
  688. {
  689. if (pageIndex < 1) pageIndex = 1;
  690. if (pageSize < 1) pageSize = 20;
  691. var q = _db.Queryable<ScrewFeederBatchRecord>();
  692. if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
  693. if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
  694. if (!string.IsNullOrEmpty(batchNumber)) q = q.Where(r => r.BatchNumber == batchNumber);
  695. if (feederNumber.HasValue) q = q.Where(r => r.FeederNumber == feederNumber.Value);
  696. var total = await q.CountAsync();
  697. var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToPageListAsync(pageIndex, pageSize);
  698. return (list, total);
  699. }
  700. /// <summary>
  701. /// 获取指定供料器编号的最新一条批次更换记录
  702. /// </summary>
  703. /// <param name="feederNumber"></param>
  704. /// <returns></returns>
  705. public async Task<ScrewFeederBatchRecord> GetLatestScrewFeederBatchRecordAsync(int feederNumber)
  706. {
  707. var recs = await _db.Queryable<ScrewFeederBatchRecord>()
  708. .Where(r => r.FeederNumber == feederNumber)
  709. .OrderBy(r => r.ChangeTime, OrderByType.Desc)
  710. .Take(1)
  711. .ToListAsync();
  712. return recs.FirstOrDefault();
  713. }
  714. #endregion
  715. #region 拍照记录
  716. /// <summary>
  717. /// 记录产品拍照结果
  718. /// </summary>
  719. /// <param name="record"></param>
  720. /// <returns></returns>
  721. /// <exception cref="ArgumentNullException"></exception>
  722. public async Task RecordPhotoCaptureAsync(PhotoCaptureRecord record)
  723. {
  724. if (record == null) throw new ArgumentNullException(nameof(record));
  725. if (record.CaptureTime == default(DateTime)) record.CaptureTime = DateTime.Now;
  726. record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
  727. await _db.Insertable(record).ExecuteCommandAsync();
  728. }
  729. /// <summary>
  730. /// 查询拍照记录,支持按配方名、产品SN和时间段过滤,参数均为可选。
  731. /// </summary>
  732. /// <param name="recipeName"></param>
  733. /// <param name="cameraName"></param>
  734. /// <param name="productSN"></param>
  735. /// <param name="start"></param>
  736. /// <param name="end"></param>
  737. /// <returns></returns>
  738. public async Task<IEnumerable<PhotoCaptureRecord>> QueryPhotoCapturesAsync(string recipeName = null, string cameraName = null, string productSN = null, DateTime? start = null, DateTime? end = null)
  739. {
  740. var q = _db.Queryable<PhotoCaptureRecord>();
  741. if (!string.IsNullOrEmpty(recipeName)) q = q.Where(r => r.RecipeName == recipeName);
  742. if (!string.IsNullOrEmpty(productSN)) q = q.Where(r => r.ProductSN == productSN);
  743. if (start.HasValue) q = q.Where(r => r.CaptureTime >= start.Value);
  744. if (end.HasValue) q = q.Where(r => r.CaptureTime <= end.Value);
  745. var list = await q.OrderBy(r => r.CaptureTime, OrderByType.Desc).ToListAsync();
  746. return list;
  747. }
  748. /// <summary>
  749. /// 分页查询拍照记录
  750. /// </summary>
  751. public async Task<(IEnumerable<PhotoCaptureRecord> Items, int TotalCount)> QueryPhotoCapturesPagedAsync(string recipeName = null, string cameraName = null, string productSN = null, DateTime? start = null, DateTime? end = null, int pageIndex = 1, int pageSize = 20)
  752. {
  753. if (pageIndex < 1) pageIndex = 1;
  754. if (pageSize < 1) pageSize = 20;
  755. var q = _db.Queryable<PhotoCaptureRecord>();
  756. if (!string.IsNullOrEmpty(recipeName)) q = q.Where(r => r.RecipeName == recipeName);
  757. if (!string.IsNullOrEmpty(cameraName)) q = q.Where(r => r.CameraName == cameraName);
  758. if (!string.IsNullOrEmpty(productSN)) q = q.Where(r => r.ProductSN == productSN);
  759. if (start.HasValue) q = q.Where(r => r.CaptureTime >= start.Value);
  760. if (end.HasValue) q = q.Where(r => r.CaptureTime <= end.Value);
  761. var total = await q.CountAsync();
  762. var list = await q.OrderBy(r => r.CaptureTime, OrderByType.Desc).ToPageListAsync(pageIndex, pageSize);
  763. return (list, total);
  764. }
  765. #endregion
  766. #region 披头更换记录
  767. /// <summary>
  768. /// 记录披头更换
  769. /// </summary>
  770. /// <param name="record"></param>
  771. /// <returns></returns>
  772. /// <exception cref="ArgumentNullException"></exception>
  773. public async Task RecordHeadChangeAsync(HeadChangeRecord record)
  774. {
  775. if (record == null) throw new ArgumentNullException(nameof(record));
  776. if (record.ChangeTime == default(DateTime)) record.ChangeTime = DateTime.Now;
  777. record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
  778. await _db.Insertable(record).ExecuteCommandAsync();
  779. }
  780. /// <summary>
  781. /// 查询披头更换记录
  782. /// </summary>
  783. /// <param name="start"></param>
  784. /// <param name="end"></param>
  785. /// <param name="headBatchNumber"></param>
  786. /// <returns></returns>
  787. public async Task<IEnumerable<HeadChangeRecord>> QueryHeadChangeRecordsAsync(DateTime? start = null, DateTime? end = null, string headBatchNumber = null)
  788. {
  789. var q = _db.Queryable<HeadChangeRecord>();
  790. if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
  791. if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
  792. if (!string.IsNullOrEmpty(headBatchNumber)) q = q.Where(r => r.HeadBatchNumber == headBatchNumber);
  793. var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToListAsync();
  794. return list;
  795. }
  796. /// <summary>
  797. /// 分页查询披头更换记录
  798. /// </summary>
  799. public async Task<(IEnumerable<HeadChangeRecord> Items, int TotalCount)> QueryHeadChangeRecordsPagedAsync(DateTime? start = null, DateTime? end = null, string headBatchNumber = null, int pageIndex = 1, int pageSize = 20)
  800. {
  801. if (pageIndex < 1) pageIndex = 1;
  802. if (pageSize < 1) pageSize = 20;
  803. var q = _db.Queryable<HeadChangeRecord>();
  804. if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
  805. if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
  806. if (!string.IsNullOrEmpty(headBatchNumber)) q = q.Where(r => r.HeadBatchNumber == headBatchNumber);
  807. var total = await q.CountAsync();
  808. var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToPageListAsync(pageIndex, pageSize);
  809. return (list, total);
  810. }
  811. /// <summary>
  812. /// 获取最新的一条披头更换记录
  813. /// </summary>
  814. /// <returns></returns>
  815. public async Task<HeadChangeRecord> GetLatestHeadChangeRecordAsync()
  816. {
  817. var recs = await _db.Queryable<HeadChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).ToListAsync();
  818. return recs.FirstOrDefault();
  819. }
  820. /// <summary>
  821. /// 对最新披头的锁付次数进行递增并返回更新后的次数(原子操作)。
  822. /// 若不存在记录则创建一条新记录并返回 1。
  823. /// </summary>
  824. public async Task<int> IncrementLatestHeadLockCountAsync(string operatorName = null)
  825. {
  826. // 使用事务保证原子性
  827. try
  828. {
  829. _db.Ado.BeginTran();
  830. // 获取最新记录(排他查询)
  831. var latest = _db.Queryable<HeadChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).First();
  832. if (latest == null)
  833. {
  834. var newRec = new HeadChangeRecord
  835. {
  836. ChangeTime = DateTime.Now,
  837. HeadBatchNumber = string.Empty,
  838. CurrentLockCount = 1,
  839. OperatorName = operatorName
  840. };
  841. await _db.Insertable(newRec).ExecuteCommandAsync();
  842. _db.Ado.CommitTran();
  843. return 1;
  844. }
  845. latest.CurrentLockCount += 1;
  846. if (!string.IsNullOrEmpty(operatorName)) latest.OperatorName = operatorName;
  847. await _db.Updateable(latest).ExecuteCommandAsync();
  848. _db.Ado.CommitTran();
  849. return latest.CurrentLockCount;
  850. }
  851. catch
  852. {
  853. try { _db.Ado.RollbackTran(); } catch { }
  854. throw;
  855. }
  856. }
  857. #endregion
  858. #region 吸嘴更换记录
  859. /// <summary>
  860. /// 记录喷嘴更换记录。
  861. /// </summary>
  862. /// <param name="record"></param>
  863. /// <returns></returns>
  864. public async Task RecordNozzleChangeAsync(NozzleChangeRecord record)
  865. {
  866. if (record == null) throw new ArgumentNullException(nameof(record));
  867. if (record.ChangeTime == default(DateTime)) record.ChangeTime = DateTime.Now;
  868. record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
  869. await _db.Insertable(record).ExecuteCommandAsync();
  870. }
  871. /// <summary>
  872. /// 查询喷嘴更换记录,可按时间段或喷嘴批号过滤(所有参数可选)。
  873. /// </summary>
  874. /// <param name="start"></param>
  875. /// <param name="end"></param>
  876. /// <param name="nozzleBatchNumber"></param>
  877. /// <returns></returns>
  878. public async Task<IEnumerable<NozzleChangeRecord>> QueryNozzleChangeRecordsAsync(DateTime? start = null, DateTime? end = null, string nozzleBatchNumber = null)
  879. {
  880. var q = _db.Queryable<NozzleChangeRecord>();
  881. if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
  882. if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
  883. if (!string.IsNullOrEmpty(nozzleBatchNumber)) q = q.Where(r => r.NozzleBatchNumber == nozzleBatchNumber);
  884. var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToListAsync();
  885. return list;
  886. }
  887. /// <summary>
  888. /// 获取最新的一条喷嘴更换记录(按 ChangeTime 降序)。
  889. /// </summary>
  890. /// <returns></returns>
  891. public async Task<NozzleChangeRecord> GetLatestNozzleChangeRecordAsync()
  892. {
  893. var recs = await _db.Queryable<NozzleChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).ToListAsync();
  894. return recs.FirstOrDefault();
  895. }
  896. /// <summary>
  897. /// 吸嘴的锁付次数进行递增并返回更新后的次数(如果不存在最新记录,则创建一条新的记录并返回 1)。
  898. /// </summary>
  899. /// <param name="operatorName"></param>
  900. /// <returns></returns>
  901. public async Task<int> IncrementLatestNozzleLockCountAsync(string operatorName = null)
  902. {
  903. try
  904. {
  905. _db.Ado.BeginTran();
  906. var latest = _db.Queryable<NozzleChangeRecord>().OrderBy(r => r.ChangeTime, OrderByType.Desc).Take(1).First();
  907. if (latest == null)
  908. {
  909. var newRec = new NozzleChangeRecord
  910. {
  911. ChangeTime = DateTime.Now,
  912. NozzleBatchNumber = string.Empty,
  913. CurrentLockCount = 1,
  914. OperatorName = operatorName
  915. };
  916. await _db.Insertable(newRec).ExecuteCommandAsync();
  917. _db.Ado.CommitTran();
  918. return 1;
  919. }
  920. latest.CurrentLockCount += 1;
  921. if (!string.IsNullOrEmpty(operatorName)) latest.OperatorName = operatorName;
  922. await _db.Updateable(latest).ExecuteCommandAsync();
  923. _db.Ado.CommitTran();
  924. return latest.CurrentLockCount;
  925. }
  926. catch
  927. {
  928. try { _db.Ado.RollbackTran(); } catch { }
  929. throw;
  930. }
  931. }
  932. /// <summary>
  933. /// 分页查询喷嘴更换记录
  934. /// </summary>
  935. public async Task<(IEnumerable<NozzleChangeRecord> Items, int TotalCount)> QueryNozzleChangeRecordsPagedAsync(DateTime? start = null, DateTime? end = null, string nozzleBatchNumber = null, int pageIndex = 1, int pageSize = 20)
  936. {
  937. if (pageIndex < 1) pageIndex = 1;
  938. if (pageSize < 1) pageSize = 20;
  939. var q = _db.Queryable<NozzleChangeRecord>();
  940. if (start.HasValue) q = q.Where(r => r.ChangeTime >= start.Value);
  941. if (end.HasValue) q = q.Where(r => r.ChangeTime <= end.Value);
  942. if (!string.IsNullOrEmpty(nozzleBatchNumber)) q = q.Where(r => r.NozzleBatchNumber == nozzleBatchNumber);
  943. var total = await q.CountAsync();
  944. var list = await q.OrderBy(r => r.ChangeTime, OrderByType.Desc).ToPageListAsync(pageIndex, pageSize);
  945. return (list, total);
  946. }
  947. #endregion
  948. #region 生成测试数据
  949. //定义一个方法来生成一些测试的数据,测试数据库写入和查询功能
  950. public async Task GenerateTestDataAsync()
  951. {
  952. //生成一些喷嘴更换记录的测试数据
  953. for (int i = 0; i < 10; i++)
  954. {
  955. var record = new NozzleChangeRecord
  956. {
  957. ChangeTime = DateTime.Now.AddMinutes(-i * 10),
  958. NozzleBatchNumber = $"Batch-{i}",
  959. CurrentLockCount = i,
  960. OperatorName = $"Operator-{i}"
  961. };
  962. await RecordNozzleChangeAsync(record);
  963. }
  964. //生成一些披头更换记录的测试数据
  965. for (int i = 0; i < 10; i++)
  966. {
  967. var record = new HeadChangeRecord
  968. {
  969. ChangeTime = DateTime.Now.AddMinutes(-i * 15),
  970. HeadBatchNumber = $"HeadBatch-{i}",
  971. CurrentLockCount = i,
  972. OperatorName = $"HeadOperator-{i}"
  973. };
  974. await RecordHeadChangeAsync(record);
  975. }
  976. //生成一些螺丝供料器批次更换记录的测试数据
  977. for (int i = 0; i < 10; i++)
  978. {
  979. var record = new ScrewFeederBatchRecord
  980. {
  981. ChangeTime = DateTime.Now.AddMinutes(-i * 20),
  982. BatchNumber = $"ScrewBatch-{i}",
  983. FeederNumber = i + 1
  984. };
  985. await RecordScrewFeederBatchAsync(record);
  986. }
  987. //生成一些生产记录的测试数据
  988. for (int i = 0; i < 50; i++)
  989. {
  990. var record = new ProductionRecord
  991. {
  992. Id = Guid.NewGuid(),
  993. ProductName = "产品1",
  994. Category = (i % 2 == 0) ? "OK" : "NG",
  995. Quantity = 1,
  996. Timestamp = DateTime.Now.AddMinutes(-i * 5),
  997. UserName = "TestUser",
  998. Remark = $"TotalSeconds:1.10"
  999. };
  1000. await RecordProductionAsync(record);
  1001. }
  1002. //生成一些锁付结果的测试数据
  1003. for (int i = 0; i < 200; i++)
  1004. {
  1005. var result = new LockResult(i % 4, $"PN-{i % 5}", 10.5f, 0.3f, (i % 3 != 0))
  1006. {
  1007. ProductName = "产品1",
  1008. Timestamp = DateTime.Now.AddMinutes(-i * 3),
  1009. };
  1010. await RecordLockResultAsync(result);
  1011. }
  1012. //生成一些相机拍照记录的测试数据
  1013. for (int i = 0; i < 20; i++)
  1014. {
  1015. try
  1016. {
  1017. var record = new PhotoCaptureRecord
  1018. {
  1019. Id = Guid.NewGuid(),
  1020. RecipeName = "产品1",
  1021. CameraName = $"下相机",
  1022. ProductSN = $"SN-{i}",
  1023. IsSuccess = (i % 2 == 0),
  1024. CaptureTime = DateTime.Now.AddMinutes(-i * 7),
  1025. ImagePath = $"/images/photo_{i}.jpg"
  1026. };
  1027. await RecordPhotoCaptureAsync(record);
  1028. }
  1029. catch (Exception ex)
  1030. {
  1031. Console.WriteLine(ex.ToString());
  1032. }
  1033. }
  1034. }
  1035. #endregion
  1036. #region 抛料
  1037. /// <summary>
  1038. /// 将一条抛料信息记录到数据库中
  1039. /// </summary>
  1040. /// <param name="record"></param>
  1041. /// <returns></returns>
  1042. /// <exception cref="ArgumentNullException"></exception>
  1043. public async Task RecordThrowNumberAsync(ThrowRecord record)
  1044. {
  1045. if (record == null) throw new ArgumentNullException(nameof(record));
  1046. record.Id = record.Id == Guid.Empty ? Guid.NewGuid() : record.Id;
  1047. await _db.Insertable(record).ExecuteCommandAsync();
  1048. }
  1049. /// <summary>
  1050. /// 获取抛料数量
  1051. /// </summary>
  1052. /// <returns></returns>
  1053. public async Task<int> GetThrowNumberAsync()
  1054. {
  1055. //今天的起始时间
  1056. var todayStart = DateTime.Now.Date;
  1057. //今天的结束时间
  1058. var todayEnd = DateTime.Now.Date;
  1059. todayEnd = todayEnd.AddDays(1);
  1060. //按照时间范围查询抛料的总数
  1061. var total = await _db.Queryable<ThrowRecord>()
  1062. .Where(r => r.DayTime >= todayStart && r.DayTime < todayEnd)
  1063. .SumAsync(r => r.ThrowCount);
  1064. return total;
  1065. }
  1066. /// <summary>
  1067. /// 重置抛料计数
  1068. /// </summary>
  1069. /// <returns></returns>
  1070. public async Task ResetThrowNumberAsync()
  1071. {
  1072. //今天的起始时间
  1073. var todayStart = DateTime.Now.Date;
  1074. //今天的结束时间
  1075. var todayEnd = DateTime.Now.Date;
  1076. todayEnd = todayEnd.AddDays(1);
  1077. await _db.Deleteable<ThrowRecord>().Where(r => r.DayTime >= todayStart && r.DayTime < todayEnd).ExecuteCommandAsync();
  1078. }
  1079. #endregion
  1080. }
  1081. }