AdoS0QualityFqcOqcControllers.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Quality;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Quality;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. using static Admin.NET.Plugin.AiDOP.Controllers.S0.Quality.AdoS0QmsControllerHelpers;
  5. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Quality;
  6. [ApiController]
  7. [Route("api/s0/quality/fqc-inspection-specs")]
  8. [NonUnify]
  9. public class AdoS0QmsFqcInspectionSpecsController : ControllerBase
  10. {
  11. private readonly SqlSugarRepository<AdoS0QmsFqcInspectionSpec> _rep;
  12. private readonly SqlSugarRepository<AdoS0QmsFqcInspectionSpecEntry> _entryRep;
  13. public AdoS0QmsFqcInspectionSpecsController(
  14. SqlSugarRepository<AdoS0QmsFqcInspectionSpec> rep,
  15. SqlSugarRepository<AdoS0QmsFqcInspectionSpecEntry> entryRep)
  16. {
  17. _rep = rep;
  18. _entryRep = entryRep;
  19. }
  20. [HttpGet]
  21. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0QualityPagedQueryDto q)
  22. {
  23. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  24. (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
  25. var query = _rep.AsQueryable().Where(x => x.TenantId == tenantId)
  26. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword), x =>
  27. (x.FileNumber != null && x.FileNumber.Contains(q.Keyword!))
  28. || (x.ApplicableModel != null && x.ApplicableModel.Contains(q.Keyword!))
  29. || (x.MaterialCode != null && x.MaterialCode.Contains(q.Keyword!)));
  30. var total = await query.CountAsync();
  31. var list = await query.OrderByDescending(x => x.Id).Skip((q.Page - 1) * q.PageSize).Take(q.PageSize).ToListAsync();
  32. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  33. }
  34. [HttpGet("{id:long}")]
  35. public async Task<IActionResult> GetDetailAsync(long id)
  36. {
  37. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  38. var master = await _rep.ByIdScopedByColumnAsync(id, tenantId);
  39. if (master == null) return NotFound();
  40. var items = await _entryRep.AsQueryable().Where(x => x.MasterId == id).OrderBy(x => x.Id).ToListAsync();
  41. return Ok(new { master, items });
  42. }
  43. [HttpPost]
  44. public async Task<IActionResult> CreateAsync([FromBody] AdoS0QmsFqcInspectionSpecUpsertDto dto)
  45. {
  46. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  47. var items = AdoS0QmsSpecAggregateGuard.Meaningful(dto.Items);
  48. var guardError = AdoS0QmsSpecAggregateGuard.ValidateCreate(FqcRequiredFields(dto), items.Count);
  49. if (guardError != null) return AdoS0ApiErrors.InvalidRequest(guardError);
  50. var db = _rep.Context;
  51. await db.Ado.BeginTranAsync();
  52. try
  53. {
  54. var master = new AdoS0QmsFqcInspectionSpec();
  55. ApplyFqcSpec(master, dto);
  56. master.TenantId = tenantId;
  57. await _rep.AsInsertable(master).ExecuteReturnEntityAsync();
  58. await SyncFqcEntriesAsync(master.Id, items, tenantId);
  59. await db.Ado.CommitTranAsync();
  60. return await GetDetailAsync(master.Id);
  61. }
  62. catch
  63. {
  64. await db.Ado.RollbackTranAsync();
  65. throw;
  66. }
  67. }
  68. [HttpPut("{id:long}")]
  69. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0QmsFqcInspectionSpecUpsertDto dto)
  70. {
  71. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  72. var master = await _rep.ByIdScopedByColumnAsync(id, tenantId);
  73. if (master == null) return NotFound();
  74. // 写入集保留历史已持久化的明细行(含空行),只丢弃本次新补的空占位;
  75. // 「原有明细数」按 meaningful 计,避免全空明细的历史单被自己的空行锁死。
  76. var existingRows = await _entryRep.AsQueryable().Where(x => x.MasterId == id).ToListAsync();
  77. var items = AdoS0QmsSpecAggregateGuard.ForUpdateWrite(dto.Items);
  78. var guardError = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
  79. [
  80. ("文件编号", master.FileNumber, dto.FileNumber),
  81. ("物料编码", master.MaterialCode, dto.MaterialCode),
  82. ("版本", master.VersionNo, dto.VersionNo)
  83. ],
  84. AdoS0QmsSpecAggregateGuard.CountMeaningful(existingRows),
  85. AdoS0QmsSpecAggregateGuard.Meaningful(dto.Items).Count);
  86. if (guardError != null) return AdoS0ApiErrors.InvalidRequest(guardError);
  87. var db = _rep.Context;
  88. await db.Ado.BeginTranAsync();
  89. try
  90. {
  91. ApplyFqcSpec(master, dto);
  92. await _rep.AsUpdateable(master).ExecuteCommandAsync();
  93. await _entryRep.AsDeleteable().Where(x => x.MasterId == id).ExecuteCommandAsync();
  94. await SyncFqcEntriesAsync(id, items, tenantId);
  95. await db.Ado.CommitTranAsync();
  96. return await GetDetailAsync(id);
  97. }
  98. catch
  99. {
  100. await db.Ado.RollbackTranAsync();
  101. throw;
  102. }
  103. }
  104. [HttpDelete("{id:long}")]
  105. public async Task<IActionResult> DeleteAsync(long id)
  106. {
  107. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  108. var master = await _rep.ByIdScopedByColumnAsync(id, tenantId);
  109. if (master == null) return NotFound();
  110. var db = _rep.Context;
  111. await db.Ado.BeginTranAsync();
  112. try
  113. {
  114. await _entryRep.AsDeleteable().Where(x => x.MasterId == id).ExecuteCommandAsync();
  115. await _rep.DeleteAsync(master);
  116. await db.Ado.CommitTranAsync();
  117. return Ok(new { message = "删除成功" });
  118. }
  119. catch
  120. {
  121. await db.Ado.RollbackTranAsync();
  122. throw;
  123. }
  124. }
  125. /// <summary>FQC 检规必填集(UAT-S0-10 拍板;本轮不含「适用型号」,缺业务依据)。</summary>
  126. private static IReadOnlyList<(string, string?)> FqcRequiredFields(AdoS0QmsFqcInspectionSpecUpsertDto dto) =>
  127. [
  128. ("文件编号", dto.FileNumber),
  129. ("物料编码", dto.MaterialCode),
  130. ("版本", dto.VersionNo),
  131. ];
  132. private static void ApplyFqcSpec(AdoS0QmsFqcInspectionSpec entity, AdoS0QmsFqcInspectionSpecUpsertDto dto)
  133. {
  134. entity.ApplicableModel = NullIfWhiteSpace(dto.ApplicableModel);
  135. entity.FileNumber = NullIfWhiteSpace(dto.FileNumber);
  136. entity.VersionNo = NullIfWhiteSpace(dto.VersionNo);
  137. entity.EffectiveDate = NullIfWhiteSpace(dto.EffectiveDate);
  138. entity.Attachment = NullIfWhiteSpace(dto.Attachment);
  139. entity.MaterialCode = NullIfWhiteSpace(dto.MaterialCode);
  140. entity.Attachment2 = NullIfWhiteSpace(dto.Attachment2);
  141. entity.Version = dto.Version;
  142. }
  143. private async Task SyncFqcEntriesAsync(long masterId, IEnumerable<AdoS0QmsFqcInspectionSpecEntryDto> items, long tenantId)
  144. {
  145. foreach (var item in items)
  146. {
  147. var entity = new AdoS0QmsFqcInspectionSpecEntry
  148. {
  149. TenantId = tenantId,
  150. MasterId = masterId,
  151. OperationCode = NullIfWhiteSpace(item.OperationCode),
  152. OperationName = NullIfWhiteSpace(item.OperationName),
  153. InspectionItem = NullIfWhiteSpace(item.InspectionItem),
  154. InspectionMethod = NullIfWhiteSpace(item.InspectionMethod),
  155. InspectionSpec = NullIfWhiteSpace(item.InspectionSpec),
  156. ImageCategory = NullIfWhiteSpace(item.ImageCategory),
  157. InspectionFrequency = NullIfWhiteSpace(item.InspectionFrequency),
  158. TechnicalStandard = NullIfWhiteSpace(item.TechnicalStandard),
  159. PeelingForce = item.PeelingForce,
  160. UpperLimit = NullIfWhiteSpace(item.UpperLimit),
  161. LowerLimit = NullIfWhiteSpace(item.LowerLimit)
  162. };
  163. await _entryRep.AsInsertable(entity).ExecuteCommandAsync();
  164. }
  165. }
  166. }
  167. [ApiController]
  168. [Route("api/s0/quality/oqc-inspection-specs")]
  169. [NonUnify]
  170. public class AdoS0QmsOqcInspectionSpecsController : ControllerBase
  171. {
  172. private readonly SqlSugarRepository<AdoS0QmsOqcInspectionSpec> _rep;
  173. private readonly SqlSugarRepository<AdoS0QmsOqcInspectionSpecEntry> _entryRep;
  174. public AdoS0QmsOqcInspectionSpecsController(
  175. SqlSugarRepository<AdoS0QmsOqcInspectionSpec> rep,
  176. SqlSugarRepository<AdoS0QmsOqcInspectionSpecEntry> entryRep)
  177. {
  178. _rep = rep;
  179. _entryRep = entryRep;
  180. }
  181. [HttpGet]
  182. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0QualityPagedQueryDto q)
  183. {
  184. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  185. (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
  186. var query = _rep.AsQueryable().Where(x => x.TenantId == tenantId)
  187. .WhereIF(!string.IsNullOrWhiteSpace(q.Keyword), x =>
  188. (x.FileNumber != null && x.FileNumber.Contains(q.Keyword!))
  189. || (x.ApplicableModel != null && x.ApplicableModel.Contains(q.Keyword!))
  190. || (x.MaterialCode != null && x.MaterialCode.Contains(q.Keyword!)));
  191. var total = await query.CountAsync();
  192. var list = await query.OrderByDescending(x => x.Id).Skip((q.Page - 1) * q.PageSize).Take(q.PageSize).ToListAsync();
  193. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  194. }
  195. [HttpGet("{id:long}")]
  196. public async Task<IActionResult> GetDetailAsync(long id)
  197. {
  198. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  199. var master = await _rep.ByIdScopedByColumnAsync(id, tenantId);
  200. if (master == null) return NotFound();
  201. var items = await _entryRep.AsQueryable().Where(x => x.MasterId == id).OrderBy(x => x.Id).ToListAsync();
  202. return Ok(new { master, items });
  203. }
  204. [HttpPost]
  205. public async Task<IActionResult> CreateAsync([FromBody] AdoS0QmsOqcInspectionSpecUpsertDto dto)
  206. {
  207. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  208. var items = AdoS0QmsSpecAggregateGuard.Meaningful(dto.Items);
  209. var guardError = AdoS0QmsSpecAggregateGuard.ValidateCreate(OqcRequiredFields(dto), items.Count);
  210. if (guardError != null) return AdoS0ApiErrors.InvalidRequest(guardError);
  211. var db = _rep.Context;
  212. await db.Ado.BeginTranAsync();
  213. try
  214. {
  215. var master = new AdoS0QmsOqcInspectionSpec();
  216. ApplyOqcSpec(master, dto);
  217. master.TenantId = tenantId;
  218. await _rep.AsInsertable(master).ExecuteReturnEntityAsync();
  219. await SyncOqcEntriesAsync(master.Id, items, tenantId);
  220. await db.Ado.CommitTranAsync();
  221. return await GetDetailAsync(master.Id);
  222. }
  223. catch
  224. {
  225. await db.Ado.RollbackTranAsync();
  226. throw;
  227. }
  228. }
  229. [HttpPut("{id:long}")]
  230. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0QmsOqcInspectionSpecUpsertDto dto)
  231. {
  232. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  233. var master = await _rep.ByIdScopedByColumnAsync(id, tenantId);
  234. if (master == null) return NotFound();
  235. // 写入集保留历史已持久化的明细行(含空行),只丢弃本次新补的空占位;
  236. // 「原有明细数」按 meaningful 计,避免全空明细的历史单被自己的空行锁死。
  237. var existingRows = await _entryRep.AsQueryable().Where(x => x.MasterId == id).ToListAsync();
  238. var items = AdoS0QmsSpecAggregateGuard.ForUpdateWrite(dto.Items);
  239. var guardError = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
  240. [
  241. ("文件编号", master.FileNumber, dto.FileNumber),
  242. ("物料编码", master.MaterialCode, dto.MaterialCode),
  243. ("版本", master.VersionNo, dto.VersionNo)
  244. ],
  245. AdoS0QmsSpecAggregateGuard.CountMeaningful(existingRows),
  246. AdoS0QmsSpecAggregateGuard.Meaningful(dto.Items).Count);
  247. if (guardError != null) return AdoS0ApiErrors.InvalidRequest(guardError);
  248. var db = _rep.Context;
  249. await db.Ado.BeginTranAsync();
  250. try
  251. {
  252. ApplyOqcSpec(master, dto);
  253. await _rep.AsUpdateable(master).ExecuteCommandAsync();
  254. await _entryRep.AsDeleteable().Where(x => x.MasterId == id).ExecuteCommandAsync();
  255. await SyncOqcEntriesAsync(id, items, tenantId);
  256. await db.Ado.CommitTranAsync();
  257. return await GetDetailAsync(id);
  258. }
  259. catch
  260. {
  261. await db.Ado.RollbackTranAsync();
  262. throw;
  263. }
  264. }
  265. [HttpDelete("{id:long}")]
  266. public async Task<IActionResult> DeleteAsync(long id)
  267. {
  268. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  269. var master = await _rep.ByIdScopedByColumnAsync(id, tenantId);
  270. if (master == null) return NotFound();
  271. var db = _rep.Context;
  272. await db.Ado.BeginTranAsync();
  273. try
  274. {
  275. await _entryRep.AsDeleteable().Where(x => x.MasterId == id).ExecuteCommandAsync();
  276. await _rep.DeleteAsync(master);
  277. await db.Ado.CommitTranAsync();
  278. return Ok(new { message = "删除成功" });
  279. }
  280. catch
  281. {
  282. await db.Ado.RollbackTranAsync();
  283. throw;
  284. }
  285. }
  286. /// <summary>OQC 检规必填集(UAT-S0-10 拍板;本轮不含「适用型号」,缺业务依据)。</summary>
  287. private static IReadOnlyList<(string, string?)> OqcRequiredFields(AdoS0QmsOqcInspectionSpecUpsertDto dto) =>
  288. [
  289. ("文件编号", dto.FileNumber),
  290. ("物料编码", dto.MaterialCode),
  291. ("版本", dto.VersionNo),
  292. ];
  293. private static void ApplyOqcSpec(AdoS0QmsOqcInspectionSpec entity, AdoS0QmsOqcInspectionSpecUpsertDto dto)
  294. {
  295. entity.ApplicableModel = NullIfWhiteSpace(dto.ApplicableModel);
  296. entity.FileNumber = NullIfWhiteSpace(dto.FileNumber);
  297. entity.VersionNo = NullIfWhiteSpace(dto.VersionNo);
  298. entity.EffectiveDate = NullIfWhiteSpace(dto.EffectiveDate);
  299. entity.Attachment = NullIfWhiteSpace(dto.Attachment);
  300. entity.MaterialCode = NullIfWhiteSpace(dto.MaterialCode);
  301. entity.Attachment2 = NullIfWhiteSpace(dto.Attachment2);
  302. entity.Version = dto.Version;
  303. }
  304. private async Task SyncOqcEntriesAsync(long masterId, IEnumerable<AdoS0QmsOqcInspectionSpecEntryDto> items, long tenantId)
  305. {
  306. foreach (var item in items)
  307. {
  308. var entity = new AdoS0QmsOqcInspectionSpecEntry
  309. {
  310. TenantId = tenantId,
  311. MasterId = masterId,
  312. OperationCode = NullIfWhiteSpace(item.OperationCode),
  313. OperationName = NullIfWhiteSpace(item.OperationName),
  314. InspectionItem = NullIfWhiteSpace(item.InspectionItem),
  315. InspectionMethod = NullIfWhiteSpace(item.InspectionMethod),
  316. InspectionSpec = NullIfWhiteSpace(item.InspectionSpec),
  317. ImageCategory = NullIfWhiteSpace(item.ImageCategory),
  318. InspectionFrequency = NullIfWhiteSpace(item.InspectionFrequency),
  319. TechnicalStandard = NullIfWhiteSpace(item.TechnicalStandard),
  320. PeelingForce = item.PeelingForce,
  321. UpperLimit = NullIfWhiteSpace(item.UpperLimit),
  322. LowerLimit = NullIfWhiteSpace(item.LowerLimit)
  323. };
  324. await _entryRep.AsInsertable(entity).ExecuteCommandAsync();
  325. }
  326. }
  327. }