IqcTaskEntryService.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
  2. using Yitter.IdGenerator;
  3. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  4. /// <summary>
  5. /// S5 来料检验任务(报检分录)操作:认领 / 调配 / 优先级 / 检验完成 / 生成检验单。
  6. /// 对齐旧 DOP 任务列表(program 396052925665349)行为,数据落本库 qms_qcp_*。
  7. /// </summary>
  8. [ApiDescriptionSettings(Order = 311, Description = "来料检验任务操作")]
  9. [Route("api/S5IqcTaskEntry")]
  10. [NonUnify]
  11. public class IqcTaskEntryService : IDynamicApiController, ITransient
  12. {
  13. private const long MainTenantId = 1300000000001L;
  14. private readonly ISqlSugarClient _db;
  15. private readonly UserManager _userManager;
  16. public IqcTaskEntryService(ISqlSugarClient db, UserManager userManager)
  17. {
  18. _db = db;
  19. _userManager = userManager;
  20. }
  21. private long ResolveTenantOrThrow()
  22. {
  23. var tid = _userManager.TenantId;
  24. if (tid <= 0) throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
  25. if (_userManager.SuperAdmin && tid == MainTenantId)
  26. throw Oops.Oh("超级管理员操作前必须选择目标租户");
  27. return tid;
  28. }
  29. [DisplayName("来料检验任务列表(报检分录)")]
  30. [HttpGet("list")]
  31. public async Task<object> GetList([FromQuery] IqcTaskEntryListInput input)
  32. {
  33. var page = input.Page <= 0 ? 1 : input.Page;
  34. var pageSize = input.PageSize <= 0 ? 20 : (input.PageSize > 200 ? 200 : input.PageSize);
  35. var offset = (page - 1) * pageSize;
  36. var tenantId = ResolveTenantOrThrow();
  37. var where = new List<string> { "e.tenant_id = @TenantId" };
  38. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  39. if (!string.IsNullOrWhiteSpace(input.ApplyBillNo))
  40. {
  41. where.Add("a.FBILLNO LIKE @ApplyBillNo");
  42. pars.Add(new SugarParameter("@ApplyBillNo", $"%{input.ApplyBillNo.Trim()}%"));
  43. }
  44. if (!string.IsNullOrWhiteSpace(input.MaterialCode))
  45. {
  46. where.Add("e.FMATERIALCFG LIKE @MaterialCode");
  47. pars.Add(new SugarParameter("@MaterialCode", $"%{input.MaterialCode.Trim()}%"));
  48. }
  49. if (!string.IsNullOrWhiteSpace(input.LotNo))
  50. {
  51. where.Add("e.FLOTNUMBER LIKE @LotNo");
  52. pars.Add(new SugarParameter("@LotNo", $"%{input.LotNo.Trim()}%"));
  53. }
  54. if (!string.IsNullOrWhiteSpace(input.InspectStatus))
  55. {
  56. where.Add("e.FINSPECTSTATUS = @InspectStatus");
  57. pars.Add(new SugarParameter("@InspectStatus", input.InspectStatus.Trim()));
  58. }
  59. if (!string.IsNullOrWhiteSpace(input.Receiver))
  60. {
  61. where.Add("e.FSRCORDERNUM LIKE @Receiver");
  62. pars.Add(new SugarParameter("@Receiver", $"%{input.Receiver.Trim()}%"));
  63. }
  64. var whereSql = string.Join(" AND ", where);
  65. var total = await _db.Ado.GetIntAsync(
  66. $"""
  67. SELECT COUNT(1)
  68. FROM qms_qcp_insappnentry e
  69. LEFT JOIN qms_qcp_inspecapplyn a ON a.id = e.glid AND a.tenant_id = e.tenant_id
  70. WHERE {whereSql}
  71. """,
  72. pars);
  73. var list = await _db.Ado.SqlQueryAsync<IqcTaskEntryRow>(
  74. $"""
  75. SELECT
  76. e.id AS Id,
  77. e.glid AS Glid,
  78. a.FBILLNO AS ApplyBillNo,
  79. a.FBIZTYPE AS BizType,
  80. e.FMATERIALCFG AS MaterialCode,
  81. e.wlmc AS MaterialName,
  82. e.ggxh AS Spec,
  83. e.FLOTNUMBER AS LotNo,
  84. e.FSRCORDERNUM AS Receiver,
  85. e.FAPPLYQTY AS ApplyQty,
  86. e.FINSPECTSTATUS AS InspectStatus,
  87. e.jyfzr AS OwnerId,
  88. CAST(NULL AS CHAR) AS OwnerName,
  89. e.yxj AS Priority,
  90. e.FSUPPLIER AS SupplierCode,
  91. e.gysmc AS SupplierName,
  92. e.shdh AS ShipmentNo,
  93. b.id AS InspBillId,
  94. b.FBILLNO AS InspBillNo
  95. FROM qms_qcp_insappnentry e
  96. LEFT JOIN qms_qcp_inspecapplyn a ON a.id = e.glid AND a.tenant_id = e.tenant_id
  97. LEFT JOIN qms_qcp_inspbill b ON b.hid = e.id AND b.tenant_id = e.tenant_id
  98. WHERE {whereSql}
  99. ORDER BY IFNULL(e.yxj, 999999) ASC, e.id DESC
  100. LIMIT {pageSize} OFFSET {offset}
  101. """,
  102. pars);
  103. await FillOwnerNamesAsync(list);
  104. return new { total, page, pageSize, list };
  105. }
  106. private async Task FillOwnerNamesAsync(List<IqcTaskEntryRow> list)
  107. {
  108. var ids = list
  109. .Select(x => x.OwnerId)
  110. .Where(x => !string.IsNullOrWhiteSpace(x) && long.TryParse(x, out _))
  111. .Select(x => long.Parse(x!))
  112. .Distinct()
  113. .ToList();
  114. if (ids.Count == 0) return;
  115. var users = await _db.Queryable<SysUser>()
  116. .ClearFilter()
  117. .Where(u => ids.Contains(u.Id))
  118. .Select(u => new { u.Id, u.RealName, u.Account })
  119. .ToListAsync();
  120. var map = users.ToDictionary(u => u.Id.ToString(), u => u.RealName ?? u.Account ?? u.Id.ToString());
  121. foreach (var row in list)
  122. {
  123. if (!string.IsNullOrWhiteSpace(row.OwnerId) && map.TryGetValue(row.OwnerId!, out var name))
  124. row.OwnerName = name;
  125. }
  126. }
  127. [DisplayName("认领检验任务")]
  128. [HttpPost("claim")]
  129. public async Task<object> Claim([FromBody] IqcTaskEntryIdsInput input)
  130. {
  131. var ids = NormalizeIds(input?.Ids);
  132. var tenantId = ResolveTenantOrThrow();
  133. var owner = _userManager.UserId.ToString();
  134. var n = await _db.Ado.ExecuteCommandAsync(
  135. $"""
  136. UPDATE qms_qcp_insappnentry
  137. SET jyfzr = @Owner
  138. WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})
  139. """,
  140. new SugarParameter("@Owner", owner),
  141. new SugarParameter("@TenantId", tenantId));
  142. return new { updated = n, ownerId = owner };
  143. }
  144. [DisplayName("检验员调配")]
  145. [HttpPost("assign")]
  146. public async Task<object> Assign([FromBody] IqcTaskEntryAssignInput input)
  147. {
  148. var ids = NormalizeIds(input?.Ids);
  149. if (input == null || input.InspectorUserId <= 0) throw Oops.Oh("请指定检验负责人");
  150. var tenantId = ResolveTenantOrThrow();
  151. var owner = input.InspectorUserId.ToString();
  152. var n = await _db.Ado.ExecuteCommandAsync(
  153. $"""
  154. UPDATE qms_qcp_insappnentry
  155. SET jyfzr = @Owner
  156. WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})
  157. """,
  158. new SugarParameter("@Owner", owner),
  159. new SugarParameter("@TenantId", tenantId));
  160. return new { updated = n, ownerId = owner };
  161. }
  162. [DisplayName("优先级调整")]
  163. [HttpPost("priority")]
  164. public async Task<object> AdjustPriority([FromBody] IqcTaskEntryPriorityInput input)
  165. {
  166. var ids = NormalizeIds(input?.Ids);
  167. var tenantId = ResolveTenantOrThrow();
  168. var n = await _db.Ado.ExecuteCommandAsync(
  169. $"""
  170. UPDATE qms_qcp_insappnentry
  171. SET yxj = @Priority
  172. WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})
  173. """,
  174. new SugarParameter("@Priority", input!.Priority),
  175. new SugarParameter("@TenantId", tenantId));
  176. return new { updated = n };
  177. }
  178. [DisplayName("检验完成(手工兜底)")]
  179. [HttpPost("complete")]
  180. public async Task<object> MarkComplete([FromBody] IqcTaskEntryIdsInput input)
  181. {
  182. var ids = NormalizeIds(input?.Ids);
  183. var tenantId = ResolveTenantOrThrow();
  184. var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  185. var n = await _db.Ado.ExecuteCommandAsync(
  186. $"""
  187. UPDATE qms_qcp_insappnentry
  188. SET FINSPECTSTATUS = '检验完成', jywcsj = @Now
  189. WHERE tenant_id = @TenantId AND id IN ({string.Join(",", ids)})
  190. """,
  191. new SugarParameter("@Now", now),
  192. new SugarParameter("@TenantId", tenantId));
  193. return new { updated = n };
  194. }
  195. /// <summary>
  196. /// 「来料检验」:校验认领人 + 幂等 → 置检验中 → 本库生成检验单(IQC+yyyyMMdd+4 位)。
  197. /// </summary>
  198. [DisplayName("生成来料检验单")]
  199. [HttpPost("generate-inspbill")]
  200. public async Task<IqcGenerateInspBillOutput> GenerateInspBill([FromBody] IqcGenerateInspBillInput input)
  201. {
  202. if (input == null || input.EntryId <= 0) throw Oops.Oh("报检分录 id 非法");
  203. var tenantId = ResolveTenantOrThrow();
  204. var userId = _userManager.UserId.ToString();
  205. var entry = await _db.Ado.SqlQuerySingleAsync<EntryGenRow>(
  206. """
  207. SELECT
  208. e.id AS Id, e.glid AS Glid, e.jyfzr AS OwnerId, e.FINSPECTSTATUS AS InspectStatus,
  209. e.FMATERIALCFG AS MaterialCode, e.wlmc AS MaterialName, e.ggxh AS Spec,
  210. e.FLOTNUMBER AS LotNo, e.FAPPLYQTY AS ApplyQty, e.gysmc AS SupplierName,
  211. a.FBILLNO AS ApplyBillNo, a.FBIZTYPE AS BizType
  212. FROM qms_qcp_insappnentry e
  213. LEFT JOIN qms_qcp_inspecapplyn a ON a.id = e.glid AND a.tenant_id = e.tenant_id
  214. WHERE e.id = @Id AND e.tenant_id = @TenantId
  215. LIMIT 1
  216. """,
  217. new SugarParameter("@Id", input.EntryId),
  218. new SugarParameter("@TenantId", tenantId));
  219. if (entry == null) throw Oops.Oh("报检分录不存在");
  220. if (string.IsNullOrWhiteSpace(entry.ApplyBillNo)) throw Oops.Oh("报检单号缺失,无法生成检验单");
  221. if (!string.Equals(entry.OwnerId?.Trim(), userId, StringComparison.Ordinal))
  222. throw Oops.Oh("尚未认领或非认领人操作!");
  223. var exists = await _db.Ado.GetIntAsync(
  224. """
  225. SELECT COUNT(1)
  226. FROM qms_qcp_inspbill
  227. WHERE tenant_id = @TenantId AND lydjbh = @ApplyBillNo AND hid = @Hid
  228. """,
  229. new SugarParameter("@TenantId", tenantId),
  230. new SugarParameter("@ApplyBillNo", entry.ApplyBillNo),
  231. new SugarParameter("@Hid", entry.Id));
  232. if (exists > 0) throw Oops.Oh("该检验任务已经有检验单,不能重新生成!");
  233. var billNo = await AllocateIqcBillNoAsync(tenantId);
  234. var billId = YitIdHelper.NextId();
  235. var now = DateTime.Now;
  236. var startText = now.ToString("yyyy-MM-dd HH:mm:ss");
  237. var tran = await _db.Ado.UseTranAsync(async () =>
  238. {
  239. await _db.Ado.ExecuteCommandAsync(
  240. """
  241. UPDATE qms_qcp_insappnentry
  242. SET jykssj = @Start, FINSPECTSTATUS = '检验中'
  243. WHERE id = @Id AND tenant_id = @TenantId
  244. """,
  245. new SugarParameter("@Start", startText),
  246. new SugarParameter("@Id", entry.Id),
  247. new SugarParameter("@TenantId", tenantId));
  248. await _db.Ado.ExecuteCommandAsync(
  249. """
  250. INSERT INTO qms_qcp_inspbill (
  251. id, tenant_id, FBILLNO, lydjbh, hid, FMATERIALCFG, wlmc, ggxh, gysmc, pch,
  252. FRINSQTY, jysl, dhsl, bhgsl, FBILLSTATUS, jyr, FINSPECTORID,
  253. FINSPESTARTDATE, FCREATETIME
  254. ) VALUES (
  255. @Id, @TenantId, @FBILLNO, @Lydjbh, @Hid, @FMATERIALCFG, @Wlmc, @Ggxh, @Gysmc, @Pch,
  256. @FRINSQTY, @Jysl, 0, 0, '检验中', @Jyr, @Inspector,
  257. @StartDate, @CreateTime
  258. )
  259. """,
  260. new SugarParameter("@Id", billId),
  261. new SugarParameter("@TenantId", tenantId),
  262. new SugarParameter("@FBILLNO", billNo),
  263. new SugarParameter("@Lydjbh", entry.ApplyBillNo),
  264. new SugarParameter("@Hid", entry.Id),
  265. new SugarParameter("@FMATERIALCFG", entry.MaterialCode ?? (object)DBNull.Value),
  266. new SugarParameter("@Wlmc", entry.MaterialName ?? (object)DBNull.Value),
  267. new SugarParameter("@Ggxh", entry.Spec ?? (object)DBNull.Value),
  268. new SugarParameter("@Gysmc", entry.SupplierName ?? (object)DBNull.Value),
  269. new SugarParameter("@Pch", entry.LotNo ?? (object)DBNull.Value),
  270. new SugarParameter("@FRINSQTY", entry.ApplyQty ?? 0m),
  271. new SugarParameter("@Jysl", entry.ApplyQty ?? 0m),
  272. new SugarParameter("@Jyr", userId),
  273. new SugarParameter("@Inspector", _userManager.RealName ?? _userManager.Account ?? userId),
  274. new SugarParameter("@StartDate", now),
  275. new SugarParameter("@CreateTime", now));
  276. });
  277. if (!tran.IsSuccess) throw tran.ErrorException ?? Oops.Oh("生成检验单失败");
  278. return new IqcGenerateInspBillOutput { InspBillId = billId, InspBillNo = billNo };
  279. }
  280. private async Task<string> AllocateIqcBillNoAsync(long tenantId)
  281. {
  282. var today = DateTime.Now.ToString("yyyyMMdd");
  283. var prefix = $"IQC{today}";
  284. var max = await _db.Ado.SqlQuerySingleAsync<string>(
  285. """
  286. SELECT FBILLNO
  287. FROM qms_qcp_inspbill
  288. WHERE tenant_id = @TenantId AND FBILLNO LIKE @Prefix
  289. ORDER BY FBILLNO DESC
  290. LIMIT 1
  291. """,
  292. new SugarParameter("@TenantId", tenantId),
  293. new SugarParameter("@Prefix", prefix + "%"));
  294. if (string.IsNullOrWhiteSpace(max) || max.Length < 4)
  295. return $"{prefix}0001";
  296. var last4 = max[^4..];
  297. if (!int.TryParse(last4, out var serial))
  298. return $"{prefix}0001";
  299. if (serial >= 9999)
  300. throw Oops.Oh($"当日检验单号已达上限 {prefix}9999");
  301. return $"{prefix}{(serial + 1).ToString().PadLeft(4, '0')}";
  302. }
  303. private static List<long> NormalizeIds(List<long>? ids)
  304. {
  305. var list = (ids ?? new List<long>()).Where(x => x > 0).Distinct().ToList();
  306. if (list.Count == 0) throw Oops.Oh("请至少选择一条任务");
  307. if (list.Count > 200) throw Oops.Oh("单次最多操作 200 条");
  308. return list;
  309. }
  310. private sealed class EntryGenRow
  311. {
  312. public long Id { get; set; }
  313. public long? Glid { get; set; }
  314. public string? OwnerId { get; set; }
  315. public string? InspectStatus { get; set; }
  316. public string? MaterialCode { get; set; }
  317. public string? MaterialName { get; set; }
  318. public string? Spec { get; set; }
  319. public string? LotNo { get; set; }
  320. public decimal? ApplyQty { get; set; }
  321. public string? SupplierName { get; set; }
  322. public string? ApplyBillNo { get; set; }
  323. public string? BizType { get; set; }
  324. }
  325. }