AdoS0SrmPurchasesController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using Admin.NET.Plugin.AiDOP.Dto.S0.Supply;
  2. using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
  3. using Admin.NET.Plugin.AiDOP.Entity.S0.Supply;
  4. using Admin.NET.Plugin.AiDOP.Infrastructure;
  5. namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Supply;
  6. /// <summary>
  7. /// S0 货源清单(srm_purchase 语义,左联物料主数据展示)
  8. /// </summary>
  9. [ApiController]
  10. [Route("api/s0/supply/srm-purchases")]
  11. [NonUnify]
  12. public class AdoS0SrmPurchasesController : ControllerBase
  13. {
  14. private readonly SqlSugarRepository<AdoS0SrmPurchase> _rep;
  15. private readonly SqlSugarRepository<AdoS0ItemMaster> _itemRep;
  16. private readonly SqlSugarRepository<AdoS0SuppMaster> _suppRep;
  17. public AdoS0SrmPurchasesController(
  18. SqlSugarRepository<AdoS0SrmPurchase> rep,
  19. SqlSugarRepository<AdoS0ItemMaster> itemRep,
  20. SqlSugarRepository<AdoS0SuppMaster> suppRep)
  21. {
  22. _rep = rep;
  23. _itemRep = itemRep;
  24. _suppRep = suppRep;
  25. }
  26. [HttpGet]
  27. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS0SrmPurchaseQueryDto q)
  28. {
  29. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  30. (q.Page, q.PageSize) = PagingGuard.Normalize(q.Page, q.PageSize);
  31. var baseQuery = _rep.ScopedTo(tenantId)
  32. .WhereIF(q.CompanyRefId.HasValue, x => x.CompanyRefId == q.CompanyRefId.Value)
  33. .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId.Value)
  34. .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
  35. .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierType), x => x.SupplierType != null && x.SupplierType.Contains(q.SupplierType!))
  36. // 启用筛选按启用/停用编码集合等值匹配(IN),废弃旧 Contains("true"/"false") 无法命中 DB“是”的错误口径。
  37. .WhereIF(q.IsActive == true, AdoS0SrmPurchaseIsActive.EnabledSql("is_active"))
  38. .WhereIF(q.IsActive == false, AdoS0SrmPurchaseIsActive.DisabledSql("is_active"))
  39. .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierName), x => x.SupplierName != null && x.SupplierName.Contains(q.SupplierName!))
  40. .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierNumber), x => x.SupplierNumber != null && x.SupplierNumber.Contains(q.SupplierNumber!))
  41. .WhereIF(q.CurrencyType.HasValue, x => x.CurrencyType == q.CurrencyType.Value);
  42. if (!string.IsNullOrWhiteSpace(q.Keyword))
  43. {
  44. var kw = q.Keyword!;
  45. var itemIds = await _itemRep.ScopedTo(tenantId)
  46. .Where(it =>
  47. it.ItemNum.Contains(kw) ||
  48. it.Descr.Contains(kw) ||
  49. (it.Descr1 != null && it.Descr1.Contains(kw)))
  50. .Select(it => it.Id)
  51. .ToListAsync();
  52. baseQuery = baseQuery.Where(x =>
  53. (x.IcitemName != null && x.IcitemName.Contains(kw)) ||
  54. (x.SupplierName != null && x.SupplierName.Contains(kw)) ||
  55. (x.SupplierNumber != null && x.SupplierNumber.Contains(kw)) ||
  56. (itemIds.Count > 0 && itemIds.Contains(x.IcitemId)));
  57. }
  58. var total = await baseQuery.CountAsync();
  59. var list = await baseQuery
  60. .OrderByDescending(x => x.CreateTime)
  61. .Skip((q.Page - 1) * q.PageSize)
  62. .Take(q.PageSize)
  63. .ToListAsync();
  64. await ApplyItemAndSupplierDisplayAsync(list, tenantId);
  65. var dtoList = list.Select(AdoS0SrmPurchaseDto.FromEntity).ToList();
  66. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list = dtoList });
  67. }
  68. [HttpGet("{id:long}")]
  69. public async Task<IActionResult> GetAsync(long id)
  70. {
  71. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  72. var item = await _rep.ByIdScopedAsync(id, tenantId);
  73. if (item == null) return NotFound();
  74. await ApplyItemAndSupplierDisplayAsync(new List<AdoS0SrmPurchase> { item }, tenantId);
  75. return Ok(AdoS0SrmPurchaseDto.FromEntity(item));
  76. }
  77. [HttpPost]
  78. public async Task<IActionResult> CreateAsync([FromBody] AdoS0SrmPurchaseUpsertDto dto)
  79. {
  80. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  81. var dateErr = ValidateDateRange(dto.EffectiveDate, dto.ExpiringDate);
  82. if (dateErr != null) return AdoS0ApiErrors.InvalidRequest(dateErr);
  83. var refErr = await ValidateReferencesAsync(dto.IcitemId, dto.SupplierId, tenantId);
  84. if (refErr != null) return refErr;
  85. var now = DateTime.Now;
  86. var entity = MapDtoToEntity(dto, new AdoS0SrmPurchase(), now, isNew: true);
  87. // 租户只由后端可信上下文赋值,忽略 DTO 中任何 tenant 相关入参。
  88. entity.TenantId = tenantId;
  89. await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
  90. await ApplyItemAndSupplierDisplayAsync(new List<AdoS0SrmPurchase> { entity }, tenantId);
  91. return Ok(AdoS0SrmPurchaseDto.FromEntity(entity));
  92. }
  93. [HttpPut("{id:long}")]
  94. public async Task<IActionResult> UpdateAsync(long id, [FromBody] AdoS0SrmPurchaseUpsertDto dto)
  95. {
  96. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  97. var entity = await _rep.ByIdScopedAsync(id, tenantId);
  98. if (entity == null) return NotFound();
  99. var dateErr = ValidateDateRange(dto.EffectiveDate, dto.ExpiringDate);
  100. if (dateErr != null) return AdoS0ApiErrors.InvalidRequest(dateErr);
  101. var refErr = await ValidateReferencesAsync(dto.IcitemId, dto.SupplierId, tenantId);
  102. if (refErr != null) return refErr;
  103. MapDtoToEntity(dto, entity, DateTime.Now, isNew: false);
  104. await _rep.AsUpdateable(entity).ExecuteCommandAsync();
  105. await ApplyItemAndSupplierDisplayAsync(new List<AdoS0SrmPurchase> { entity }, tenantId);
  106. return Ok(AdoS0SrmPurchaseDto.FromEntity(entity));
  107. }
  108. [HttpDelete("{id:long}")]
  109. public async Task<IActionResult> DeleteAsync(long id)
  110. {
  111. if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
  112. var item = await _rep.ByIdScopedAsync(id, tenantId);
  113. if (item == null) return NotFound();
  114. await _rep.DeleteAsync(item);
  115. return Ok(new { message = "删除成功" });
  116. }
  117. private static string? ValidateDateRange(DateTime? effective, DateTime? expiring)
  118. {
  119. if (effective.HasValue && expiring.HasValue && effective.Value.Date > expiring.Value.Date)
  120. return "生效日期不能晚于失效日期";
  121. return null;
  122. }
  123. private async Task<IActionResult?> ValidateReferencesAsync(long icitemId, long supplierId, long tenantId)
  124. {
  125. if (!await _itemRep.IsAnyAsync(x => x.TenantId == tenantId && x.Id == icitemId))
  126. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.MaterialReferenceInvalid, "物料主数据引用无效");
  127. if (!await _suppRep.IsAnyAsync(x => x.TenantId == tenantId && x.Id == supplierId))
  128. return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.InvalidReference, "供应商主数据引用无效");
  129. return null;
  130. }
  131. private static AdoS0SrmPurchase MapDtoToEntity(AdoS0SrmPurchaseUpsertDto dto, AdoS0SrmPurchase entity, DateTime now, bool isNew)
  132. {
  133. entity.CompanyRefId = dto.CompanyRefId;
  134. entity.FactoryRefId = dto.FactoryRefId;
  135. entity.DomainCode = dto.DomainCode;
  136. entity.IcitemId = dto.IcitemId;
  137. entity.IcitemName = dto.IcitemName;
  138. entity.SupplierType = dto.SupplierType?.Trim();
  139. // bool? → “是”/“否”/null 显式编码,不依赖 JSON bool 自动转 string。
  140. entity.IsActive = AdoS0SrmPurchaseIsActive.Encode(dto.IsActive);
  141. entity.SupplierId = dto.SupplierId;
  142. entity.SupplierName = dto.SupplierName;
  143. entity.SupplierNumber = dto.SupplierNumber;
  144. entity.OrderPrice = dto.OrderPrice;
  145. entity.CurrencyType = dto.CurrencyType;
  146. entity.Taxrate = dto.Taxrate;
  147. entity.Tariff = dto.Tariff;
  148. entity.Freight = dto.Freight;
  149. entity.PriceTerms = dto.PriceTerms;
  150. entity.EffectiveDate = dto.EffectiveDate;
  151. entity.ExpiringDate = dto.ExpiringDate;
  152. entity.QuotaRate = dto.QuotaRate;
  153. entity.LeadTime = dto.LeadTime;
  154. entity.QtyMin = dto.QtyMin;
  155. entity.PackagingQty = dto.PackagingQty;
  156. entity.OrderRectorName = dto.OrderRectorName;
  157. entity.OrderRectorNum = dto.OrderRectorNum;
  158. entity.IsRequireGoods = dto.IsRequireGoods;
  159. if (isNew)
  160. {
  161. entity.CreateUser = dto.CreateUser;
  162. entity.CreateTime = now;
  163. entity.UpdateUser = dto.UpdateUser;
  164. entity.UpdateTime = null;
  165. }
  166. else
  167. {
  168. entity.UpdateUser = dto.UpdateUser;
  169. entity.UpdateTime = now;
  170. }
  171. return entity;
  172. }
  173. private async Task ApplyItemAndSupplierDisplayAsync(List<AdoS0SrmPurchase> rows, long tenantId)
  174. {
  175. if (rows.Count == 0) return;
  176. // 物料编码直接取 srm_purchase.number(历史迁移已回填,100% 有值)。
  177. // 旧平台 icitem_id 为雪花 ID,与本地 ItemMaster.RecID 不同域、匹配为 0,故辅助展示字段
  178. // (规格/单位/物料类型)改按 number == ItemMaster.ItemNum 关联补齐;未匹配行仅缺辅助字段。
  179. var numbers = rows.Select(x => x.Number)
  180. .Where(n => !string.IsNullOrWhiteSpace(n))
  181. .Distinct()
  182. .ToList();
  183. var items = numbers.Count == 0
  184. ? new List<AdoS0ItemMaster>()
  185. : await _itemRep.ScopedTo(tenantId).Where(it => numbers.Contains(it.ItemNum)).ToListAsync();
  186. // MySQL 默认 ci 排序,用大小写不敏感字典对齐旧 SQL 的 number=ItemNum 关联语义。
  187. var itemByNum = new Dictionary<string, AdoS0ItemMaster>(StringComparer.OrdinalIgnoreCase);
  188. foreach (var it in items)
  189. {
  190. if (!string.IsNullOrWhiteSpace(it.ItemNum) && !itemByNum.ContainsKey(it.ItemNum))
  191. itemByNum[it.ItemNum] = it;
  192. }
  193. foreach (var sp in rows)
  194. {
  195. AdoS0ItemMaster? it = null;
  196. if (!string.IsNullOrWhiteSpace(sp.Number))
  197. itemByNum.TryGetValue(sp.Number, out it);
  198. sp.MaterialCode = sp.Number;
  199. sp.Model = it?.Descr1;
  200. sp.Unit = it?.UM;
  201. sp.ItemTypeLabel = string.IsNullOrWhiteSpace(it?.ItemType) ? "原材料" : it!.ItemType;
  202. var num = sp.MaterialCode ?? "";
  203. var name = sp.IcitemName ?? "";
  204. sp.Icitem = $"{num}{name}";
  205. var sname = sp.SupplierName ?? "";
  206. var snum = sp.SupplierNumber ?? "";
  207. sp.Supplier = $"{sname}{snum}";
  208. }
  209. }
  210. }