|
|
@@ -123,8 +123,7 @@ public class AdoS0MfgRoutingOpDetailsController : ControllerBase
|
|
|
// 底层 checker 语义保持不变(不在 checker 里「查不到就放行」)。
|
|
|
//
|
|
|
// 各引用的 scope 依据(按 AdoS0ReferenceChecker 实现推导,非猜测):
|
|
|
- // MaterialCode -> MaterialExistsInScopeAsync(tenantId, itemNum, CompanyRefId, FactoryRefId)
|
|
|
- // 故 scope 字段 = CompanyRefId + FactoryRefId
|
|
|
+ // MaterialCode -> 值未变时按 CompanyRefId + FactoryRefId 作用域(见下方 else 分支)
|
|
|
// WorkCenterCode -> WorkCenterExistsAsync(tenantId, workCenterCode)
|
|
|
// 只取 tenantId,UPDATE 中租户不可变 → 无额外 scope 字段
|
|
|
//
|
|
|
@@ -134,8 +133,10 @@ public class AdoS0MfgRoutingOpDetailsController : ControllerBase
|
|
|
static string? Norm(string? v) => string.IsNullOrWhiteSpace(v) ? null : v.Trim();
|
|
|
|
|
|
var scopeUnchanged = entity.CompanyRefId == dto.CompanyRefId && entity.FactoryRefId == dto.FactoryRefId;
|
|
|
- var materialPreserved = scopeUnchanged
|
|
|
- && string.Equals(Norm(entity.MaterialCode), Norm(dto.MaterialCode), StringComparison.Ordinal);
|
|
|
+ // 「用户主动换了一个新物料」与「原值原样写回」是两种语义,必须分开判定。
|
|
|
+ var materialChanged =
|
|
|
+ !string.Equals(Norm(entity.MaterialCode), Norm(dto.MaterialCode), StringComparison.Ordinal);
|
|
|
+ var materialPreserved = scopeUnchanged && !materialChanged;
|
|
|
var workCenterPreserved =
|
|
|
string.Equals(Norm(entity.WorkCenterCode), Norm(dto.WorkCenterCode), StringComparison.Ordinal);
|
|
|
|
|
|
@@ -147,40 +148,65 @@ public class AdoS0MfgRoutingOpDetailsController : ControllerBase
|
|
|
entity.Id, entity.MaterialCode, entity.CompanyRefId, entity.FactoryRefId);
|
|
|
}
|
|
|
|
|
|
- // B1 + B2 + 历史兼容降级(D-03 严格条件)
|
|
|
- var matResult = materialPreserved
|
|
|
- ? MaterialScopeCheck.Match
|
|
|
- : await _refChecker.MaterialExistsInScopeAsync(tenantId, dto.MaterialCode, dto.CompanyRefId, dto.FactoryRefId);
|
|
|
- if (matResult == MaterialScopeCheck.NotFound)
|
|
|
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
|
|
|
- $"物料编码 '{dto.MaterialCode}' 不存在于物料主数据");
|
|
|
-
|
|
|
- if (matResult == MaterialScopeCheck.ScopeMiss)
|
|
|
+ if (materialChanged)
|
|
|
+ {
|
|
|
+ // 「选择一个新的真实物料」——与 Create 同一契约:**当前租户内物料真实存在即可**。
|
|
|
+ //
|
|
|
+ // 为什么不能继续用 MaterialExistsInScopeAsync:该方法要求 ItemMaster 的
|
|
|
+ // Company/FactoryRefId 与本行组织归属**数值完全相等**,但两者分属没有映射关系的
|
|
|
+ // 两套 namespace(本行 = SysOrg 雪花 id,ItemMaster 历史归属 = legacy 1000/8010),
|
|
|
+ // 跨 namespace 强比较恒不成立。上一批把 Create 改成租户级存在性后,新建行带的是
|
|
|
+ // SysOrg scope,再来换物料必判 ScopeMiss;而 D-03 降级要求「原 scope 为 0/0 且物料未改」,
|
|
|
+ // 对换物料这一动作**结构上永不可达** → 新行换物料 100% 400。
|
|
|
+ //
|
|
|
+ // 校验并未放宽:租户隔离与物料真实性依旧严格(他租户物料查不到 → 400 S01011),
|
|
|
+ // 移除的只是那条无语义基础的跨 namespace 数值比较。
|
|
|
+ // 共享的 AdoS0ReferenceChecker **未改动**,只是本调用点换用同类中已有的租户级方法;
|
|
|
+ // 其它仍依赖严格 scope 的功能不受影响。legacy↔SysOrg 映射治理(I-007)不在本轮范围。
|
|
|
+ if (!await _refChecker.MaterialExistsAsync(tenantId, dto.MaterialCode))
|
|
|
+ return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
|
|
|
+ $"物料编码 '{dto.MaterialCode}' 不存在于物料主数据");
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- // 降级条件(全部满足才允许,对应 D-03 严格条件):
|
|
|
- // 1. 是 Update(已满足)
|
|
|
- // 2. DB 原记录 scope 为 0/null/0
|
|
|
- // 3. 本次未修改 MaterialCode(dto 与 entity 相同)
|
|
|
- // 4. B1 存在性通过(matResult != NotFound,已满足)
|
|
|
- var originScopeEmpty = entity.CompanyRefId == 0 && entity.FactoryRefId == 0;
|
|
|
- var materialUnchanged = string.Equals(entity.MaterialCode, dto.MaterialCode?.Trim(), StringComparison.Ordinal);
|
|
|
+ // 物料值未变(可能只改了组织归属或其它字段):历史兼容语义原样保留,本轮不动。
|
|
|
+ // B1 + B2 + 历史兼容降级(D-03 严格条件)
|
|
|
+ var matResult = materialPreserved
|
|
|
+ ? MaterialScopeCheck.Match
|
|
|
+ : await _refChecker.MaterialExistsInScopeAsync(tenantId, dto.MaterialCode, dto.CompanyRefId, dto.FactoryRefId);
|
|
|
+ if (matResult == MaterialScopeCheck.NotFound)
|
|
|
+ return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.ReferenceNotFound,
|
|
|
+ $"物料编码 '{dto.MaterialCode}' 不存在于物料主数据");
|
|
|
|
|
|
- if (originScopeEmpty && materialUnchanged)
|
|
|
- {
|
|
|
- _logger.LogWarning(
|
|
|
- "[B2 Downgrade] Table={TableName} PrimaryKey={PrimaryKey} ReferenceField={ReferenceField} " +
|
|
|
- "OldValue={OldValue} NewValue={NewValue} CompanyRefId={CompanyRefId} FactoryRefId={FactoryRefId} " +
|
|
|
- "DowngradeReason={DowngradeReason}",
|
|
|
- "RoutingOpDetail", entity.Id, "MaterialCode",
|
|
|
- entity.MaterialCode, dto.MaterialCode,
|
|
|
- dto.CompanyRefId, dto.FactoryRefId,
|
|
|
- "LegacyRecord_OriginScopeZero_MaterialUnchanged");
|
|
|
- // 放行,继续执行写入
|
|
|
- }
|
|
|
- else
|
|
|
+ if (matResult == MaterialScopeCheck.ScopeMiss)
|
|
|
{
|
|
|
- return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.InvalidReferenceScope,
|
|
|
- $"物料编码 '{dto.MaterialCode}' 不属于当前公司/工厂 (CompanyRefId={dto.CompanyRefId}, FactoryRefId={dto.FactoryRefId})");
|
|
|
+ // 降级条件(全部满足才允许,对应 D-03 严格条件):
|
|
|
+ // 1. 是 Update(已满足)
|
|
|
+ // 2. DB 原记录 scope 为 0/null/0
|
|
|
+ // 3. 本次未修改 MaterialCode(dto 与 entity 相同)
|
|
|
+ // 4. B1 存在性通过(matResult != NotFound,已满足)
|
|
|
+ // 注:进入本分支时条件 3 恒成立(外层已按 materialChanged 分流),
|
|
|
+ // 但**刻意保留该判定**,不趁本轮重构历史兼容代码。
|
|
|
+ var originScopeEmpty = entity.CompanyRefId == 0 && entity.FactoryRefId == 0;
|
|
|
+ var materialUnchanged = string.Equals(entity.MaterialCode, dto.MaterialCode?.Trim(), StringComparison.Ordinal);
|
|
|
+
|
|
|
+ if (originScopeEmpty && materialUnchanged)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(
|
|
|
+ "[B2 Downgrade] Table={TableName} PrimaryKey={PrimaryKey} ReferenceField={ReferenceField} " +
|
|
|
+ "OldValue={OldValue} NewValue={NewValue} CompanyRefId={CompanyRefId} FactoryRefId={FactoryRefId} " +
|
|
|
+ "DowngradeReason={DowngradeReason}",
|
|
|
+ "RoutingOpDetail", entity.Id, "MaterialCode",
|
|
|
+ entity.MaterialCode, dto.MaterialCode,
|
|
|
+ dto.CompanyRefId, dto.FactoryRefId,
|
|
|
+ "LegacyRecord_OriginScopeZero_MaterialUnchanged");
|
|
|
+ // 放行,继续执行写入
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return AdoS0ApiErrors.InvalidReference(AdoS0ErrorCodes.InvalidReferenceScope,
|
|
|
+ $"物料编码 '{dto.MaterialCode}' 不属于当前公司/工厂 (CompanyRefId={dto.CompanyRefId}, FactoryRefId={dto.FactoryRefId})");
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|