|
|
@@ -6,10 +6,32 @@ namespace Admin.NET.Plugin.AiDOP.ProcurementExecution;
|
|
|
|
|
|
/// <summary>
|
|
|
/// S4 供应商发货单(列表 + 表单)
|
|
|
+///
|
|
|
+/// 【租户安全边界】租户一律经 <see cref="AidopTenantScope.ResolveOrThrow"/> 从认证后 JWT 解析,
|
|
|
+/// 无有效租户即拒绝,不读前端 tenantId、无默认回退。
|
|
|
+/// <para>
|
|
|
+/// 原实现:类级 <c>[AllowAnonymous]</c> + 全类使用 <c>AidopTenantHelper.Resolve(App.HttpContext)</c>。
|
|
|
+/// 后者的解析顺序为 requestTenantId > JWT claim > <b>query 参数 tenantId</b> > <b>DefaultTenantId 兜底</b>,
|
|
|
+/// 其自身注释即写明「允许前端指定租户且有默认回退,<b>不得用于业务数据的查询与写入</b>」。
|
|
|
+/// 两者叠加的实测后果:匿名(无 Token、无 Cookie)GET
|
|
|
+/// <c>/api/ProcurementExecution/supplier-shipment/list?tenantId=<任意租户></c> 可读回该租户业务数据
|
|
|
+/// (797 返回 13 条、UAT 返回 2 条);同类的 HttpPost 写接口走同一条解析链。
|
|
|
+/// </para>
|
|
|
+/// <para>
|
|
|
+/// 本类的写 SQL 一律带 <c>tenant_id = @TenantId</c>:业务键 shddh/shdh 在库中无唯一索引,
|
|
|
+/// 一旦将来出现跨租户重复单号,仅按业务键定位会造成越权改写。
|
|
|
+/// </para>
|
|
|
+/// <para>
|
|
|
+/// 生成标签的 <c>INSERT INTO MissedPrint ... SELECT ... FROM scm_shbq</c> 另有两处缺陷一并修复:
|
|
|
+/// ① 未写 <c>tenant_id</c>,导致库中 152 行标签全部为 NULL、下游 008/013 无法按租户过滤;
|
|
|
+/// ② <c>LEFT JOIN ItemMaster ON ItemNum</c> 未限定租户,而 ItemMaster 有 663 个 ItemNum 跨租户重名
|
|
|
+/// (<c>(tenant_id, ItemNum)</c> 唯一、单独 ItemNum 不唯一),实测按现网 scm_shbq 数据:
|
|
|
+/// 797 的 50 行会放大成 82 行、UAT 的 24 行会放大成 48 行,且 ExpireDate 可能取自其它租户的物料主数据。
|
|
|
+/// 现补 <c>i.tenant_id / p.tenant_id / s.tenant_id = @TenantId</c>。
|
|
|
+/// </para>
|
|
|
/// </summary>
|
|
|
[ApiDescriptionSettings(Order = 321, Description = "S4供应商发货单")]
|
|
|
[Route("api/ProcurementExecution")]
|
|
|
-[AllowAnonymous]
|
|
|
[NonUnify]
|
|
|
public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
{
|
|
|
@@ -39,7 +61,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
[HttpGet("supplier-shipment/list")]
|
|
|
public async Task<object> GetList([FromQuery] SupplierShipmentListInput input)
|
|
|
{
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
|
|
|
var conditions = new List<string> { "IFNULL(m.state, 1) <> 0" };
|
|
|
if (!string.IsNullOrWhiteSpace(input.JhshrqFrom))
|
|
|
@@ -99,7 +121,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
[HttpGet("supplier-shipment/{id:long}")]
|
|
|
public async Task<object> GetDetail(long id)
|
|
|
{
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var master = await _masterRep.GetFirstAsync(x => x.Id == id && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
|
|
|
var details = await _detailRep.AsQueryable().Where(x => x.Glid == id.ToString() && x.TenantId == tenantId).OrderBy(x => x.Hh).ToListAsync();
|
|
|
return new
|
|
|
@@ -144,7 +166,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
[HttpGet("supplier-shipment/create-draft")]
|
|
|
public async Task<object> GetCreateDraft([FromQuery] string ids)
|
|
|
{
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var pars = new List<SugarParameter> { new("@ids", ids), new("@TenantId", tenantId) };
|
|
|
var rows = await _db.Ado.SqlQueryAsync<SupplierShipmentDraftRow>(
|
|
|
"""
|
|
|
@@ -220,7 +242,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
public async Task<object> Save([FromBody] SupplierShipmentSaveInput input)
|
|
|
{
|
|
|
var now = DateTime.Now;
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var userId = _userManager.UserId.ToString();
|
|
|
var userName = _userManager.Account ?? "system";
|
|
|
var shipDate = string.IsNullOrWhiteSpace(input.Jhshrq) ? now.ToString("yyyy-MM-dd") : input.Jhshrq!.Trim();
|
|
|
@@ -272,7 +294,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
[ApiDescriptionSettings(Name = "DeleteSupplierShipment"), HttpPost("supplier-shipment/delete")]
|
|
|
public async Task<object> Delete([FromBody] SupplierShipmentDeleteInput input)
|
|
|
{
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
|
|
|
master.State = 0;
|
|
|
await _masterRep.UpdateAsync(master);
|
|
|
@@ -286,7 +308,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
if (input.Id <= 0)
|
|
|
throw Oops.Oh("缺少发货单ID(id)");
|
|
|
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var userName = _userManager.Account ?? "system";
|
|
|
|
|
|
var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id && x.TenantId == tenantId) ?? throw Oops.Oh("发货单不存在");
|
|
|
@@ -323,9 +345,10 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
""",
|
|
|
new List<SugarParameter> { new("@shdh", shddh) });
|
|
|
|
|
|
+ // 业务键 shdh 无唯一索引,必须叠加租户条件,否则跨租户重号时会删掉别人的标签
|
|
|
await _db.Ado.ExecuteCommandAsync(
|
|
|
- "DELETE FROM scm_shbq WHERE shdh=@shdh",
|
|
|
- new List<SugarParameter> { new("@shdh", shddh) });
|
|
|
+ "DELETE FROM scm_shbq WHERE shdh=@shdh AND tenant_id=@TenantId",
|
|
|
+ new List<SugarParameter> { new("@shdh", shddh), new("@TenantId", tenantId) });
|
|
|
|
|
|
// 2) 需要补 shpc 的明细(按物料+供应商批号去重)
|
|
|
var needShpc = await _db.Ado.SqlQueryAsync<NeedShpcRow>(
|
|
|
@@ -513,8 +536,8 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
|
|
|
// 5) 更新发货单状态
|
|
|
await _db.Ado.ExecuteCommandAsync(
|
|
|
- "UPDATE scm_shd SET shzt='待收', state=2, dycs=0 WHERE shddh=@shddh",
|
|
|
- new List<SugarParameter> { new("@shddh", shddh) });
|
|
|
+ "UPDATE scm_shd SET shzt='待收', state=2, dycs=0 WHERE shddh=@shddh AND tenant_id=@TenantId",
|
|
|
+ new List<SugarParameter> { new("@shddh", shddh), new("@TenantId", tenantId) });
|
|
|
|
|
|
// 6) MissedPrint:作废旧未打印(U)
|
|
|
await _db.Ado.ExecuteCommandAsync(
|
|
|
@@ -526,7 +549,8 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
RelatedBarCode = '',
|
|
|
UpdateTime = NOW(),
|
|
|
UpdateUser = @u
|
|
|
- WHERE Domain = @domain
|
|
|
+ WHERE tenant_id = @TenantId
|
|
|
+ AND Domain = @domain
|
|
|
AND ShipperNbr = @shddh
|
|
|
AND Status = 'U';
|
|
|
""",
|
|
|
@@ -535,6 +559,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
new("@u", userName),
|
|
|
new("@domain", domain),
|
|
|
new("@shddh", shddh),
|
|
|
+ new("@TenantId", tenantId),
|
|
|
});
|
|
|
|
|
|
// 7) MissedPrint:插入待打印(U)
|
|
|
@@ -551,7 +576,8 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
LabelFormat, StandItem, EffSize, GP12CheckedQty, NetWeight,
|
|
|
Remark, CreateTime, UpdateTime, CreateUser, UpdateUser,
|
|
|
LevelChar, PurOrdDetBatchNbr, FirmString5, ExpireDate,
|
|
|
- Printer, Company, Checker, Position
|
|
|
+ Printer, Company, Checker, Position,
|
|
|
+ tenant_id
|
|
|
)
|
|
|
SELECT
|
|
|
@domain AS Domain,
|
|
|
@@ -605,17 +631,24 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
'' AS Printer,
|
|
|
'' AS Company,
|
|
|
'' AS Checker,
|
|
|
- '' AS Position
|
|
|
+ '' AS Position,
|
|
|
+ @TenantId AS tenant_id
|
|
|
FROM scm_shbq s
|
|
|
- LEFT JOIN PurOrdDetail p ON p.PurOrd = s.po_billno AND p.Line = s.po_billline
|
|
|
- LEFT JOIN ItemMaster i ON i.ItemNum = s.sh_material_code
|
|
|
- WHERE s.shdh = @shddh;
|
|
|
+ LEFT JOIN PurOrdDetail p
|
|
|
+ ON p.PurOrd = s.po_billno AND p.Line = s.po_billline
|
|
|
+ AND p.tenant_id = @TenantId
|
|
|
+ LEFT JOIN ItemMaster i
|
|
|
+ ON i.ItemNum = s.sh_material_code
|
|
|
+ AND i.tenant_id = @TenantId
|
|
|
+ WHERE s.shdh = @shddh
|
|
|
+ AND s.tenant_id = @TenantId;
|
|
|
""",
|
|
|
new List<SugarParameter>
|
|
|
{
|
|
|
new("@domain", domain),
|
|
|
new("@u", userName),
|
|
|
new("@shddh", shddh),
|
|
|
+ new("@TenantId", tenantId),
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -715,7 +748,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
/// </summary>
|
|
|
private async Task<string> AllocateShddhAsync(string supplierCode)
|
|
|
{
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var code = (supplierCode ?? string.Empty).Trim().Replace("-", string.Empty);
|
|
|
if (string.IsNullOrEmpty(code))
|
|
|
throw Oops.Oh("请先填写供应商编号,保存时将用于生成发货单号");
|
|
|
@@ -877,7 +910,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
if (string.IsNullOrWhiteSpace(shddh))
|
|
|
throw Oops.Oh("缺少发货单编号(shddh)");
|
|
|
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var pars = new List<SugarParameter> { new("@shddh", shddh.Trim()), new("@TenantId", tenantId) };
|
|
|
|
|
|
const string masterSql = """
|
|
|
@@ -1009,7 +1042,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
if (string.IsNullOrWhiteSpace(shddh))
|
|
|
throw Oops.Oh("缺少发货单编号(shddh)");
|
|
|
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var pars = new List<SugarParameter> { new("@shddh", shddh.Trim()), new("@TenantId", tenantId) };
|
|
|
const string sql = """
|
|
|
SELECT DISTINCT
|
|
|
@@ -1045,7 +1078,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
|
|
|
|
|
|
private async Task SaveDetailsAsync(long masterId, List<SupplierShipmentDetailInput> inputDetails)
|
|
|
{
|
|
|
- var tenantId = AidopTenantHelper.Resolve(App.HttpContext);
|
|
|
+ var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
|
|
|
var dbDetails = await _detailRep.AsQueryable().Where(x => x.Glid == masterId.ToString() && x.TenantId == tenantId).ToListAsync();
|
|
|
var dbById = dbDetails.ToDictionary(x => x.Id);
|
|
|
var inputIds = new HashSet<long>(inputDetails.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
|