SystemDatabaseService.cs 53 KB

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