namespace Admin.NET.Plugin.AiDOP.Supply;
///
/// 采购申请转 DO/PO 服务。P4 初版仅处理本次生成的要货令 PR,不扫描历史 PR。
///
public class PurchaseOrderTransferService : ITransient
{
private readonly ISqlSugarClient _db;
private readonly NumberRuleService _numberRuleService;
private readonly UserManager _userManager;
public PurchaseOrderTransferService(ISqlSugarClient db, NumberRuleService numberRuleService, UserManager userManager)
{
_db = db;
_numberRuleService = numberRuleService;
_userManager = userManager;
}
public async Task TransferGeneratedRequireGoodsAsync(List requests, string account)
{
var candidates = requests.Where(x => x.IsRequireGoods == 1).ToList();
if (candidates.Count == 0) return new PurchaseOrderTransferResult();
var createdOrders = new List();
var transferredPrIds = new List();
var groups = candidates
.GroupBy(x => new
{
x.TenantId,
x.CompanyId,
x.FactoryId,
x.PrPurchaseId,
x.PrPurchaseNumber,
x.PrPurchaseName,
SupplierType = x.SupplierType ?? string.Empty
})
.ToList();
foreach (var tenantGroup in groups.GroupBy(x => x.Key.TenantId.ToString()))
{
var groupList = tenantGroup.ToList();
var purOrdNumbers = await _numberRuleService.NextBatchInCurrentTransactionAsync("DO", tenantGroup.Key, groupList.Count, account);
if (purOrdNumbers.Count < groupList.Count || purOrdNumbers.Any(string.IsNullOrWhiteSpace))
throw Oops.Oh($"当前DO/PO单号生成失败,请检查DO编号规则维护。Domain={tenantGroup.Key}");
for (var i = 0; i < groupList.Count; i++)
{
var group = groupList[i];
var purOrd = purOrdNumbers[i].Trim();
var now = DateTime.Now;
var supplierType = group.Key.SupplierType;
var isOutsource = string.Equals(supplierType, "委外", StringComparison.OrdinalIgnoreCase);
var poType = isOutsource ? "PW" : "po";
var usage = isOutsource ? "委外加工" : supplierType;
var buyer = ResolveBuyer(supplierType);
var rows = group.OrderBy(x => x.PrSarriveDate).ThenBy(x => x.PrBillNo).ToList();
await InsertPurchaseOrderMasterAsync(purOrd, poType, usage, buyer, group.Key.PrPurchaseNumber, now, account, group.Key.TenantId);
var masterId = await _db.Ado.GetIntAsync(
"SELECT IFNULL(MAX(RecID),0) FROM PurOrdMaster WHERE PurOrd=@PurOrd",
new SugarParameter("@PurOrd", purOrd));
if (masterId <= 0) throw Oops.Oh("DO/PO主表生成失败。");
for (var lineIndex = 0; lineIndex < rows.Count; lineIndex++)
{
await InsertPurchaseOrderDetailAsync(purOrd, poType, masterId, lineIndex + 1, rows[lineIndex], account);
rows[lineIndex].State = 4;
rows[lineIndex].UpdateByName = account;
rows[lineIndex].UpdateTime = now;
transferredPrIds.Add(rows[lineIndex].Id);
}
createdOrders.Add(purOrd);
}
}
return new PurchaseOrderTransferResult
{
CreatedOrderCount = createdOrders.Count,
TransferredPrCount = transferredPrIds.Count,
CreatedOrders = createdOrders,
TransferredPrIds = transferredPrIds
};
}
private async Task InsertPurchaseOrderMasterAsync(
string purOrd,
string poType,
string? usage,
string buyer,
string? supplierCode,
DateTime now,
string account,
long tenantId)
{
await _db.Ado.ExecuteCommandAsync(
"""
INSERT INTO PurOrdMaster
(
Confirming, CreditTermsInt, Disc, ExchRate, EstVal, ExchRate1, ExchRate2,
FixedPrice, FixedRate, Frt, PartialOK, AmtPrepaid, PrintPO, PST, Recurr,
`Release`, Revision, Scheduled, ServiceCharge, SpecialCharge, Taxable,
Tax1, Tax2, Tax3, TransportDays, IsActive, IsConfirm, Potype, IsChanged,
TaxIn, Amt, IsPriceChanged,
Buyer, Domain, PurOrd, OrdDate, ReqBy, Status, Supp, CreateUser, CreateTime,
UpdateUser, UpdateTime, `Usage`, FSTID, Typed, tenant_id
)
VALUES
(
0, 0, 0, 1, 0, 1, 1,
0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 1, 1, @PoType, 0,
1, 0, 0,
@Buyer, @Domain, @PurOrd, @Now, 'DO', '', @Supp, @CreateUser, @Now,
@UpdateUser, @Now, @Usage, @FSTID, @Typed, @TenantId
)
""",
new SugarParameter("@PoType", poType),
new SugarParameter("@Buyer", buyer),
new SugarParameter("@Domain", tenantId.ToString()),
new SugarParameter("@PurOrd", purOrd),
new SugarParameter("@Now", now),
new SugarParameter("@Supp", supplierCode),
new SugarParameter("@CreateUser", account),
new SugarParameter("@UpdateUser", account),
new SugarParameter("@Usage", usage),
new SugarParameter("@FSTID", string.Equals(usage, "VMI", StringComparison.OrdinalIgnoreCase) ? "3" : string.Empty),
new SugarParameter("@Typed", poType == "PW" ? "s" : string.Empty),
new SugarParameter("@TenantId", tenantId <= 0 ? null : tenantId));
}
private async Task InsertPurchaseOrderDetailAsync(string purOrd, string poType, int masterId, int line, PurchaseRequestMain pr, string account)
{
var item = (await _db.Ado.SqlQueryAsync(
"""
SELECT
COALESCE(NULLIF(im.ItemNum,''), ic.number) AS ItemNum,
COALESCE(NULLIF(im.Descr,''), ic.name) AS Descr,
COALESCE(NULLIF(im.UM,''), ic.unit) AS UM,
IFNULL(im.Location, '') AS Location,
IFNULL(im.Rev, '') AS Rev,
IFNULL(im.Drawing, '') AS Drawing
FROM ic_item ic
LEFT JOIN ItemMaster im ON ic.number = im.ItemNum
WHERE ic.Id = @IcitemId
LIMIT 1
""",
new SugarParameter("@IcitemId", pr.IcitemId))).FirstOrDefault();
await _db.Ado.ExecuteCommandAsync(
"""
INSERT INTO PurOrdDetail
(
QtyBO, RctCost, CreditTermsInt, UpdateCurrentCost, CumReceived1, CumReceived2,
CumReceived3, CumReceived4, Disc, FixedPrice, InspectReq, SingleLot, SupplyPer,
PurOrd, PST, PackingSlipQty, PayUMConv, PurCost, RctQty, QtyOrded, QtyReceived,
QtyReturned, Active, QtyReleased, RctUMConversion, Scheduled, ScheduledChanged,
SchedMRPReq, SafetyDays, SafetyHours, StdCost, Taxable, TaxIn, MaxTaxableAmt,
TransportHours, UMConversion, VAT, IsActive, IsConfirm, Potype, IsChanged,
TaxRate, IsRounding, ReceiptQty, BarCodeQty, IsClosed, QtyReturnedRefund, CumQtyBO,
Line, ItemNum, Descr, UM, Rev, Drawing, Location, DueDate, NeedDate, LotSerial, PurOrdRecID, Status, Req,
CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
)
VALUES
(
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
@PurOrd, 0, 0, 1, 0, 0, @QtyOrded, 0,
0, 1, 0, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0,
0, 1, 0, 1, 1, @PoType, 0,
0, 0, 0, 0, 0, 0, 0,
@Line, @ItemNum, @Descr, @UM, @Rev, @Drawing, @Location, @DueDate, @NeedDate, '', @PurOrdRecID, 'R', @Req,
@CreateUser, @Now, @UpdateUser, @Now, @TenantId
)
""",
new SugarParameter("@PurOrd", purOrd),
new SugarParameter("@PoType", poType),
new SugarParameter("@QtyOrded", pr.PrAqty ?? pr.PrSqty ?? pr.PrRqty ?? 0),
new SugarParameter("@Line", line),
new SugarParameter("@ItemNum", item?.ItemNum ?? pr.IcitemName ?? string.Empty),
new SugarParameter("@Descr", item?.Descr ?? pr.IcitemName ?? string.Empty),
new SugarParameter("@UM", item?.UM ?? pr.PrUnit ?? string.Empty),
new SugarParameter("@Rev", item?.Rev ?? string.Empty),
new SugarParameter("@Drawing", item?.Drawing ?? string.Empty),
new SugarParameter("@Location", string.IsNullOrWhiteSpace(item?.Location) ? "1001" : item.Location),
new SugarParameter("@DueDate", pr.PrSarriveDate),
new SugarParameter("@NeedDate", pr.PrSarriveDate),
new SugarParameter("@PurOrdRecID", masterId),
new SugarParameter("@Req", pr.PrBillNo),
new SugarParameter("@CreateUser", account),
new SugarParameter("@UpdateUser", account),
new SugarParameter("@Now", DateTime.Now),
new SugarParameter("@TenantId", pr.TenantId <= 0 ? null : pr.TenantId));
}
private static string ResolveBuyer(string? supplierType)
{
return supplierType switch
{
"研发" => "130",
"ECR" => "170",
_ => "110"
};
}
private sealed class ItemLookupRow
{
public string? ItemNum { get; set; }
public string? Descr { get; set; }
public string? UM { get; set; }
public string? Location { get; set; }
public string? Rev { get; set; }
public string? Drawing { get; set; }
}
}
public sealed class PurchaseOrderTransferResult
{
public int CreatedOrderCount { get; set; }
public int TransferredPrCount { get; set; }
public List CreatedOrders { get; set; } = new();
public List TransferredPrIds { get; set; } = new();
}