using DOAN.Model; using DOAN.Model.BZFM; using DOAN.Model.BZFM.Dto; using DOAN.Model.Mobile.Dto; using DOAN.Repository; using DOAN.Service.BZFM.IBZFMService; using Infrastructure.Attribute; using Infrastructure.Converter; using Infrastructure.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; namespace DOAN.Service.BZFM { /// /// 库存表Service业务层处理 /// [AppService(ServiceType = typeof(IMmInventoryService), ServiceLifetime = LifeTime.Transient)] public class MmInventoryService : BaseService, IMmInventoryService { /// /// 查询库存表列表 /// /// /// public PagedInfo GetList(MmInventoryQueryDto parm) { var predicate = QueryExp(parm); var response = Queryable() .Where(predicate.ToExpression()) .ToPage(parm); return response; } /// /// 获取详情 /// /// /// public MmInventory GetInfo(int Id) { var response = Queryable().Where(x => x.Id == Id).First(); return response; } /// /// 添加库存表 /// /// /// public MmInventory AddMmInventory(MmInventory model) { return Insertable(model).ExecuteReturnEntity(); } /// /// 修改库存表 /// /// /// public int UpdateMmInventory(MmInventory model) { return Update(model, true); } /// /// 查询导出表达式 /// /// /// private static Expressionable QueryExp(MmInventoryQueryDto parm) { var predicate = Expressionable .Create() .AndIF( !string.IsNullOrEmpty(parm.MaterialCode), m => m.MaterialCode.Contains(parm.MaterialCode) ) .AndIF( !string.IsNullOrEmpty(parm.WarehouseName), m => m.WarehouseName.Contains(parm.WarehouseName) ) .AndIF( !string.IsNullOrEmpty(parm.WarehouseCode), m => m.WarehouseCode.Contains(parm.WarehouseCode) ) .AndIF( !string.IsNullOrEmpty(parm.LocationCode), m => m.LocationCode.Contains(parm.LocationCode) ) .AndIF( !string.IsNullOrEmpty(parm.SupplierCode), m => m.SupplierCode.Contains(parm.SupplierCode) ) .AndIF( !string.IsNullOrEmpty(parm.SupplierName), m => m.SupplierName.Contains(parm.SupplierName) ) .AndIF(!string.IsNullOrEmpty(parm.BatchNo), m => m.BatchNo.Contains(parm.BatchNo)); return predicate; } public List GetMaterialOption() { try { return Context .Queryable() .Where(it => it.Status == "启用") .Select(it => new MmMaterialOption { Id = it.Id, MaterialCode = it.MaterialCode, MaterialName = it.MaterialName, CategoryCode = it.CategoryCode, Specification = it.Specification, SupplierCode = it.SupplierCode, SupplierName = it.SupplierName, Type = it.Type, }) .OrderBy(it => it.Type) .ToList(); } catch (Exception) { throw; } } public List GetLocationOption() { try { return Context .Queryable() .Where(it => it.Status == "启用") .Select(it => new MmLocationOption { Id = it.Id, WarehouseCode = it.WarehouseCode, WarehouseName = it.WarehouseName, LocationCode = it.LocationCode, LocationName = it.LocationName, LocationType = it.LocationType, }) .ToList(); } catch (Exception) { throw; } } public List GetTransactionOption() { try { return Context .Queryable() .Where(it => it.Status == "启用") .Select(it => new MmTransactionOption { Label = it.TypeName, Value = it.TypeCode, }) .ToList(); } catch (Exception) { throw; } } // 入库单 public string CreateInboundReceipt(InboundReceiptDto parm) { try { DateTime nowDate = DateTime.Now; // 计算有符号变动量(蓝单为正,红单为负) decimal delta = GetSignedQuantity(parm.ReceiptType, parm.Quantity); // 校验物料和库位 var mmMaterial = Context .Queryable() .Where(it => it.MaterialCode == parm.MaterialCode) .WhereIF( !string.IsNullOrEmpty(parm.SupplierCode), it => it.SupplierCode == parm.SupplierCode ) .First(); if (mmMaterial == null) return "物料不存在!"; var mmLocation = Context .Queryable() .Where(it => it.WarehouseCode == parm.WarehouseCode) .Where(it => it.LocationCode == parm.LocationCode) .First(); if (mmLocation == null) return "仓库编码或库位编码不存在!"; // 启用事务 Context.Ado.BeginTran(); // 获取现有库存(同物料、批次、库位、供应商) var mmInventory = Context .Queryable() .Where(it => it.MaterialCode == parm.MaterialCode) .Where(it => it.BatchNo == parm.BatchNo) .Where(it => it.WarehouseCode == parm.WarehouseCode) .Where(it => it.LocationCode == parm.LocationCode) .Where(it => it.SupplierCode == parm.SupplierCode) .First(); // 若不存在则新增;存在则更新 if (mmInventory == null) { var newInventory = new MmInventory() { MaterialCode = mmMaterial.MaterialCode, MaterialName = mmMaterial.MaterialName, SupplierCode = mmMaterial.SupplierCode, SupplierName = mmMaterial.SupplierName, LocationCode = mmLocation.LocationCode, LocationName = mmLocation.LocationName, WarehouseCode = mmLocation.WarehouseCode, WarehouseName = mmLocation.WarehouseName, BatchNo = parm.BatchNo, CurrentQty = delta, Unit = parm.Unit, ExpiryDate = parm.ExpiryDate, LastUpdatedTime = null, ProductionDate = parm.ProductionDate, CreatedTime = nowDate, }; Context.Insertable(newInventory).ExecuteCommand(); } else { mmInventory.CurrentQty += delta; Context .Updateable(mmInventory) .UpdateColumns(it => it.CurrentQty) .ExecuteCommand(); } // 插入入库记录,入库单号使用自动增长策略(同日期按序号) var inboundNo = GenerateReceiptNo("RK"); MmRecordInbound newRecord = new() { InboundNo = inboundNo, BatchNo = parm.BatchNo, Operator = parm.Operator, StoveCode = parm.StoveCode, MaterialCode = mmMaterial.MaterialCode, MaterialName = mmMaterial.MaterialName, SupplierCode = mmMaterial.SupplierCode, SupplierName = mmMaterial.SupplierName, LocationCode = mmLocation.LocationCode, LocationName = mmLocation.LocationName, WarehouseCode = mmLocation.WarehouseCode, WarehouseName = mmLocation.WarehouseName, //TODO 待调整(可能涉及记录汇总) Quantity = delta, Unit = parm.Unit, ProductionDate = parm.ProductionDate, Workorder = parm.Workorder, ExpiryDate = parm.ExpiryDate, CreatedTime = nowDate, TransactionType = parm.TransactionType, Remarks = parm.Remarks, }; Context.Insertable(newRecord).ExecuteCommand(); Context.Ado.CommitTran(); return "ok"; } catch (Exception ex) { // 回滚操作 Context.Ado.RollbackTran(); return ex.Message; } } public string CreateOutboundReceipt(OutboundReceiptDto parm) { try { DateTime nowDate = DateTime.Now; // 计算有符号变动量(蓝单为正,红单为负) decimal delta = GetSignedQuantity(parm.ReceiptType, parm.Quantity); var mmMaterial = Context .Queryable() .Where(it => it.MaterialCode == parm.MaterialCode) .First(); if (mmMaterial == null) return "物料不存在!"; var mmLocation = Context .Queryable() .Where(it => it.WarehouseCode == parm.WarehouseCode) .Where(it => it.LocationCode == parm.LocationCode) .First(); if (mmLocation == null) return "仓库编码或库位编码不存在!"; Context.Ado.BeginTran(); var mmInventory = Context .Queryable() .Where(it => it.MaterialCode == parm.MaterialCode) .Where(it => it.BatchNo == parm.BatchNo) .Where(it => it.WarehouseCode == parm.WarehouseCode) .Where(it => it.LocationCode == parm.LocationCode) .First(); if (mmInventory == null) { if (parm.ReceiptType == 1) { //库存为0或者不存在,不允许出库 Context.Ado.RollbackTran(); return "库存不存在,禁止出库!"; } var newInventory = new MmInventory() { MaterialCode = mmMaterial.MaterialCode, MaterialName = mmMaterial.MaterialName, SupplierCode = mmMaterial.SupplierCode, SupplierName = mmMaterial.SupplierName, LocationCode = mmLocation.LocationCode, LocationName = mmLocation.LocationName, WarehouseCode = mmLocation.WarehouseCode, WarehouseName = mmLocation.WarehouseName, BatchNo = parm.BatchNo, CurrentQty = -delta, Unit = parm.Unit, LastUpdatedTime = null, CreatedTime = nowDate, }; Context.Insertable(newInventory).ExecuteCommand(); } else { if (mmInventory.CurrentQty - delta < 0) { Context.Ado.RollbackTran(); return "库存不足,无法出库!"; } mmInventory.CurrentQty -= delta; Context .Updateable(mmInventory) .UpdateColumns(it => it.CurrentQty) .ExecuteCommand(); } var outboundNo = GenerateReceiptNo("CK"); MmRecordOutbound newRecord = new() { OutboundNo = outboundNo, BatchNo = parm.BatchNo, Operator = parm.Operator, MaterialCode = mmMaterial.MaterialCode, MaterialName = mmMaterial.MaterialName, LocationCode = mmLocation.LocationCode, LocationName = mmLocation.LocationName, WarehouseCode = mmLocation.WarehouseCode, WarehouseName = mmLocation.WarehouseName, //TODO 待调整(可能涉及记录汇总) Quantity = -delta, Unit = parm.Unit, CreatedTime = nowDate, TransactionType = parm.TransactionType, Workorder = parm.Workorder, OrderNo = parm.OrderNo, Remarks = parm.Remarks, }; Context.Insertable(newRecord).ExecuteCommand(); Context.Ado.CommitTran(); return "ok"; } catch (Exception ex) { // 回滚操作 Context.Ado.RollbackTran(); return ex.Message; } } /// /// 根据单据类型与数量计算有符号变动量(蓝单为正,红单为负) /// /// 1 表示蓝单(正),其它为红单(负) /// 1 表示蓝单(正),其它为红单(负) private static decimal GetSignedQuantity(int receiptType, decimal quantity) { return receiptType == 1 ? Math.Abs(quantity) : -Math.Abs(quantity); } /// /// 生成单据编号,格式:{prefix}{yyyyMMdd}-{nnn} /// 例如:RK20251225-001 /// private string GenerateReceiptNo(string prefix) { var datePart = DateTime.Now.ToString("yyyyMMdd"); var baseNo = prefix + datePart + "-"; // 尝试从入库/出库表中获取当天最大的编号后缀 try { if (prefix == "RK") { var last = Context .Queryable() .Where(it => it.InboundNo.StartsWith(prefix + datePart)) .OrderByDescending(it => it.InboundNo) .Select(it => it.InboundNo) .First(); if (string.IsNullOrEmpty(last)) { return baseNo + "001"; } var suf = last.Substring((prefix + datePart).Length).TrimStart('-', '_'); if (int.TryParse(suf, out var n)) { return baseNo + (n + 1).ToString("D3"); } return baseNo + "001"; } else { var last = Context .Queryable() .Where(it => it.OutboundNo.StartsWith(prefix + datePart)) .OrderByDescending(it => it.OutboundNo) .Select(it => it.OutboundNo) .First(); if (string.IsNullOrEmpty(last)) { return baseNo + "001"; } var suf = last.Substring((prefix + datePart).Length).TrimStart('-', '_'); if (int.TryParse(suf, out var n)) { return baseNo + (n + 1).ToString("D3"); } return baseNo + "001"; } } catch { return baseNo + "001"; } } /// /// 导入数据 /// /// /// public ImportResultDto ImportInventory(IFormFile formFile, string username) { string message = "导入成功"; List inventoryList = new(); using (var stream = formFile.OpenReadStream()) { try { IWorkbook workbook = new XSSFWorkbook(stream); ISheet sheet = workbook.GetSheetAt(0); #region 读取excel // 遍历每一行 for (int row = 1; row <= sheet.LastRowNum; row++) { IRow currentRow = sheet.GetRow(row); if (currentRow != null) // 确保行不为空 { MmInventoryExcelDto inventory = new MmInventoryExcelDto(); //00 ID NPOI.SS.UserModel.ICell currentCell_00 = currentRow.GetCell(0); inventory.Id = (int)currentCell_00?.NumericCellValue; //02物料编码 NPOI.SS.UserModel.ICell currentCell_01 = currentRow.GetCell(1); inventory.MaterialCode = currentCell_01?.ToString(); if ( currentCell_01 == null || string.IsNullOrEmpty(inventory.MaterialCode) ) { message = $"物料编码不可为空,第{row + 1}行"; break; } //03批次号 NPOI.SS.UserModel.ICell currentCell_02 = currentRow.GetCell(2); inventory.BatchNo = currentCell_02?.ToString() ?? string.Empty; //04当前库存量 NPOI.SS.UserModel.ICell currentCell_03 = currentRow.GetCell(3); string currentQtyStr = currentCell_03?.ToString(); if (currentCell_03 == null || string.IsNullOrWhiteSpace(currentQtyStr)) { message = $"当前库存量不可为空,第{row + 1}行"; break; } // 尝试转换为decimal if (!decimal.TryParse(currentQtyStr, out decimal currentQty)) { message = $"当前库存量格式错误(必须是数字),第{row + 1}行"; break; } // 验证数值范围(可根据业务需求调整) //if (currentQty < 0) //{ // message = $"当前库存量不能为负数,第{row + 1}行"; // break; //} inventory.CurrentQty = currentQty; //05 仓库编码 NPOI.SS.UserModel.ICell currentCell_04 = currentRow.GetCell(4); inventory.WarehouseCode = currentCell_04?.ToString(); if ( currentCell_04 == null || string.IsNullOrEmpty(inventory.WarehouseCode) ) { message = $"仓库编码不可为空,第{row + 1}行"; break; } //06 仓库名称 NPOI.SS.UserModel.ICell currentCell_05 = currentRow.GetCell(5); inventory.WarehouseName = currentCell_05?.ToString(); if ( currentCell_05 == null || string.IsNullOrEmpty(inventory.WarehouseName) ) { message = $"仓库名称不可为空,第{row + 1}行"; break; } //07 库位编码 NPOI.SS.UserModel.ICell currentCell_06 = currentRow.GetCell(6); inventory.LocationCode = currentCell_06?.ToString(); if ( currentCell_06 == null || string.IsNullOrEmpty(inventory.LocationCode) ) { message = $"仓库编码不可为空,第{row + 1}行"; break; } //08 库位名称 NPOI.SS.UserModel.ICell currentCell_07 = currentRow.GetCell(7); inventory.LocationName = currentCell_07?.ToString(); if ( currentCell_07 == null || string.IsNullOrEmpty(inventory.LocationName) ) { message = $"库位名称不可为空,第{row + 1}行"; break; } //09 创建时间 NPOI.SS.UserModel.ICell currentCell_08 = currentRow.GetCell(8); inventory.CreatedTime = currentCell_08?.DateCellValue ?? DateTime.Now; inventoryList.Add(inventory); } } #endregion } catch (Exception ex) { return null; } } // TODO 3.调用SplitInsert方法实现导入操作,注意主键列的配置(建议优化为,ID相同则修改,不同则新增) var x = Context .Storageable(inventoryList) //.SplitInsert(it => !it.Any()) //.WhereColumns(it => it.Id) //如果不是主键可以这样实现(多字段it=>new{it.x1,it.x2}) .ToStorage(); var result = x.AsInsertable.ExecuteCommand(); var result2 = x.AsUpdateable.IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommand(); //插入可插入部分; var importResult = new ImportResultDto { Message = message, Inserted = x.InsertList.Count, Updated = x.UpdateList.Count, ErrorCount = x.ErrorList.Count, IgnoredCount = x.IgnoreList.Count, Deleted = x.DeleteList.Count, Total = x.TotalList.Count, }; //输出统计 Console.WriteLine(importResult); // 4.收集错误与忽略信息,返回导入结果ImportResultDto 提示,需要修改IServer相关返回格式 foreach (var item in x.ErrorList) { importResult.Errors.Add( new ImportErrorDto { MaterialCode = item.Item.MaterialCode, Message = item.StorageMessage, } ); } foreach (var item in x.IgnoreList) { importResult.Ignored.Add( new ImportErrorDto { MaterialCode = item.Item.MaterialCode, Message = item.StorageMessage, } ); } return importResult; } /// /// 导出物料表列表 /// /// public PagedInfo SelectInventoryList( MmInventoryQueryDto inventory, PagerInfo pager ) { // Use the same predicate builder as GetList to support consistent filtering var predicate = QueryExp(inventory); var query = Queryable() .Where(predicate.ToExpression()) .Select(it => new MmInventoryExcelDto { Id = it.Id, MaterialCode = it.MaterialCode, BatchNo = it.BatchNo, CurrentQty = it.CurrentQty, WarehouseCode = it.WarehouseCode, WarehouseName = it.WarehouseName, LocationCode = it.LocationCode, LocationName = it.LocationName, CreatedTime = it.CreatedTime, }); return query.ToPage(pager); } /// /// 查询出/入记录列表 /// /// /// public PagedInfo GetInOrOutRecord(MmInventoryRecordQueryDto parm) { PagedInfo result = new PagedInfo(); // 处理日期 if (parm.StartTime != null && parm.StartTime.Value > DateTime.MinValue) { parm.StartTime = DOANConvertDate.ConvertLocalDate(parm.StartTime.Value); } if (parm.EndTime != null && parm.EndTime.Value > DateTime.MinValue) { parm.EndTime = DOANConvertDate.ConvertLocalDate(parm.EndTime.Value); } // 查询入库记录 if (parm.SearchType == 1) { result = Context .Queryable() .WhereIF( parm.StartTime != null && parm.StartTime.Value > DateTime.MinValue, it => it.CreatedTime >= parm.StartTime ) .WhereIF( parm.EndTime != null && parm.EndTime.Value > DateTime.MinValue, it => it.CreatedTime <= parm.EndTime ) .WhereIF( !string.IsNullOrEmpty(parm.TransactionType), it => it.TransactionType == parm.TransactionType ) .Where(it => it.MaterialCode == parm.MaterialCode) .Where(it => it.SupplierCode == parm.SupplierCode) .Where(it => it.LocationCode == parm.LocationCode) .Where(it => it.BatchNo == parm.BatchNo) .Select(it => new MmInventoryRecordDto() { Id = it.Id, InboundNo = it.InboundNo, MaterialCode = it.MaterialCode, MaterialName = it.MaterialName, SupplierCode = it.SupplierCode, SupplierName = it.SupplierName, LocationCode = it.LocationCode, LocationName = it.LocationName, WarehouseCode = it.WarehouseCode, WarehouseName = it.WarehouseName, BatchNo = it.BatchNo, Quantity = it.Quantity, TransactionType = it.TransactionType, Operator = it.Operator, CreatedTime = it.CreatedTime, Workorder = it.Workorder, StoveCode = it.StoveCode, Remarks = it.Remarks }) .OrderByDescending(it => it.CreatedTime) .ToPage(parm); } // 查询出库记录 else if (parm.SearchType == 2) { result = Context .Queryable() .WhereIF( parm.StartTime != null && parm.StartTime.Value > DateTime.MinValue, it => it.CreatedTime >= parm.StartTime ) .WhereIF( parm.EndTime != null && parm.EndTime.Value > DateTime.MinValue, it => it.CreatedTime <= parm.EndTime ) .WhereIF( !string.IsNullOrEmpty(parm.TransactionType), it => it.TransactionType == parm.TransactionType ) .Where(it => it.MaterialCode == parm.MaterialCode) //.Where(it => it.SupplierCode == parm.SupplierCode) .Where(it => it.LocationCode == parm.LocationCode) .Where(it => it.BatchNo == parm.BatchNo) .Select(it => new MmInventoryRecordDto() { Id = it.Id, OutboundNo = it.OutboundNo, MaterialCode = it.MaterialCode, MaterialName = it.MaterialName, LocationCode = it.LocationCode, LocationName = it.LocationName, WarehouseCode = it.WarehouseCode, WarehouseName = it.WarehouseName, BatchNo = it.BatchNo, Quantity = it.Quantity, TransactionType = it.TransactionType, Operator = it.Operator, CreatedTime = it.CreatedTime, Workorder = it.Workorder, OrderNo = it.OrderNo, }) .OrderByDescending(it => it.CreatedTime) .ToPage(parm); } return result; } /// /// 撤销单据,传入单据类型(1-入库单,2-出库单)和ID /// /// /// 返回ok即为成功其他都是不成功 /// public string RevokeReceipt(MmInventoryRevokeDto parm) { try { int _type = parm.Type; int _id = parm.Id; if (_type < -1 && _id < -1) { return $"传入参数有误,请检查:type-{_type},id-{_id}"; } // type == 1 入库单 if (_type == 1) { MmRecordInbound recordInbound = Context .Queryable() .Where(it => it.Id == _id) .First(); if (recordInbound == null) { return $"无此入库记录,请检查:type-{_type},id-{_id}"; } if (recordInbound.Remarks == "已撤销") { return $"此记录已撤销过,不可重复撤销"; } //做出库红单 InboundReceiptDto revokeRecepitDto = new() { ReceiptType = 2, MaterialCode = recordInbound.MaterialCode, BatchNo = recordInbound.BatchNo, LocationCode = recordInbound.LocationCode, WarehouseCode = recordInbound.WarehouseCode, SupplierCode = recordInbound.SupplierCode, StoveCode = recordInbound.StoveCode, Workorder = recordInbound.Workorder, Operator = recordInbound.Operator, Quantity = recordInbound.Quantity, TransactionType = "入库红单", Remarks = $"撤销操作,入库单号:{recordInbound.InboundNo}", }; string result = CreateInboundReceipt(revokeRecepitDto); if (result == "ok") { recordInbound.Remarks = "已撤销"; Context.Updateable(recordInbound).ExecuteCommand(); return result; } else { return result; } } else { MmRecordOutbound recordOutbound = Context .Queryable() .Where(it => it.Id == _id) .First(); if (recordOutbound == null) { return $"无此出库记录,请检查:type-{_type},id-{_id}"; } if (recordOutbound.Remarks == "已撤销") { return $"此记录已撤销过,不可重复撤销"; } //做出库红单 OutboundReceiptDto revokeRecepitDto = new() { ReceiptType = 2, MaterialCode = recordOutbound.MaterialCode, BatchNo = recordOutbound.BatchNo, LocationCode = recordOutbound.LocationCode, WarehouseCode = recordOutbound.WarehouseCode, OrderNo = recordOutbound.OrderNo, Workorder = recordOutbound.Workorder, Operator = recordOutbound.Operator, Quantity = recordOutbound.Quantity, TransactionType = "出库红单", Remarks = $"撤销操作,出库单号:{recordOutbound.OutboundNo}", }; string result = CreateOutboundReceipt(revokeRecepitDto); if (result == "ok") { recordOutbound.Remarks = "已撤销"; Context.Updateable(recordOutbound).ExecuteCommand(); return result; } else { return result; } } } catch (Exception e) { return e.Message; } } } }