DemandScheduleService.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. using Yitter.IdGenerator;
  2. namespace Admin.NET.Plugin.AiDOP.Supply;
  3. /// <summary>
  4. /// 物料需求计划服务
  5. ///
  6. /// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
  7. /// 无有效租户即拒绝,不读前端 tenantId、无默认回退。
  8. /// <para>
  9. /// 原实现:类级 <c>[AllowAnonymous]</c> + 多处以 <c>_userManager.TenantId</c> 拼接条件
  10. /// (<c>if (TenantId > 0)</c> / <c>tenantId <= 0 ||</c> 跳过形态)。匿名请求下 TenantId=0,
  11. /// 租户过滤整体被跳过,等价于未认证可跨租户读/软删/发布需求计划。
  12. /// </para>
  13. /// </summary>
  14. [ApiDescriptionSettings(Order = 305, Description = "物料需求计划")]
  15. [Route("api/Supply")]
  16. [NonUnify]
  17. public class DemandScheduleService : IDynamicApiController, ITransient
  18. {
  19. private readonly ISqlSugarClient _db;
  20. private readonly SqlSugarRepository<DemandSchedule> _rep;
  21. private readonly UserManager _userManager;
  22. public DemandScheduleService(
  23. ISqlSugarClient db,
  24. SqlSugarRepository<DemandSchedule> rep,
  25. UserManager userManager)
  26. {
  27. _db = db;
  28. _rep = rep;
  29. _userManager = userManager;
  30. }
  31. [DisplayName("物料需求计划列表")]
  32. [HttpGet("demand-schedule/list")]
  33. public async Task<object> GetList([FromQuery] DemandScheduleListInput input)
  34. {
  35. var page = input.Page <= 0 ? 1 : input.Page;
  36. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  37. var offset = (page - 1) * pageSize;
  38. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  39. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  40. var where = new List<string>
  41. {
  42. "(a.ishistoryversion = 'N' OR a.ishistoryversion IS NULL)",
  43. "IFNULL(a.IsDeleted,0)=0",
  44. "a.tenant_id = @TenantId"
  45. };
  46. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  47. {
  48. where.Add("a.itemnum LIKE @ItemNum");
  49. pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
  50. }
  51. if (!string.IsNullOrWhiteSpace(input.Descr))
  52. {
  53. where.Add("b.Descr LIKE @Descr");
  54. pars.Add(new SugarParameter("@Descr", $"%{input.Descr.Trim()}%"));
  55. }
  56. if (!string.IsNullOrWhiteSpace(input.RequestDateFrom) && DateTime.TryParse(input.RequestDateFrom, out var reqFrom))
  57. {
  58. where.Add("a.requestdate >= @RequestDateFrom");
  59. pars.Add(new SugarParameter("@RequestDateFrom", reqFrom.Date));
  60. }
  61. var fromSql = $"""
  62. FROM ic_demandschedule a
  63. LEFT JOIN ItemMaster b ON a.itemnum = b.ItemNum AND b.tenant_id = a.tenant_id
  64. WHERE {string.Join(" AND ", where)}
  65. """;
  66. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  67. var list = await _db.Ado.SqlQueryAsync<DemandScheduleListRow>(
  68. $"""
  69. SELECT
  70. a.Id AS Id,
  71. a.itemnum AS ItemNum,
  72. b.Descr AS Descr,
  73. b.Descr1 AS Descr1,
  74. a.fversion AS FVersion,
  75. a.drawing AS Drawing,
  76. a.requestdate AS RequestDate,
  77. a.arrivaldate AS ArrivalDate,
  78. a.shortqty AS ShortQty,
  79. a.mesqty AS MesQty,
  80. a.locqty AS LocQty,
  81. a.sechedqty AS SechedQty,
  82. a.tosechedqty AS ToSechedQty,
  83. CASE IFNULL(a.status,'') WHEN 'P' THEN '已发布' ELSE '' END AS StatusText,
  84. CASE IFNULL(a.status,'') WHEN 'P' THEN DATE_FORMAT(a.update_time,'%Y-%m-%d') ELSE '' END AS PublishDate,
  85. IFNULL(a.status,'') AS Status,
  86. a.remarks AS Remarks,
  87. a.tenant_id AS TenantId,
  88. a.factory_id AS FactoryId,
  89. a.company_id AS CompanyId,
  90. a.wolist AS WoList
  91. {fromSql}
  92. ORDER BY a.requestdate DESC, a.Id DESC
  93. LIMIT {pageSize} OFFSET {offset}
  94. """,
  95. pars);
  96. return new { total, page, pageSize, list };
  97. }
  98. [DisplayName("获取物料需求计划详情")]
  99. [HttpGet("demand-schedule/{id:long}")]
  100. public async Task<object> GetDetail(long id)
  101. {
  102. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  103. var row = await _rep.GetFirstAsync(x => x.Id == id && (x.IsDeleted == null || x.IsDeleted == 0) && x.TenantId == tenantId);
  104. if (row == null) throw Oops.Oh("记录不存在");
  105. return row;
  106. }
  107. [DisplayName("保存物料需求计划")]
  108. [ApiDescriptionSettings(Name = "SaveDemandSchedule"), HttpPost("demand-schedule/save")]
  109. public async Task<object> Save([FromBody] DemandScheduleSaveInput input)
  110. {
  111. if (string.IsNullOrWhiteSpace(input.ItemNum)) throw Oops.Oh("物料编号不能为空");
  112. if (string.IsNullOrWhiteSpace(input.RequestDate)) throw Oops.Oh("需求日期不能为空");
  113. if (string.IsNullOrWhiteSpace(input.ArrivalDate)) throw Oops.Oh("建议到货日期不能为空");
  114. if (input.ToSechedQty is null) throw Oops.Oh("物料净需求数量不能为空");
  115. if (input.Id is null or 0)
  116. {
  117. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  118. var entity = input.Adapt<DemandSchedule>();
  119. entity.Id = YitIdHelper.NextId();
  120. entity.ItemNum = input.ItemNum?.Trim();
  121. entity.IsHistoryVersion = null;
  122. entity.Status ??= string.Empty;
  123. entity.IsDeleted ??= 0;
  124. entity.TenantId = tenantId;
  125. entity.CompanyId ??= 1000;
  126. entity.CreateBy = _userManager.UserId;
  127. entity.CreateByName = _userManager.Account;
  128. entity.CreateTime = DateTime.Now;
  129. entity.UpdateBy = _userManager.UserId;
  130. entity.UpdateByName = _userManager.Account;
  131. entity.UpdateTime = DateTime.Now;
  132. entity.RequestDate = ParseDate(input.RequestDate);
  133. entity.ArrivalDate = ParseDate(input.ArrivalDate);
  134. await _rep.InsertAsync(entity);
  135. return new { id = entity.Id, message = "新增成功" };
  136. }
  137. else
  138. {
  139. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  140. var entity = await _rep.GetFirstAsync(x => x.Id == input.Id.Value && (x.IsDeleted == null || x.IsDeleted == 0) && x.TenantId == tenantId)
  141. ?? throw Oops.Oh("记录不存在");
  142. entity.ItemNum = input.ItemNum?.Trim();
  143. entity.FVersion = input.FVersion;
  144. entity.Drawing = input.Drawing;
  145. entity.RequestDate = ParseDate(input.RequestDate);
  146. entity.ArrivalDate = ParseDate(input.ArrivalDate);
  147. entity.ShortQty = input.ShortQty;
  148. entity.MesQty = input.MesQty;
  149. entity.LocQty = input.LocQty;
  150. entity.SechedQty = input.SechedQty;
  151. entity.ToSechedQty = input.ToSechedQty;
  152. entity.Remarks = input.Remarks;
  153. entity.CompanyId = input.CompanyId ?? entity.CompanyId ?? 1000;
  154. entity.FactoryId = input.FactoryId ?? entity.FactoryId;
  155. entity.UpdateBy = _userManager.UserId;
  156. entity.UpdateByName = _userManager.Account;
  157. entity.UpdateTime = DateTime.Now;
  158. await _rep.UpdateAsync(entity);
  159. return new { id = entity.Id, message = "编辑成功" };
  160. }
  161. }
  162. [DisplayName("删除物料需求计划")]
  163. [HttpPost("demand-schedule/delete/{id:long}")]
  164. public async Task<object> Delete(long id)
  165. {
  166. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  167. var entity = await _rep.GetFirstAsync(x => x.Id == id && (x.IsDeleted == null || x.IsDeleted == 0) && x.TenantId == tenantId)
  168. ?? throw Oops.Oh("记录不存在");
  169. entity.IsDeleted = 1;
  170. entity.UpdateBy = _userManager.UserId;
  171. entity.UpdateByName = _userManager.Account;
  172. entity.UpdateTime = DateTime.Now;
  173. await _rep.UpdateAsync(entity);
  174. return new { message = "删除成功" };
  175. }
  176. [DisplayName("勾选发布")]
  177. [HttpPost("demand-schedule/publish-selected")]
  178. public async Task<object> PublishSelected([FromBody] DemandScheduleBatchIdsInput input)
  179. {
  180. var ids = ParseIds(input.Ids);
  181. if (!ids.Any()) throw Oops.Oh("请选择要发布的数据");
  182. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  183. var affected = await _db.Updateable<DemandSchedule>()
  184. .SetColumns(x => new DemandSchedule
  185. {
  186. Status = "P",
  187. UpdateBy = _userManager.UserId,
  188. UpdateByName = _userManager.Account,
  189. UpdateTime = DateTime.Now
  190. })
  191. .Where(x => ids.Contains(x.Id) && (x.IsDeleted == null || x.IsDeleted == 0) && (x.Status == null || x.Status == "") && x.TenantId == tenantId)
  192. .ExecuteCommandAsync();
  193. return new { affected, message = affected > 0 ? "发布成功" : "无可发布数据" };
  194. }
  195. [DisplayName("全部发布")]
  196. [HttpPost("demand-schedule/publish-all")]
  197. public async Task<object> PublishAll()
  198. {
  199. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  200. var affected = await _db.Updateable<DemandSchedule>()
  201. .SetColumns(x => new DemandSchedule
  202. {
  203. Status = "P",
  204. UpdateBy = _userManager.UserId,
  205. UpdateByName = _userManager.Account,
  206. UpdateTime = DateTime.Now
  207. })
  208. .Where(x => (x.IsDeleted == null || x.IsDeleted == 0) && (x.Status == null || x.Status == "") && x.TenantId == tenantId)
  209. .ExecuteCommandAsync();
  210. return new { affected, message = affected > 0 ? "全部发布成功" : "无可发布数据" };
  211. }
  212. [DisplayName("取消发布")]
  213. [HttpPost("demand-schedule/unpublish-selected")]
  214. public async Task<object> UnPublishSelected([FromBody] DemandScheduleBatchIdsInput input)
  215. {
  216. var ids = ParseIds(input.Ids);
  217. if (!ids.Any()) throw Oops.Oh("请选择要取消发布的数据");
  218. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  219. var affected = await _db.Updateable<DemandSchedule>()
  220. .SetColumns(x => new DemandSchedule
  221. {
  222. Status = "",
  223. UpdateBy = _userManager.UserId,
  224. UpdateByName = _userManager.Account,
  225. UpdateTime = DateTime.Now
  226. })
  227. .Where(x => ids.Contains(x.Id) && (x.IsDeleted == null || x.IsDeleted == 0) && x.Status == "P" && x.TenantId == tenantId)
  228. .ExecuteCommandAsync();
  229. return new { affected, message = affected > 0 ? "取消发布成功" : "无可取消发布数据" };
  230. }
  231. private static DateTime? ParseDate(string? value)
  232. {
  233. return DateTime.TryParse(value, out var dt) ? dt.Date : null;
  234. }
  235. private static List<long> ParseIds(string ids)
  236. {
  237. return ids.Split(',', StringSplitOptions.RemoveEmptyEntries)
  238. .Select(x => long.TryParse(x.Trim(), out var id) ? id : 0)
  239. .Where(x => x > 0)
  240. .Distinct()
  241. .ToList();
  242. }
  243. private sealed class DemandScheduleListRow
  244. {
  245. public long Id { get; set; }
  246. public string? ItemNum { get; set; }
  247. public string? Descr { get; set; }
  248. public string? Descr1 { get; set; }
  249. public string? FVersion { get; set; }
  250. public string? Drawing { get; set; }
  251. public DateTime? RequestDate { get; set; }
  252. public DateTime? ArrivalDate { get; set; }
  253. public decimal? ShortQty { get; set; }
  254. public decimal? MesQty { get; set; }
  255. public decimal? LocQty { get; set; }
  256. public decimal? SechedQty { get; set; }
  257. public decimal? ToSechedQty { get; set; }
  258. public string? Status { get; set; }
  259. public string? StatusText { get; set; }
  260. public string? PublishDate { get; set; }
  261. public string? Remarks { get; set; }
  262. public long? TenantId { get; set; }
  263. public long? FactoryId { get; set; }
  264. public long? CompanyId { get; set; }
  265. public string? WoList { get; set; }
  266. }
  267. }