using Admin.NET.Plugin.AiDOP.MaterialWarehouse.Dto;
namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
///
/// S5 进出存查询 只读 list 服务。
///
/// 数据源:aidopdev.InvTransHist(库存交易流水),忠实复刻证据 sql.txt:
/// LEFT JOIN GeneralizedCodeMaster(TransType→名称)、LocationMaster(货位名)、ItemMaster(物料名/单位),
/// 过滤 a.Domain=@Domain。进出存口径由 SQL 内定义(非脑补):
/// 期初 = BeginBalance;入库 = QtyChange>0?QtyChange:0;出库 = QtyChange<0?abs(QtyChange):0;
/// 期末(结存) = BeginBalance+QtyChange。逐笔交易流水,BeginBalance 按行存储。
/// Domain 由 GeneralizedCodeMaster(SystemConfig/CompanyCode) 解析,默认 8010。
/// 排序 a.RecID DESC(PK,近似交易时间倒序,兼顾 1M 行分页性能与稳定)。
///
/// 本服务仅 SELECT:无新增/编辑/删除/库存事务/状态流转。
/// 安全:InvTransHist / LocationMaster / GeneralizedCodeMaster 均含 tenant_id,主业务查询按当前租户强制过滤;
/// 租户只来自认证后的可信 JWT(严格解析),忽略前端 tenantId,杜绝匿名/跨租户可见。
///
[ApiDescriptionSettings(Order = 303, Description = "进出存查询")]
[Route("api/S5InoutQuery")]
[NonUnify]
public class InoutQueryService : IDynamicApiController, ITransient
{
/// 主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。
private const long MainTenantId = 1300000000001L;
private readonly ISqlSugarClient _db;
private readonly UserManager _userManager;
public InoutQueryService(ISqlSugarClient db, UserManager userManager)
{
_db = db;
_userManager = userManager;
}
/// 严格可信租户解析:只来自认证后 JWT;无 Token(<=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。
private long ResolveTenantOrThrow()
{
var tid = _userManager.TenantId;
if (tid <= 0)
throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
if (_userManager.SuperAdmin && tid == MainTenantId)
throw Oops.Oh("超级管理员操作前必须选择目标租户");
return tid;
}
/// 解析公司域(GeneralizedCodeMaster SystemConfig/CompanyCode,默认 8010)。
private async Task ResolveDomainAsync()
{
var list = await _db.Ado.SqlQueryAsync(
"SELECT Domain FROM GeneralizedCodeMaster WHERE FldName='SystemConfig' AND Val='CompanyCode' ORDER BY RecID LIMIT 1");
var domain = list.FirstOrDefault();
return string.IsNullOrWhiteSpace(domain) ? "8010" : domain;
}
///
/// 进出存查询列表(只读分页查询)。
///
[DisplayName("进出存查询列表")]
[HttpGet("list")]
public async Task