| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- using Yitter.IdGenerator;
- namespace Admin.NET.Plugin.AiDOP.Order;
- /// <summary>
- /// 产品设计服务(常规/非标;合同维度跟踪设计负责人、图号、BOM、工艺、图纸计划/实际时间)
- /// 路由前缀:/api/Order/productdesign/...
- /// </summary>
- [ApiDescriptionSettings(Order = 255, Description = "产品设计")]
- [Route("api/Order")]
- [AllowAnonymous]
- [NonUnify]
- public class ProductDesignService : IDynamicApiController, ITransient
- {
- private readonly ISqlSugarClient _db;
- private readonly SqlSugarRepository<ProductDesign> _designRep;
- private readonly SqlSugarRepository<ProductDesignBom> _bomRep;
- private readonly SqlSugarRepository<ProductDesignRouting> _routingRep;
- private readonly UserManager _userManager;
- public ProductDesignService(
- ISqlSugarClient db,
- SqlSugarRepository<ProductDesign> designRep,
- SqlSugarRepository<ProductDesignBom> bomRep,
- SqlSugarRepository<ProductDesignRouting> routingRep,
- UserManager userManager)
- {
- _db = db;
- _designRep = designRep;
- _bomRep = bomRep;
- _routingRep = routingRep;
- _userManager = userManager;
- }
- [DisplayName("获取产品设计列表")]
- [HttpGet("productdesign/list")]
- public async Task<object> GetProductDesignList([FromQuery] ProductDesignListInput input)
- {
- var q = _db.Queryable<ProductDesign>()
- .WhereIF(!string.IsNullOrWhiteSpace(input.BillNo), u => u.BillNo.Contains(input.BillNo!.Trim()))
- .WhereIF(!string.IsNullOrWhiteSpace(input.ContractNo), u => u.ContractNo != null && u.ContractNo.Contains(input.ContractNo!.Trim()))
- .WhereIF(input.ProductKind is 1 or 2, u => u.ProductKind == input.ProductKind!.Value)
- .WhereIF(!string.IsNullOrWhiteSpace(input.DesignLeadName), u => u.DesignLeadName != null && u.DesignLeadName.Contains(input.DesignLeadName!.Trim()));
- var paged = await q.OrderByDescending(u => u.Id).ToPagedListAsync(input.Page, input.PageSize);
- var list = paged.Items.Select(u => new
- {
- id = u.Id,
- billNo = u.BillNo,
- contractNo = u.ContractNo,
- productKind = u.ProductKind,
- designLeadAccount = u.DesignLeadAccount,
- designLeadName = u.DesignLeadName,
- drawingNo = u.DrawingNo,
- drawingPlanStart = u.DrawingPlanStart?.ToString("yyyy-MM-dd HH:mm"),
- drawingPlanEnd = u.DrawingPlanEnd?.ToString("yyyy-MM-dd HH:mm"),
- drawingActualStart = u.DrawingActualStart?.ToString("yyyy-MM-dd HH:mm"),
- drawingActualEnd = u.DrawingActualEnd?.ToString("yyyy-MM-dd HH:mm"),
- applicant = u.Applicant,
- applyDate = u.ApplyDate?.ToString("yyyy-MM-dd"),
- productModel = u.ProductModel,
- itemNum = u.ItemNum,
- createTime = u.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
- updateTime = u.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
- }).ToList();
- return new { total = paged.Total, page = input.Page, pageSize = input.PageSize, list };
- }
- [DisplayName("获取产品设计详情")]
- [HttpGet("productdesign/{id:long}")]
- public async Task<object> GetProductDesignDetail(long id)
- {
- var m = await _designRep.GetFirstAsync(u => u.Id == id)
- ?? throw Oops.Oh("产品设计记录不存在");
- var boms = await _bomRep.GetListAsync(u => u.ProductDesignId == id);
- var routings = await _routingRep.GetListAsync(u => u.ProductDesignId == id);
- return new
- {
- id = m.Id,
- billNo = m.BillNo,
- contractNo = m.ContractNo,
- productKind = m.ProductKind,
- designLeadAccount = m.DesignLeadAccount,
- designLeadName = m.DesignLeadName,
- drawingNo = m.DrawingNo,
- drawingPlanStart = m.DrawingPlanStart?.ToString("yyyy-MM-dd HH:mm:ss"),
- drawingPlanEnd = m.DrawingPlanEnd?.ToString("yyyy-MM-dd HH:mm:ss"),
- drawingActualStart = m.DrawingActualStart?.ToString("yyyy-MM-dd HH:mm:ss"),
- drawingActualEnd = m.DrawingActualEnd?.ToString("yyyy-MM-dd HH:mm:ss"),
- applicant = m.Applicant,
- applyDate = m.ApplyDate?.ToString("yyyy-MM-dd"),
- productModel = m.ProductModel,
- itemNum = m.ItemNum,
- language = m.Language,
- lineRemark = m.LineRemark,
- createUser = m.CreateUser,
- createTime = m.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
- updateUser = m.UpdateUser,
- updateTime = m.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
- boms = boms.OrderBy(x => x.Seq).ThenBy(x => x.Id).Select(x => new
- {
- id = x.Id.ToString(),
- parentBomId = x.ParentBomId.HasValue ? x.ParentBomId.Value.ToString() : null,
- seq = x.Seq,
- itemNum = x.ItemNum,
- itemName = x.ItemName,
- processCode = x.ProcessCode,
- fixedLossQty = x.FixedLossQty,
- batchNo = x.BatchNo,
- }),
- routings = routings.OrderBy(x => x.Seq).ThenBy(x => x.Id).Select(x => new
- {
- id = x.Id,
- seq = x.Seq,
- opName = x.OpName,
- opCode = x.OpCode,
- isKeyProcess = x.IsKeyProcess,
- productionLine = x.ProductionLine,
- routeCode = x.RouteCode,
- }),
- };
- }
- [DisplayName("保存产品设计")]
- [ApiDescriptionSettings(Name = "SaveProductDesign"), HttpPost("productdesign/save")]
- public async Task<object> SaveProductDesign([FromBody] ProductDesignSaveInput input)
- {
- if (input.ProductKind is not (1 or 2))
- throw Oops.Oh("产品类型无效,请选择常规产品或非标产品");
- var now = DateTime.Now;
- var user = _userManager.Account ?? "system";
- await _db.Ado.BeginTranAsync();
- try
- {
- long designId;
- if (input.Id is null or 0)
- {
- var month = now.ToString("yyyyMM");
- var maxSeq = await _db.Ado.GetIntAsync(
- $"SELECT IFNULL(MAX(CAST(SUBSTRING(BillNo, 9) AS UNSIGNED)), 0) FROM ado_product_design WHERE BillNo LIKE 'PD{month}%'");
- var billNo = $"PD{month}{(maxSeq + 1):D4}";
- designId = YitIdHelper.NextId();
- var entity = MapMaster(new ProductDesign { Id = designId, BillNo = billNo }, input, user, now, isNew: true);
- await _designRep.InsertAsync(entity);
- await SaveBomsAsync(designId, input.Boms ?? new List<ProductDesignBomInput>(), isNew: true);
- await SaveRoutingsAsync(designId, input.Routings ?? new List<ProductDesignRoutingInput>(), isNew: true);
- await _db.Ado.CommitTranAsync();
- return new { id = designId, billNo, message = "新增成功" };
- }
- var existing = await _designRep.GetFirstAsync(u => u.Id == input.Id!.Value)
- ?? throw Oops.Oh("产品设计记录不存在");
- designId = existing.Id;
- MapMaster(existing, input, user, now, isNew: false);
- await _designRep.UpdateAsync(existing);
- await SaveBomsAsync(designId, input.Boms ?? new List<ProductDesignBomInput>(), isNew: false);
- await SaveRoutingsAsync(designId, input.Routings ?? new List<ProductDesignRoutingInput>(), isNew: false);
- await _db.Ado.CommitTranAsync();
- return new { id = designId, billNo = existing.BillNo, message = "编辑成功" };
- }
- catch
- {
- await _db.Ado.RollbackTranAsync();
- throw;
- }
- }
- [DisplayName("删除产品设计")]
- [ApiDescriptionSettings(Name = "DeleteProductDesign"), HttpPost("productdesign/delete")]
- public async Task<object> DeleteProductDesign([FromBody] ProductDesignDeleteInput input)
- {
- var entity = await _designRep.GetFirstAsync(u => u.Id == input.Id)
- ?? throw Oops.Oh("产品设计记录不存在");
- await _db.Ado.BeginTranAsync();
- try
- {
- await _bomRep.DeleteAsync(u => u.ProductDesignId == entity.Id);
- await _routingRep.DeleteAsync(u => u.ProductDesignId == entity.Id);
- await _designRep.DeleteAsync(u => u.Id == entity.Id);
- await _db.Ado.CommitTranAsync();
- return new { message = "删除成功" };
- }
- catch
- {
- await _db.Ado.RollbackTranAsync();
- throw;
- }
- }
- private static ProductDesign MapMaster(ProductDesign entity, ProductDesignSaveInput input, string user, DateTime now, bool isNew)
- {
- entity.ContractNo = string.IsNullOrWhiteSpace(input.ContractNo) ? null : input.ContractNo.Trim();
- entity.ProductKind = input.ProductKind;
- entity.DesignLeadAccount = string.IsNullOrWhiteSpace(input.DesignLeadAccount) ? null : input.DesignLeadAccount.Trim();
- entity.DesignLeadName = string.IsNullOrWhiteSpace(input.DesignLeadName) ? null : input.DesignLeadName.Trim();
- entity.DrawingNo = string.IsNullOrWhiteSpace(input.DrawingNo) ? null : input.DrawingNo.Trim();
- entity.DrawingPlanStart = ParseOptionalDate(input.DrawingPlanStart);
- entity.DrawingPlanEnd = ParseOptionalDate(input.DrawingPlanEnd);
- entity.DrawingActualStart = ParseOptionalDate(input.DrawingActualStart);
- entity.DrawingActualEnd = ParseOptionalDate(input.DrawingActualEnd);
- entity.Applicant = string.IsNullOrWhiteSpace(input.Applicant) ? null : input.Applicant.Trim();
- entity.ApplyDate = ParseOptionalDate(input.ApplyDate);
- entity.ProductModel = string.IsNullOrWhiteSpace(input.ProductModel) ? null : input.ProductModel.Trim();
- entity.ItemNum = string.IsNullOrWhiteSpace(input.ItemNum) ? null : input.ItemNum.Trim();
- entity.Language = string.IsNullOrWhiteSpace(input.Language) ? null : input.Language.Trim();
- entity.Qty = null;
- entity.LineRemark = string.IsNullOrWhiteSpace(input.LineRemark) ? null : input.LineRemark.Trim();
- if (isNew)
- {
- entity.CreateUser = user;
- entity.CreateTime = now;
- entity.IsActive = 1;
- }
- else
- {
- entity.UpdateUser = user;
- entity.UpdateTime = now;
- }
- return entity;
- }
- private static DateTime? ParseOptionalDate(string? s)
- {
- if (string.IsNullOrWhiteSpace(s)) return null;
- return DateTime.TryParse(s.Trim(), out var dt) ? dt : null;
- }
- private async Task SaveBomsAsync(long designId, List<ProductDesignBomInput> items, bool isNew)
- {
- var dbRows = isNew ? new List<ProductDesignBom>()
- : await _bomRep.GetListAsync(u => u.ProductDesignId == designId);
- var dbById = dbRows.ToDictionary(u => u.Id);
- if (items.Any(x => !x.Id.HasValue))
- throw Oops.Oh("BOM 每行必须包含 Id(已保存行为正数,新行请使用负数临时 Id)");
- var ordered = OrderBomsForSave(items);
- var tempToReal = new Dictionary<long, long>();
- var keptIds = new HashSet<long>();
- var idx = 0;
- foreach (var d in ordered)
- {
- idx++;
- var seq = d.Seq ?? idx;
- var parentResolved = ResolveBomParentId(d.ParentBomId, tempToReal);
- if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
- {
- if (existing.ProductDesignId != designId) continue;
- existing.ParentBomId = parentResolved;
- existing.Seq = seq;
- existing.ItemNum = string.IsNullOrWhiteSpace(d.ItemNum) ? null : d.ItemNum.Trim();
- existing.ItemName = string.IsNullOrWhiteSpace(d.ItemName) ? null : d.ItemName.Trim();
- existing.ProcessCode = string.IsNullOrWhiteSpace(d.ProcessCode) ? null : d.ProcessCode.Trim();
- existing.Qty = null;
- existing.FixedLossQty = d.FixedLossQty;
- existing.BatchNo = string.IsNullOrWhiteSpace(d.BatchNo) ? null : d.BatchNo.Trim();
- await _bomRep.UpdateAsync(existing);
- keptIds.Add(existing.Id);
- }
- else if (d.Id is < 0)
- {
- var newId = YitIdHelper.NextId();
- tempToReal[d.Id.Value] = newId;
- var row = new ProductDesignBom
- {
- Id = newId,
- ProductDesignId = designId,
- ParentBomId = parentResolved,
- Seq = seq,
- ItemNum = string.IsNullOrWhiteSpace(d.ItemNum) ? null : d.ItemNum.Trim(),
- ItemName = string.IsNullOrWhiteSpace(d.ItemName) ? null : d.ItemName.Trim(),
- ProcessCode = string.IsNullOrWhiteSpace(d.ProcessCode) ? null : d.ProcessCode.Trim(),
- Qty = null,
- FixedLossQty = d.FixedLossQty,
- BatchNo = string.IsNullOrWhiteSpace(d.BatchNo) ? null : d.BatchNo.Trim(),
- };
- await _bomRep.InsertAsync(row);
- keptIds.Add(newId);
- }
- }
- foreach (var row in dbRows.Where(u => !keptIds.Contains(u.Id)))
- await _bomRep.DeleteAsync(u => u.Id == row.Id);
- }
- /// <summary>按父子顺序排列:父(含库中已有 Id)先于子;负数临时 Id 先于引用它的子行。</summary>
- private static List<ProductDesignBomInput> OrderBomsForSave(List<ProductDesignBomInput> items)
- {
- var withId = items.Where(x => x.Id.HasValue).ToList();
- var inBatch = withId.Select(x => x.Id!.Value).ToHashSet();
- var done = new HashSet<long>();
- var result = new List<ProductDesignBomInput>();
- var remaining = withId.ToList();
- bool CanTake(ProductDesignBomInput r)
- {
- var p = r.ParentBomId;
- if (p is null or 0) return true;
- if (p < 0) return done.Contains(p.Value);
- if (inBatch.Contains(p.Value)) return done.Contains(p.Value);
- return true;
- }
- while (remaining.Count > 0)
- {
- var batch = remaining.Where(CanTake).ToList();
- if (batch.Count == 0)
- throw Oops.Oh("BOM 父子关系无效(循环或缺失父节点)");
- foreach (var b in batch)
- {
- result.Add(b);
- remaining.Remove(b);
- if (b.Id.HasValue)
- done.Add(b.Id.Value);
- }
- }
- return result;
- }
- private static long? ResolveBomParentId(long? parentBomId, Dictionary<long, long> tempToReal)
- {
- if (parentBomId is null or 0) return null;
- if (parentBomId.Value < 0)
- {
- if (tempToReal.TryGetValue(parentBomId.Value, out var real))
- return real;
- throw Oops.Oh("BOM 父节点未找到,请检查父子顺序");
- }
- return parentBomId;
- }
- private async Task SaveRoutingsAsync(long designId, List<ProductDesignRoutingInput> items, bool isNew)
- {
- var dbRows = isNew ? new List<ProductDesignRouting>()
- : await _routingRep.GetListAsync(u => u.ProductDesignId == designId);
- var dbById = dbRows.ToDictionary(u => u.Id);
- var inputIds = new HashSet<long>(items.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
- var idx = 0;
- foreach (var d in items)
- {
- idx++;
- var seq = d.Seq ?? idx;
- if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
- {
- if (existing.ProductDesignId != designId) continue;
- existing.Seq = seq;
- existing.OpName = string.IsNullOrWhiteSpace(d.OpName) ? null : d.OpName.Trim();
- existing.OpCode = string.IsNullOrWhiteSpace(d.OpCode) ? null : d.OpCode.Trim();
- existing.ParentOpCode = null;
- existing.IsKeyProcess = d.IsKeyProcess;
- existing.ProductionLine = string.IsNullOrWhiteSpace(d.ProductionLine) ? null : d.ProductionLine.Trim();
- existing.RouteCode = string.IsNullOrWhiteSpace(d.RouteCode) ? null : d.RouteCode.Trim();
- await _routingRep.UpdateAsync(existing);
- }
- else
- {
- var row = new ProductDesignRouting
- {
- Id = YitIdHelper.NextId(),
- ProductDesignId = designId,
- Seq = seq,
- OpName = string.IsNullOrWhiteSpace(d.OpName) ? null : d.OpName.Trim(),
- OpCode = string.IsNullOrWhiteSpace(d.OpCode) ? null : d.OpCode.Trim(),
- ParentOpCode = null,
- IsKeyProcess = d.IsKeyProcess,
- ProductionLine = string.IsNullOrWhiteSpace(d.ProductionLine) ? null : d.ProductionLine.Trim(),
- RouteCode = string.IsNullOrWhiteSpace(d.RouteCode) ? null : d.RouteCode.Trim(),
- };
- await _routingRep.InsertAsync(row);
- }
- }
- foreach (var toDelete in dbRows.Where(u => !inputIds.Contains(u.Id)))
- await _routingRep.DeleteAsync(u => u.Id == toDelete.Id);
- }
- }
|