using Yitter.IdGenerator;
namespace Admin.NET.Plugin.AiDOP.Order;
/// 将资源检查结果写入 b_examine_result / b_bom_child_examine。
public class ResourceCheckResultWriter : ITransient
{
private readonly ISqlSugarClient _db;
private readonly CapacityDateCalculator _capacityCalc;
public ResourceCheckResultWriter(ISqlSugarClient db, CapacityDateCalculator capacityCalc)
{
_db = db;
_capacityCalc = capacityCalc;
}
public async Task WriteAsync(
OrderWorkOrderGenerationService.OrderHeader order,
OrderWorkOrderGenerationService.OrderEntryLine entry,
string workOrd,
long? morderId,
IReadOnlyList lines,
string account,
DateTime now,
long bangId = 0,
string checkScope = "REVIEW")
{
await InvalidatePreviousAsync(entry.Id, entry.TenantId, workOrd, now);
var examineId = YitIdHelper.NextId();
var needQty = entry.Qty ?? 0;
var needTime = entry.SysCapacityDate ?? entry.PlanDate ?? now;
// 对根行(Level=1)执行产能计算,更新 KittingTime
var rootLine = lines.FirstOrDefault(x => x.Level == 1 && x.IsUse == 1 && x.LackQty > 0);
if (rootLine != null)
{
var planDate = entry.PlanDate ?? now;
rootLine.KittingTime = await _capacityCalc.CalculateAsync(
rootLine.ItemNumber, rootLine.LackQty, needTime, planDate, entry.TenantId);
}
var kittingTime = lines
.Where(x => x.IsUse == 1 && x.LackQty > 0)
.Select(x => x.KittingTime)
.DefaultIfEmpty(needTime)
.Max();
var factoryId = entry.FactoryId ?? order.FactoryId ?? entry.TenantId;
var companyId = entry.CompanyId ?? factoryId;
await _db.Ado.ExecuteCommandAsync(
"""
INSERT INTO b_examine_result (
Id, sorderid, bill_no, sentry_id, entry_seq, morder_id, morder_no, bom_number,
need_qty, need_time, earliest_times, latest_times, order_statr_time, kitting_times,
sys_capacity_date, sys_material_date,
bangid, check_scope, net_scope,
create_by_name, create_time, update_by_name, update_time,
tenant_id, factory_id, company_id, IsDeleted
) VALUES (
@Id, @SorderId, @BillNo, @SentryId, @EntrySeq, @MorderId, @MorderNo, @BomNumber,
@NeedQty, @NeedTime, @NeedTime, @NeedTime, @NeedTime, @KittingTime,
@NeedTime, @KittingTime,
@BangId, @CheckScope, @NetScope,
@User, @Now, @User, @Now,
@TenantId, @FactoryId, @CompanyId, 0
)
""",
new SugarParameter("@Id", examineId),
new SugarParameter("@SorderId", order.Id),
new SugarParameter("@BillNo", order.BillNo ?? entry.BillNo ?? string.Empty),
new SugarParameter("@SentryId", entry.Id),
new SugarParameter("@EntrySeq", entry.EntrySeq ?? 0),
new SugarParameter("@MorderId", morderId ?? (object)DBNull.Value),
new SugarParameter("@MorderNo", workOrd),
new SugarParameter("@BomNumber", entry.BomNumber ?? (object)DBNull.Value),
new SugarParameter("@NeedQty", needQty),
new SugarParameter("@NeedTime", needTime),
new SugarParameter("@KittingTime", kittingTime ?? needTime),
new SugarParameter("@BangId", bangId > 0 ? bangId : (object)DBNull.Value),
new SugarParameter("@CheckScope", string.IsNullOrWhiteSpace(checkScope) ? "REVIEW" : checkScope.Trim()),
new SugarParameter("@NetScope", bangId > 0 ? 1 : 0),
new SugarParameter("@User", account),
new SugarParameter("@Now", now),
new SugarParameter("@TenantId", entry.TenantId),
new SugarParameter("@FactoryId", factoryId),
new SugarParameter("@CompanyId", companyId));
var activeLines = lines.Where(x => x.IsUse == 1).ToList();
foreach (var line in lines)
{
var childId = YitIdHelper.NextId();
await _db.Ado.ExecuteCommandAsync(
"""
INSERT INTO b_bom_child_examine (
Id, examine_id, fid, parent_id, bom_child_id, bom_number, item_number, level, sentry_id,
num, type, item_name, model, unit, erp_cls, erp_cls_name, backflush,
qty, sqty, use_qty, self_lack_qty, lack_qty, mo_qty, make_qty, purchase_qty, purchase_occupy_qty,
kitting_time, satisfy_time, isbom, haveicsubs, substitute_code, needCount, needCountNoloss,
wastage, scrap, is_use, Op,
create_by_name, create_time, update_by_name, update_time,
tenant_id, factory_id, company_id, IsDeleted
) VALUES (
@Id, @ExamineId, @Fid, @ParentId, @BomChildId, @BomNumber, @ItemNumber, @Level, @SentryId,
@Num, @Type, @ItemName, @Model, @Unit, @ErpCls, @ErpClsName, @Backflush,
@Qty, @Sqty, @UseQty, @SelfLackQty, @LackQty, @MoQty, @MakeQty, @PurchaseQty, @PurchaseOccupyQty,
@KittingTime, @SatisfyTime, @IsBom, @HaveIcSubs, @SubstituteCode, @NeedCount, @NeedCountNoLoss,
@Wastage, @Scrap, @IsUse, 0,
@User, @Now, @User, @Now,
@TenantId, @FactoryId, @CompanyId, 0
)
""",
new SugarParameter("@Id", childId),
new SugarParameter("@ExamineId", examineId),
new SugarParameter("@Fid", line.Fid),
new SugarParameter("@ParentId", line.ParentFid ?? (object)DBNull.Value),
new SugarParameter("@BomChildId", line.BomChildId ?? (object)DBNull.Value),
new SugarParameter("@BomNumber", line.BomNumber ?? (object)DBNull.Value),
new SugarParameter("@ItemNumber", line.ItemNumber),
new SugarParameter("@Level", line.Level),
new SugarParameter("@SentryId", entry.Id),
new SugarParameter("@Num", line.Num),
new SugarParameter("@Type", line.Type),
new SugarParameter("@ItemName", line.ItemName ?? (object)DBNull.Value),
new SugarParameter("@Model", line.Model ?? (object)DBNull.Value),
new SugarParameter("@Unit", line.Unit ?? (object)DBNull.Value),
new SugarParameter("@ErpCls", line.ErpCls),
new SugarParameter("@ErpClsName", line.ErpClsName ?? (object)DBNull.Value),
new SugarParameter("@Backflush", line.Backflush),
new SugarParameter("@Qty", line.Qty),
new SugarParameter("@Sqty", line.Sqty),
new SugarParameter("@UseQty", line.UseQty),
new SugarParameter("@SelfLackQty", line.SelfLackQty),
new SugarParameter("@LackQty", line.LackQty),
new SugarParameter("@MoQty", line.MoQty),
new SugarParameter("@MakeQty", line.MakeQty),
new SugarParameter("@PurchaseQty", line.PurchaseQty),
new SugarParameter("@PurchaseOccupyQty", line.PurchaseOccupyQty),
new SugarParameter("@KittingTime", line.KittingTime ?? needTime),
new SugarParameter("@SatisfyTime", line.SatisfyTime ?? needTime),
new SugarParameter("@IsBom", line.IsBom),
new SugarParameter("@HaveIcSubs", line.HaveIcSubs),
new SugarParameter("@SubstituteCode", line.SubstituteCode ?? (object)DBNull.Value),
new SugarParameter("@NeedCount", line.NeedCount),
new SugarParameter("@NeedCountNoLoss", line.NeedCountNoLoss),
new SugarParameter("@Wastage", line.Wastage),
new SugarParameter("@Scrap", line.Scrap),
new SugarParameter("@IsUse", line.IsUse),
new SugarParameter("@User", account),
new SugarParameter("@Now", now),
new SugarParameter("@TenantId", entry.TenantId),
new SugarParameter("@FactoryId", factoryId),
new SugarParameter("@CompanyId", companyId));
}
var hasShortage = activeLines.Any(x => x.LackQty > 0);
await UpdateMesMorderMaterialSituationAsync(workOrd, entry.TenantId, hasShortage, account, now);
return new OrderResourceCheckResult
{
ExamineId = examineId,
LineCount = activeLines.Count,
HasShortage = hasShortage,
KittingTime = kittingTime
};
}
///
/// 让同一工单(或同一订单明细行)的历史齐套结果失效。
///
/// 两个要点:
/// 1) 以 morder_no 为主条件。sentry_id 只在 > 0 时追加——工单不挂订单明细行时 entry.Id = 0,
/// 若按 sentry_id = 0 失效会命中所有同类历史结果,把别的工单的结论一起标删。
/// 2) SELECT 与 UPDATE 必须用同一套条件,否则会出现"子表明细已失效、头还是有效"的半失效状态。
///
private async Task InvalidatePreviousAsync(long entryId, long tenantId, string morderNo, DateTime now)
{
var mo = (morderNo ?? "").Trim();
if (mo.Length == 0 && entryId <= 0)
return;
var conds = new List();
var pars = new List { new("@TenantId", tenantId) };
if (mo.Length > 0)
{
conds.Add("morder_no = @MorderNo");
pars.Add(new SugarParameter("@MorderNo", mo));
}
if (entryId > 0)
{
conds.Add("sentry_id = @EntryId");
pars.Add(new SugarParameter("@EntryId", entryId));
}
var scope = $"tenant_id = @TenantId AND IsDeleted = 0 AND ({string.Join(" OR ", conds)})";
var oldIds = await _db.Ado.SqlQueryAsync(
$"SELECT Id FROM b_examine_result WHERE {scope}", pars);
if (oldIds.Count == 0)
return;
foreach (var examineId in oldIds)
{
await _db.Ado.ExecuteCommandAsync(
"""
UPDATE b_bom_child_examine
SET is_use = 0, update_time = @Now
WHERE examine_id = @ExamineId
""",
new SugarParameter("@ExamineId", examineId),
new SugarParameter("@Now", now));
}
var updPars = new List(pars) { new("@Now", now) };
await _db.Ado.ExecuteCommandAsync(
$"UPDATE b_examine_result SET IsDeleted = 1, update_time = @Now WHERE {scope}",
updPars);
}
private async Task UpdateMesMorderMaterialSituationAsync(
string workOrd,
long tenantId,
bool hasShortage,
string account,
DateTime now)
{
var situation = hasShortage ? "缺料" : "齐套";
await _db.Ado.ExecuteCommandAsync(
"""
UPDATE mes_morder
SET MaterialSituation = @Situation,
update_by_name = @User,
update_time = @Now
WHERE morder_no = @WorkOrd AND tenant_id = @TenantId AND IsDeleted = 0
""",
new SugarParameter("@Situation", situation),
new SugarParameter("@User", account),
new SugarParameter("@Now", now),
new SugarParameter("@WorkOrd", workOrd),
new SugarParameter("@TenantId", tenantId));
}
}