feat(库存管理): 添加出货操作功能

实现物料出货功能,包括库存校验、事务处理、单据生成和关联订单/工单更新
支持蓝单正向和红单逆向操作,包含完整的参数校验和错误处理
This commit is contained in:
2026-01-28 14:52:32 +08:00
parent 3895195b1b
commit 4d5893d124
4 changed files with 303 additions and 1 deletions

View File

@@ -288,5 +288,32 @@ namespace DOAN.Admin.WebApi.Controllers.BZFM
return SUCCESS(response);
}
/// <summary>
/// 出货操作
/// </summary>
/// <returns></returns>
[HttpPost("Shipment")]
[AllowAnonymous]
[Log(Title = "出货操作", BusinessType = BusinessType.INSERT)]
public IActionResult Shipment([FromBody] ShipmentDto parm)
{
try
{
string response = _MmInventoryService.Shipment(parm);
if (response == "ok")
{
return ToResponse(new ApiResult(200, "ok"));
}
else
{
return ToResponse(new ApiResult(500, response));
}
}
catch (Exception)
{
throw;
}
}
}
}

View File

@@ -0,0 +1,52 @@
using System.ComponentModel.DataAnnotations;
namespace DOAN.Model.BZFM.Dto
{
/// <summary>
/// 出货请求参数
/// </summary>
public class ShipmentDto
{
[Required(ErrorMessage = "物料编码不能为空")]
public string MaterialCode { get; set; }
[Required(ErrorMessage = "出货数量不能为空")]
public decimal Quantity { get; set; }
[Required(ErrorMessage = "仓库编码不能为空")]
public string WarehouseCode { get; set; }
[Required(ErrorMessage = "库位编码不能为空")]
public string LocationCode { get; set; }
public string BatchNo { get; set; }
[Required(ErrorMessage = "操作员不能为空")]
public string Operator { get; set; }
public string Workorder { get; set; }
public string WorkorderRaw { get; set; }
public string OrderNo { get; set; }
[Required(ErrorMessage = "交易类型不能为空")]
public string TransactionType { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remarks { get; set; }
/// <summary>
/// 客户订单号
/// </summary>
[Required(ErrorMessage = "客户订单号不能为空")]
public string CustomerOrder { get; set; }
/// <summary>
/// 单据类型1-蓝单正向2-红单逆向
/// </summary>
public int ReceiptType { get; set; } = 1;
}
}

View File

@@ -51,6 +51,12 @@ namespace DOAN.Service.BZFM.IBZFMService
/// <param name="parm"></param>
/// <returns></returns>
string RevokeReceipt(MmInventoryRevokeDto parm);
/// <summary>
/// 出货操作 成功返回ok
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
string Shipment(ShipmentDto parm);
/// <summary>
/// 导入

View File

@@ -1,7 +1,8 @@
using DOAN.Model;
using DOAN.Model.BZFM;
using DOAN.Model.BZFM.Dto;
using DOAN.Model.Mobile.Dto;
using DOAN.Model.MES.order;
using DOAN.Model.MES.product;
using DOAN.Repository;
using DOAN.Service.BZFM.IBZFMService;
using Infrastructure.Attribute;
@@ -896,5 +897,221 @@ namespace DOAN.Service.BZFM
return e.Message;
}
}
/// <summary>
/// 出货操作 成功返回ok
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
public string Shipment(ShipmentDto 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();
// 订单关联处理
if (!string.IsNullOrEmpty(parm.CustomerOrder))
{
// 判断订单号是否存在
var orderPurchase = Context
.Queryable<OrderPurchase>()
.Where(o => o.OrderNoMes == parm.CustomerOrder)
.First();
if (orderPurchase == null)
{
Context.Ado.RollbackTran();
return "订单号不存在";
}
// 判断工单是否存在
if (!string.IsNullOrEmpty(parm.Workorder))
{
var workorderInfo = Context
.Queryable<ProWorkorder>()
.Where(it => it.Workorder == parm.Workorder)
.First();
if (workorderInfo == null)
{
Context.Ado.RollbackTran();
return "工单不存在";
}
// 判断工单主体型号和订单物料号是否匹配
if (workorderInfo.productionCode != orderPurchase.MaterialCode)
{
Context.Ado.RollbackTran();
return "工单主体型号和订单物料号不匹配";
}
}
}
// 获取现有库存
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)
{
if (parm.ReceiptType == 1)
{
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 = mmMaterial.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 shipmentNo = GenerateReceiptNo("CH");
// 创建出货记录
MmRecordOutbound newRecord = new()
{
OutboundNo = shipmentNo,
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 = -delta,
Unit = mmMaterial.Unit,
CreatedTime = nowDate,
TransactionType = parm.TransactionType,
Workorder = parm.Workorder,
WorkorderRaw = parm.WorkorderRaw,
OrderNo = parm.CustomerOrder,
Remarks = parm.Remarks,
};
Context.Insertable(newRecord).ExecuteCommand();
// 更新工单和订单信息
if (!string.IsNullOrEmpty(parm.CustomerOrder) && !string.IsNullOrEmpty(parm.Workorder))
{
// 获取当前工单信息
var workorderInfo = Context
.Queryable<ProWorkorder>()
.Where(it => it.Workorder == parm.Workorder)
.First();
// 计算累计出货数量使用delta值考虑单据类型的影响
int currentShipmentNum = workorderInfo.ShipmentNum ?? 0;
int newShipmentNum = currentShipmentNum + (int)delta;
// 验证出货数量有效性
if (newShipmentNum < 0)
{
Context.Ado.RollbackTran();
return "累计出货数量不能为负数";
}
// 更新工单信息
Context
.Updateable<ProWorkorder>()
.Where(it => it.Workorder == parm.Workorder)
.SetColumns(it => it.ShipmentNum == newShipmentNum)
.SetColumns(it => it.CustomerOrder == parm.CustomerOrder)
.ExecuteCommand();
// 修改采购订单信息
var orderPurchase = Context
.Queryable<OrderPurchase>()
.Where(o => o.OrderNoMes == parm.CustomerOrder)
.First();
if (orderPurchase != null)
{
int newQuantity = Context
.Queryable<ProWorkorder>()
.Where(it => it.CustomerOrder == parm.CustomerOrder)
.Sum(it => it.ShipmentNum) ?? 0;
orderPurchase.DeliveryQuantity = newQuantity;
if (orderPurchase.DeliveryQuantity > orderPurchase.DemandQuantity)
{
Context.Ado.RollbackTran();
return "交货数量超过订单需求数量";
}
if (orderPurchase.DeliveryQuantity == orderPurchase.DemandQuantity)
{
orderPurchase.Orderindicator = 1;
}
else
{
orderPurchase.Orderindicator = 0;
}
Context.Updateable(orderPurchase).ExecuteCommand();
}
}
// 提交事务
Context.Ado.CommitTran();
return "ok";
}
catch (Exception ex)
{
// 回滚操作
Context.Ado.RollbackTran();
return ex.Message;
}
}
}
}