using Admin.NET.Plugin.AiDOP.Dto.S0.Manufacturing;
using Admin.NET.Plugin.AiDOP.Dto.S0.Sales;
using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
using Admin.NET.Plugin.AiDOP.Infrastructure;
namespace Admin.NET.Plugin.AiDOP.Controllers.S0.Manufacturing;
///
/// S0 物料工序条件(ItemOpCondition 语义)。
/// 多租户隔离:所有读写显式限定当前请求租户(AdoS0TenantScope.TryResolveRequired),不依赖全局 AOP 兜底。
///
[ApiController]
[Route("api/s0/manufacturing/material-process-elements")]
[NonUnify]
public class AdoS0MfgMaterialProcessElementsController : ControllerBase
{
private readonly SqlSugarRepository _rep;
public AdoS0MfgMaterialProcessElementsController(SqlSugarRepository rep)
{
_rep = rep;
}
[HttpGet]
public async Task GetPagedAsync([FromQuery] AdoS0ItemOpConditionQueryDto q)
{
if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
var page = q.Page;
var pageSize = q.PageSize;
(page, pageSize) = PagingGuard.Normalize(page, pageSize);
// 显式租户隔离:不依赖全局 AOP,直接按当前请求租户过滤;保留 CodeType 业务条件。
var query = _rep.AsQueryable()
.Where(x => x.TenantId == tenantId)
.Where(x => x.CodeType == "Prod")
.WhereIF(!string.IsNullOrWhiteSpace(q.Keyword),
x =>
x.ItemNum.Contains(q.Keyword!)
|| (x.ItemCode != null && x.ItemCode.Contains(q.Keyword!))
|| (x.Descr != null && x.Descr.Contains(q.Keyword!))
|| (x.Op != null && x.Op.Contains(q.Keyword!))
|| (x.Typed != null && x.Typed.Contains(q.Keyword!)))
.WhereIF(!string.IsNullOrWhiteSpace(q.ItemNum), x => x.ItemNum.Contains(q.ItemNum!))
.WhereIF(!string.IsNullOrWhiteSpace(q.Op), x => x.Op != null && x.Op.Contains(q.Op!))
.WhereIF(!string.IsNullOrWhiteSpace(q.Line), x => x.Line != null && x.Line.Contains(q.Line!))
.WhereIF(!string.IsNullOrWhiteSpace(q.Typed), x => x.Typed != null && x.Typed.Contains(q.Typed!))
.WhereIF(!string.IsNullOrWhiteSpace(q.ItemCode), x => x.ItemCode != null && x.ItemCode.Contains(q.ItemCode!));
var total = await query.CountAsync();
var list = await query.OrderByDescending(x => x.UpdateTime ?? x.CreateTime).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
// 展示字段:ItemDescr / OpDescr / TypedDescr(当前阶段仅能稳定补 ItemDescr 与 OpDescr;TypedDescr 先留空)
var itemNums = list.Select(x => x.ItemNum).Distinct().ToList();
// 去 domain 化:物料名补显不再按 Domain 等值;ItemMaster 带 ITenantIdFilter,租户内 ItemNum 唯一,确定性以 IsActive/RecID 稳定排序兜底。
var items = await _rep.Context.Queryable()
.Where(m => m.TenantId == tenantId && itemNums.Contains(m.ItemNum))
.OrderByDescending(m => m.IsActive)
.OrderBy(m => m.Id)
.Select(m => new { m.ItemNum, m.Descr })
.ToListAsync();
var itemDescrMap = items
.GroupBy(x => x.ItemNum)
.ToDictionary(g => g.Key, g => g.First().Descr);
var opKeys = list
.Where(x => !string.IsNullOrWhiteSpace(x.Op))
.Select(x => new { x.ItemNum, Op = x.Op! })
.Distinct()
.ToList();
var opDescrMap = new Dictionary();
if (opKeys.Count > 0)
{
var r = await _rep.Context.Queryable()
.Where(x => x.TenantId == tenantId && x.MaterialCode != null && itemNums.Contains(x.MaterialCode))
.ToListAsync();
foreach (var k in opKeys)
{
var match = r.FirstOrDefault(x => x.MaterialCode == k.ItemNum && x.OperationCode == k.Op);
if (match != null) opDescrMap[$"{k.ItemNum}::{k.Op}"] = match.OperationDescription;
}
}
var shaped = list.Select(x => new
{
id = x.Id,
itemNum = x.ItemNum,
itemDescr = itemDescrMap.TryGetValue(x.ItemNum, out var idesc) ? idesc : "",
op = x.Op,
opDescr = x.Op != null && opDescrMap.TryGetValue($"{x.ItemNum}::{x.Op}", out var odesc) ? odesc : "",
line = x.Line,
typed = x.Typed,
typedDescr = "",
itemCode = x.ItemCode,
descr = x.Descr,
isMain = x.IsMain,
codeType = x.CodeType,
domain = x.Domain,
createUser = x.CreateUser,
createTime = x.CreateTime,
updateUser = x.UpdateUser,
updateTime = x.UpdateTime,
}).ToList();
return Ok(new { total, page, pageSize, list = shaped });
}
[HttpGet("{id:long}")]
public async Task GetAsync(long id)
{
if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
return item == null ? NotFound() : Ok(item);
}
[HttpPost]
public async Task CreateAsync([FromBody] AdoS0ItemOpConditionUpsertDto dto)
{
if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
var now = DateTime.Now;
var entity = new AdoS0ItemOpCondition
{
TenantId = tenantId,
Domain = dto.Domain.Trim(),
CodeType = "Prod",
ItemNum = dto.ItemNum.Trim(),
Op = dto.Op,
Line = dto.Line,
Typed = dto.Typed,
ItemCode = dto.ItemCode,
Descr = dto.Descr,
IsMain = dto.IsMain,
CreateUser = dto.CreateUser ?? dto.UpdateUser,
CreateTime = dto.CreateTime ?? dto.UpdateTime ?? now,
UpdateUser = dto.UpdateUser,
UpdateTime = dto.UpdateTime
};
await _rep.AsInsertable(entity).ExecuteReturnEntityAsync();
return Ok(entity);
}
[HttpPut("{id:long}")]
public async Task UpdateAsync(long id, [FromBody] AdoS0ItemOpConditionUpsertDto dto)
{
if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
var entity = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
if (entity == null) return NotFound();
entity.Domain = dto.Domain.Trim();
entity.CodeType = "Prod";
entity.ItemNum = dto.ItemNum.Trim();
entity.Op = dto.Op;
entity.Line = dto.Line;
entity.Typed = dto.Typed;
entity.ItemCode = dto.ItemCode;
entity.Descr = dto.Descr;
entity.IsMain = dto.IsMain;
entity.UpdateUser = dto.UpdateUser;
entity.UpdateTime = dto.UpdateTime ?? DateTime.Now;
await _rep.AsUpdateable(entity).ExecuteCommandAsync();
return Ok(entity);
}
[HttpDelete("{id:long}")]
public async Task DeleteAsync(long id)
{
if (!AdoS0TenantScope.TryResolveRequired(out var tenantId, out var tenantError)) return tenantError!;
var item = await _rep.AsQueryable().Where(x => x.Id == id && x.TenantId == tenantId).FirstAsync();
if (item == null) return NotFound();
await _rep.DeleteAsync(item);
return Ok(new { message = "删除成功" });
}
}