AdoS0MaterialsController.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Sales;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Sales;
  5. /// <summary>
  6. /// S0 物料主数据(ItemMaster 语义)
  7. /// </summary>
  8. [ApiController]
  9. [Route("api/s0/sales/materials")]
  10. [NonUnify]
  11. public class AdoS0MaterialsController : ControllerBase
  12. {
  13. private readonly SqlSugarRepository<AdoS0ItemMaster> _rep;
  14. private readonly AdoS0ReferenceChecker _refChecker;
  15. public AdoS0MaterialsController(SqlSugarRepository<AdoS0ItemMaster> rep, AdoS0ReferenceChecker refChecker)
  16. {
  17. _rep = rep;
  18. _refChecker = refChecker;
  19. }
  20. [HttpGet]
  21. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0ItemMasterQueryDto 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.ScopedTo(tenantId)
  26. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId.Value)
  27. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId.Value)
  28. .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
  29. .WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum), x => x.ItemNum.Contains(q.ItemNum!))
  30. .WhereIF(!string.IsNullOrWhiteSpace(q.Descr), x => x.Descr.Contains(q.Descr!))
  31. .WhereIF(!string.IsNullOrWhiteSpace(q.Descr1), x => x.Descr1 != null && x.Descr1.Contains(q.Descr1!))
  32. .WhereIF(!string.IsNullOrWhiteSpace(q.ItemType), x => x.ItemType == q.ItemType)
  33. .WhereIF(!string.IsNullOrWhiteSpace(q.PurMfg), x => x.PurMfg == q.PurMfg)
  34. .WhereIF(!string.IsNullOrWhiteSpace(q.Location), x => x.Location != null && x.Location.Contains(q.Location!))
  35. .WhereIF(!string.IsNullOrWhiteSpace(q.Status), x => x.Status == q.Status)
  36. .WhereIF(q.IsActive.HasValue, x => x.IsActive == q.IsActive.Value)
  37. .WhereIF(q.IsConfirm.HasValue, x => x.IsConfirm == q.IsConfirm.Value)
  38. .WhereIF(
  39. !string.IsNullOrWhiteSpace(q.Keyword),
  40. x => x.ItemNum.Contains(q.Keyword!) || x.Descr.Contains(q.Keyword!));
  41. var total = await query.CountAsync();
  42. var list = await query
  43. .OrderByDescending(x => x.CreateTime)
  44. .Skip((q.Page - 1) * q.PageSize)
  45. .Take(q.PageSize)
  46. .ToListAsync();
  47. foreach (var x in list)
  48. x.LocationDescr = FormatLocationDescr(x.Location, null);
  49. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  50. }
  51. [HttpGet("{id:long}")]
  52. public async Task<IActionResult> GetAsync(long id)
  53. {
  54. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  55. var item = await _rep.ByIdScopedAsync(id, tenantId);
  56. if (item == null) return NotFound();
  57. item.LocationDescr = FormatLocationDescr(item.Location, null);
  58. return Ok(item);
  59. }
  60. [HttpPost]
  61. public async Task<IActionResult> CreateAsync([FromBody] AdoS0ItemMasterUpsertDto dto)
  62. {
  63. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  64. var (companyId, factoryId) = await AdoS0OrgScope.ResolveAsync(_rep.Context, tenantId);
  65. if (companyId <= 0 || factoryId <= 0) return AdoS0OrgScope.NotConfiguredError("物料");
  66. if (await _rep.IsAnyAsync(x =>
  67. x.TenantId == tenantId &&
  68. x.CompanyRefId == dto.CompanyRefId &&
  69. x.FactoryRefId == dto.FactoryRefId &&
  70. x.ItemNum == dto.ItemNum))
  71. {
  72. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.MaterialCodeExists, "物料编码已存在");
  73. }
  74. var now = DateTime.Now;
  75. var entity = MapDtoToNewEntity(dto, now);
  76. // 租户只由后端可信上下文赋值,忽略 DTO 中任何 tenant 相关入参。
  77. entity.TenantId = tenantId;
  78. entity.Status = ResolveStatus(dto);
  79. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  80. return Ok(entity);
  81. }
  82. [HttpPut("{id:long}")]
  83. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0ItemMasterUpsertDto dto)
  84. {
  85. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  86. var entity = await _rep.ByIdScopedAsync(id, tenantId);
  87. if (entity == null) return NotFound();
  88. if (await _rep.IsAnyAsync(x =>
  89. x.TenantId == tenantId &&
  90. x.Id != id &&
  91. x.CompanyRefId == dto.CompanyRefId &&
  92. x.FactoryRefId == dto.FactoryRefId &&
  93. x.ItemNum == dto.ItemNum))
  94. {
  95. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.MaterialCodeExists, "物料编码已存在");
  96. }
  97. ApplyDtoToEntity(entity, dto);
  98. entity.Status = ResolveStatus(dto);
  99. entity.UpdateTime = DateTime.Now;
  100. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  101. return Ok(entity);
  102. }
  103. [HttpPatch("{id:long}/toggle-enabled")]
  104. public async Task<IActionResult> ToggleActiveAsync(long id, [FromBody] AdoS0ItemMasterToggleActiveDto dto)
  105. {
  106. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  107. var entity = await _rep.ByIdScopedAsync(id, tenantId);
  108. if (entity == null) return NotFound();
  109. entity.IsActive = dto.IsActive;
  110. entity.Status = AdoS0SalesRules.ForbidStatusFromIsEnabled(dto.IsActive);
  111. entity.UpdateTime = DateTime.Now;
  112. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  113. return Ok(entity);
  114. }
  115. [HttpDelete("{id:long}")]
  116. public async Task<IActionResult> DeleteAsync(long id)
  117. {
  118. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  119. var item = await _rep.ByIdScopedAsync(id, tenantId);
  120. if (item == null) return NotFound();
  121. var refInfo = await _refChecker.MaterialReferencesAsync(tenantId, item.ItemNum);
  122. if (refInfo is { } r)
  123. return AdoS0ApiErrors.Conflict(AdoS0ErrorCodes.DeleteBlocked,
  124. $"存在 {r.Count} 条 {r.Table} 引用该物料,无法删除");
  125. await _rep.DeleteAsync(item);
  126. return Ok(new { message = "删除成功" });
  127. }
  128. private static string ResolveStatus(AdoS0ItemMasterUpsertDto dto) =>
  129. string.IsNullOrWhiteSpace(dto.Status)
  130. ? AdoS0SalesRules.ForbidStatusFromIsEnabled(dto.IsActive)
  131. : dto.Status!;
  132. private static string FormatLocationDescr(string? location, string? locationMasterDescr)
  133. {
  134. var a = location?.Trim();
  135. var b = locationMasterDescr?.Trim();
  136. if (string.IsNullOrEmpty(a)) return b ?? string.Empty;
  137. if (string.IsNullOrEmpty(b)) return a;
  138. return $"{a} {b}".Trim();
  139. }
  140. private static AdoS0ItemMaster MapDtoToNewEntity(AdoS0ItemMasterUpsertDto dto, DateTime now) => new()
  141. {
  142. // 组织字段由 CreateAsync stamp 解析值覆盖(不读 payload)。
  143. DomainCode = dto.DomainCode,
  144. ItemNum = dto.ItemNum,
  145. Descr = dto.Descr,
  146. Descr1 = dto.Descr1,
  147. PkgCode = dto.PkgCode,
  148. UM = dto.UM,
  149. PurMfg = dto.PurMfg,
  150. Drawing = dto.Drawing,
  151. ItemType = dto.ItemType,
  152. Location = dto.Location,
  153. DefaultShelf = dto.DefaultShelf,
  154. KeyItem = dto.KeyItem,
  155. NetWeight = dto.NetWeight,
  156. NetWeightUM = dto.NetWeightUM,
  157. Inspect = dto.Inspect,
  158. PurLT = dto.PurLT.GetValueOrDefault(),
  159. InsLT = dto.InsLT.GetValueOrDefault(),
  160. MfgLT = dto.MfgLT,
  161. Length = dto.Length,
  162. Size = dto.Size,
  163. SizeUM = dto.SizeUM,
  164. IssuePolicy = dto.IssuePolicy,
  165. MfgMttr = dto.MfgMttr,
  166. LocationType = dto.LocationType,
  167. CommodityCode = dto.CommodityCode,
  168. Rev = dto.Rev,
  169. MaxOrd = dto.MaxOrd.GetValueOrDefault(),
  170. MinOrd = dto.MinOrd.GetValueOrDefault(),
  171. OrdMult = dto.OrdMult.GetValueOrDefault(),
  172. MinOrdSales = dto.MinOrdSales.GetValueOrDefault(),
  173. AutoLotNums = dto.AutoLotNums,
  174. Install = dto.Install,
  175. SafetyStk = dto.SafetyStk.GetValueOrDefault(),
  176. DaysBetweenPM = dto.DaysBetweenPM,
  177. ExpireAlarmDay = dto.ExpireAlarmDay,
  178. StockTurnOver = dto.StockTurnOver.GetValueOrDefault(),
  179. LotSerialControl = dto.LotSerialControl,
  180. AllocateSingleLot = dto.AllocateSingleLot,
  181. Planner = dto.Planner,
  182. TraceDetail = dto.TraceDetail,
  183. IsMainMas = dto.IsMainMas,
  184. Remark = dto.Remark,
  185. EMTType = dto.EMTType,
  186. OwnerApplication = dto.OwnerApplication,
  187. BomVersion = dto.BomVersion,
  188. EnableBomVersion = dto.EnableBomVersion,
  189. BOMDesign = dto.BOMDesign,
  190. RoutingDes = dto.RoutingDes,
  191. CustSupplied = dto.CustSupplied,
  192. DrawingDesign = dto.DrawingDesign,
  193. DesignOwner = dto.DesignOwner,
  194. IsActive = dto.IsActive,
  195. IsConfirm = dto.IsConfirm,
  196. MRPRequired = dto.MRPRequired,
  197. CreateUser = dto.CreateUser,
  198. CreateTime = now,
  199. UpdateUser = dto.UpdateUser,
  200. UpdateTime = null
  201. };
  202. private static void ApplyDtoToEntity(AdoS0ItemMaster entity, AdoS0ItemMasterUpsertDto dto)
  203. {
  204. // 组织字段 pin 到既有原值,忽略 payload 组织值:既杜绝篡改成他租户组织,
  205. // 也不因一次普通编辑就顺手迁移历史 CROSS-TENANT 存量。
  206. entity.DomainCode = dto.DomainCode;
  207. entity.ItemNum = dto.ItemNum;
  208. entity.Descr = dto.Descr;
  209. entity.Descr1 = dto.Descr1;
  210. entity.PkgCode = dto.PkgCode;
  211. entity.UM = dto.UM;
  212. entity.PurMfg = dto.PurMfg;
  213. entity.Drawing = dto.Drawing;
  214. entity.ItemType = dto.ItemType;
  215. entity.Location = dto.Location;
  216. entity.DefaultShelf = dto.DefaultShelf;
  217. entity.KeyItem = dto.KeyItem;
  218. entity.NetWeight = dto.NetWeight;
  219. entity.NetWeightUM = dto.NetWeightUM;
  220. entity.Inspect = dto.Inspect;
  221. entity.PurLT = dto.PurLT.GetValueOrDefault();
  222. entity.InsLT = dto.InsLT.GetValueOrDefault();
  223. entity.MfgLT = dto.MfgLT;
  224. entity.Length = dto.Length;
  225. entity.Size = dto.Size;
  226. entity.SizeUM = dto.SizeUM;
  227. entity.IssuePolicy = dto.IssuePolicy;
  228. entity.MfgMttr = dto.MfgMttr;
  229. entity.LocationType = dto.LocationType;
  230. entity.CommodityCode = dto.CommodityCode;
  231. entity.Rev = dto.Rev;
  232. entity.MaxOrd = dto.MaxOrd.GetValueOrDefault();
  233. entity.MinOrd = dto.MinOrd.GetValueOrDefault();
  234. entity.OrdMult = dto.OrdMult.GetValueOrDefault();
  235. entity.MinOrdSales = dto.MinOrdSales.GetValueOrDefault();
  236. entity.AutoLotNums = dto.AutoLotNums;
  237. entity.Install = dto.Install;
  238. entity.SafetyStk = dto.SafetyStk.GetValueOrDefault();
  239. entity.DaysBetweenPM = dto.DaysBetweenPM;
  240. entity.ExpireAlarmDay = dto.ExpireAlarmDay;
  241. entity.StockTurnOver = dto.StockTurnOver.GetValueOrDefault();
  242. entity.LotSerialControl = dto.LotSerialControl;
  243. entity.AllocateSingleLot = dto.AllocateSingleLot;
  244. entity.Planner = dto.Planner;
  245. entity.TraceDetail = dto.TraceDetail;
  246. entity.IsMainMas = dto.IsMainMas;
  247. entity.Remark = dto.Remark;
  248. entity.EMTType = dto.EMTType;
  249. entity.OwnerApplication = dto.OwnerApplication;
  250. entity.BomVersion = dto.BomVersion;
  251. entity.EnableBomVersion = dto.EnableBomVersion;
  252. entity.BOMDesign = dto.BOMDesign;
  253. entity.RoutingDes = dto.RoutingDes;
  254. entity.CustSupplied = dto.CustSupplied;
  255. entity.DrawingDesign = dto.DrawingDesign;
  256. entity.DesignOwner = dto.DesignOwner;
  257. entity.IsActive = dto.IsActive;
  258. entity.IsConfirm = dto.IsConfirm;
  259. entity.MRPRequired = dto.MRPRequired;
  260. entity.UpdateUser = dto.UpdateUser;
  261. }
  262. }