Files
kunshan-bzfm-mes-backend/DOAN.Service/MES/Material/MmInventoryService.cs
2026-01-09 14:12:01 +08:00

660 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
namespace DOAN.Service.BZFM
{
/// <summary>
/// 库存表Service业务层处理
/// </summary>
[AppService(ServiceType = typeof(IMmInventoryService), ServiceLifetime = LifeTime.Transient)]
public class MmInventoryService : BaseService<MmInventory>, IMmInventoryService
{
/// <summary>
/// 查询库存表列表
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
public PagedInfo<MmInventoryDto> GetList(MmInventoryQueryDto parm)
{
var predicate = QueryExp(parm);
var response = Queryable()
.Where(predicate.ToExpression())
.ToPage<MmInventory, MmInventoryDto>(parm);
return response;
}
/// <summary>
/// 获取详情
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
public MmInventory GetInfo(int Id)
{
var response = Queryable().Where(x => x.Id == Id).First();
return response;
}
/// <summary>
/// 添加库存表
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public MmInventory AddMmInventory(MmInventory model)
{
return Insertable(model).ExecuteReturnEntity();
}
/// <summary>
/// 修改库存表
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public int UpdateMmInventory(MmInventory model)
{
return Update(model, true);
}
/// <summary>
/// 查询导出表达式
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
private static Expressionable<MmInventory> QueryExp(MmInventoryQueryDto parm)
{
var predicate = Expressionable
.Create<MmInventory>()
.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<MmMaterialOption> GetMaterialOption()
{
try
{
return Context
.Queryable<MmMaterial>()
.Where(it => it.Status == "启用")
.Select(it => new MmMaterialOption
{
MaterialCode = it.MaterialCode,
MaterialName = it.MaterialName,
CategoryCode = it.CategoryCode,
Specification = it.Specification,
SupplierCode = it.SupplierCode,
SupplierName = it.SupplierName,
Type = it.Type,
})
.ToList();
}
catch (Exception)
{
throw;
}
}
public List<MmLocationOption> GetLocationOption()
{
try
{
return Context
.Queryable<MmLocation>()
.Where(it => it.Status == "启用")
.Select(it => new MmLocationOption
{
WarehouseCode = it.WarehouseCode,
WarehouseName = it.WarehouseName,
LocationCode = it.LocationCode,
LocationName = it.LocationName,
LocationType = it.LocationType,
})
.ToList();
}
catch (Exception)
{
throw;
}
}
public List<MmTransactionOption> GetTransactionOption()
{
try
{
return Context
.Queryable<MmTransactionType>()
.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<MmMaterial>()
.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<MmLocation>()
.Where(it => it.WarehouseCode == parm.WarehouseCode)
.Where(it => it.LocationCode == parm.LocationCode)
.First();
if (mmLocation == null)
return "仓库编码或库位编码不存在!";
// 启用事务
Context.Ado.BeginTran();
// 获取现有库存(同物料、批次、库位、供应商)
var mmInventory = Context
.Queryable<MmInventory>()
.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
{
Context
.Updateable(mmInventory)
.SetColumns(it => it.CurrentQty == it.CurrentQty + delta)
.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,
Quantity = parm.Quantity,
Unit = parm.Unit,
ProductionDate = parm.ProductionDate,
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<MmMaterial>()
.Where(it => it.MaterialCode == parm.MaterialCode)
.First();
if (mmMaterial == null)
return "物料不存在!";
var mmLocation = Context
.Queryable<MmLocation>()
.Where(it => it.WarehouseCode == parm.WarehouseCode)
.Where(it => it.LocationCode == parm.LocationCode)
.First();
if (mmLocation == null)
return "仓库编码或库位编码不存在!";
Context.Ado.BeginTran();
var mmInventory = Context
.Queryable<MmInventory>()
.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)
{
var newInventory = new MmInventory()
{
MaterialCode = mmMaterial.MaterialCode,
MaterialName = mmMaterial.MaterialName,
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 "库存不足,无法出库";
}
Context
.Updateable(mmInventory)
.SetColumns(it => it.CurrentQty == it.CurrentQty - delta)
.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,
Quantity = parm.Quantity,
Unit = parm.Unit,
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;
}
}
/// <summary>
/// 根据单据类型与数量计算有符号变动量(蓝单为正,红单为负)
/// </summary>
/// <param name="receiptType">1 表示蓝单(正),其它为红单(负)</param>
/// <param name="quantity">1 表示蓝单(正),其它为红单(负)</param>
private static decimal GetSignedQuantity(int receiptType, decimal quantity)
{
return receiptType == 1 ? Math.Abs(quantity) : -Math.Abs(quantity);
}
/// <summary>
/// 生成单据编号,格式:{prefix}{yyyyMMdd}-{nnn}
/// 例如RK20251225-001
/// </summary>
private string GenerateReceiptNo(string prefix)
{
var datePart = DateTime.Now.ToString("yyyyMMdd");
var baseNo = prefix + datePart + "-";
// 尝试从入库/出库表中获取当天最大的编号后缀
try
{
if (prefix == "RK")
{
var last = Context
.Queryable<MmRecordInbound>()
.Where(it => it.InboundNo.StartsWith(prefix + datePart))
.OrderBy(it => it.InboundNo + " desc")
.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<MmRecordOutbound>()
.Where(it => it.OutboundNo.StartsWith(prefix + datePart))
.OrderBy(it => it.OutboundNo + " desc")
.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";
}
}
/// <summary>
/// 导入数据
/// </summary>
/// <param name="formFile"></param>
/// <returns></returns>
public ImportResultDto ImportInventory(IFormFile formFile, string username)
{
string message = "导入成功";
List<MmInventoryExcelDto> 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;
}
/// <summary>
/// 导出物料表列表
/// </summary>
/// <returns></returns>
public PagedInfo<MmInventoryExcelDto> 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);
}
}
}