Implement Inventory Management Service with CRUD operations for warehouses and inventory items, stock operations, and bulk processing capabilities.
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m48s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m48s
This commit is contained in:
@@ -0,0 +1,662 @@
|
||||
using System.Collections.Generic;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using CMSMicroservice.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.Services;
|
||||
|
||||
/// <summary>
|
||||
/// پیادهسازی سرویس مدیریت موجودی
|
||||
/// این سرویس Source of Truth برای موجودی است و مسئول همگامسازی با Product.RemainingCount
|
||||
/// </summary>
|
||||
public class InventoryService : IInventoryService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly ILogger<InventoryService> _logger;
|
||||
private const long DefaultWarehouseId = 1; // انبار پیشفرض
|
||||
|
||||
public InventoryService(ApplicationDbContext context, ILogger<InventoryService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
public async Task<long> InitializeInventoryAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int initialQuantity,
|
||||
long? warehouseId = null,
|
||||
int lowStockThreshold = 10,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var effectiveWarehouseId = warehouseId ?? DefaultWarehouseId;
|
||||
|
||||
// چک کردن اینکه آیا قبلاً InventoryItem برای این محصول وجود دارد
|
||||
var existingItem = productType == ProductType.RegularProduct
|
||||
? await _context.InventoryItems.FirstOrDefaultAsync(
|
||||
x => x.ProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct)
|
||||
: await _context.InventoryItems.FirstOrDefaultAsync(
|
||||
x => x.DiscountProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct);
|
||||
|
||||
if (existingItem != null)
|
||||
{
|
||||
_logger.LogWarning("InventoryItem already exists for {ProductType} with Id {ProductId}",
|
||||
productType, productId);
|
||||
return existingItem.Id;
|
||||
}
|
||||
|
||||
// ایجاد InventoryItem جدید
|
||||
var inventoryItem = new InventoryItem
|
||||
{
|
||||
ProductId = productType == ProductType.RegularProduct ? productId : null,
|
||||
DiscountProductId = productType == ProductType.DiscountProduct ? productId : null,
|
||||
ProductType = productType,
|
||||
Quantity = initialQuantity,
|
||||
ReservedQuantity = 0,
|
||||
LowStockThreshold = lowStockThreshold,
|
||||
WarehouseId = effectiveWarehouseId
|
||||
};
|
||||
|
||||
_context.InventoryItems.Add(inventoryItem);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement اولیه
|
||||
if (initialQuantity > 0)
|
||||
{
|
||||
await LogMovementAsync(
|
||||
inventoryItem.Id,
|
||||
StockMovementType.InitialStock,
|
||||
initialQuantity,
|
||||
0,
|
||||
initialQuantity,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"موجودی اولیه",
|
||||
null,
|
||||
ct);
|
||||
}
|
||||
|
||||
// همگامسازی با Product.RemainingCount
|
||||
await SyncRemainingCountAsync(inventoryItem, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Initialized inventory for {ProductType} Id={ProductId}, Quantity={Quantity}",
|
||||
productType, productId, initialQuantity);
|
||||
|
||||
return inventoryItem.Id;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Query Operations
|
||||
|
||||
public async Task<InventoryItem?> GetInventoryAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
long? warehouseId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var effectiveWarehouseId = warehouseId ?? DefaultWarehouseId;
|
||||
|
||||
return productType == ProductType.RegularProduct
|
||||
? await _context.InventoryItems.FirstOrDefaultAsync(
|
||||
x => x.ProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct)
|
||||
: await _context.InventoryItems.FirstOrDefaultAsync(
|
||||
x => x.DiscountProductId == productId && x.WarehouseId == effectiveWarehouseId && !x.IsDeleted, ct);
|
||||
}
|
||||
|
||||
public async Task<int> GetAvailableQuantityAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
long? warehouseId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, warehouseId, ct);
|
||||
return item?.AvailableQuantity ?? 0;
|
||||
}
|
||||
|
||||
public async Task<bool> CheckAvailabilityAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int requiredQuantity,
|
||||
long? warehouseId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var available = await GetAvailableQuantityAsync(productId, productType, warehouseId, ct);
|
||||
return available >= requiredQuantity;
|
||||
}
|
||||
|
||||
public async Task<List<InventoryItem>> GetLowStockItemsAsync(
|
||||
ProductType? productType = null,
|
||||
long? warehouseId = null,
|
||||
int count = 50,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var query = _context.InventoryItems
|
||||
.Where(x => !x.IsDeleted)
|
||||
.Where(x => x.Quantity <= x.LowStockThreshold);
|
||||
|
||||
if (productType.HasValue)
|
||||
query = query.Where(x => x.ProductType == productType.Value);
|
||||
|
||||
if (warehouseId.HasValue)
|
||||
query = query.Where(x => x.WarehouseId == warehouseId.Value);
|
||||
|
||||
return await query
|
||||
.OrderBy(x => x.Quantity)
|
||||
.Take(count)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<List<StockMovement>> GetStockMovementsAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
DateTime? fromDate = null,
|
||||
DateTime? toDate = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var inventoryItem = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (inventoryItem == null)
|
||||
return new List<StockMovement>();
|
||||
|
||||
var query = _context.StockMovements
|
||||
.Where(x => x.InventoryItemId == inventoryItem.Id && !x.IsDeleted);
|
||||
|
||||
if (fromDate.HasValue)
|
||||
query = query.Where(x => x.Created >= fromDate.Value);
|
||||
|
||||
if (toDate.HasValue)
|
||||
query = query.Where(x => x.Created <= toDate.Value);
|
||||
|
||||
return await query
|
||||
.OrderByDescending(x => x.Created)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Order Flow Operations
|
||||
|
||||
public async Task<bool> ReserveStockAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int quantity,
|
||||
long? orderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot reserve: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.AvailableQuantity < quantity)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Cannot reserve: Insufficient stock. Available={Available}, Requested={Requested}",
|
||||
item.AvailableQuantity, quantity);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.ReservedQuantity;
|
||||
item.ReservedQuantity += quantity;
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
StockMovementType.Reserved,
|
||||
quantity,
|
||||
quantityBefore,
|
||||
item.ReservedQuantity,
|
||||
orderId,
|
||||
productType == ProductType.DiscountProduct ? orderId : null,
|
||||
null,
|
||||
"رزرو برای سفارش",
|
||||
null,
|
||||
ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Reserved {Quantity} units for {ProductType} Id={ProductId}, OrderId={OrderId}",
|
||||
quantity, productType, productId, orderId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ReleaseReservationAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int quantity,
|
||||
long? orderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot release: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.ReservedQuantity;
|
||||
item.ReservedQuantity = Math.Max(0, item.ReservedQuantity - quantity);
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
StockMovementType.Released,
|
||||
quantity,
|
||||
quantityBefore,
|
||||
item.ReservedQuantity,
|
||||
orderId,
|
||||
productType == ProductType.DiscountProduct ? orderId : null,
|
||||
null,
|
||||
"آزادسازی رزرو",
|
||||
null,
|
||||
ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Released {Quantity} reserved units for {ProductType} Id={ProductId}, OrderId={OrderId}",
|
||||
quantity, productType, productId, orderId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ConfirmSaleAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int quantity,
|
||||
long? orderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot confirm sale: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.Quantity;
|
||||
|
||||
// کاهش موجودی واقعی
|
||||
item.Quantity -= quantity;
|
||||
|
||||
// کاهش رزرو (اگر رزرو شده بود)
|
||||
item.ReservedQuantity = Math.Max(0, item.ReservedQuantity - quantity);
|
||||
|
||||
// آپدیت آخرین فروش
|
||||
item.LastSoldAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
StockMovementType.Sale,
|
||||
-quantity, // منفی برای خروج
|
||||
quantityBefore,
|
||||
item.Quantity,
|
||||
orderId,
|
||||
productType == ProductType.DiscountProduct ? orderId : null,
|
||||
null,
|
||||
"فروش",
|
||||
null,
|
||||
ct);
|
||||
|
||||
// همگامسازی با Product.RemainingCount
|
||||
await SyncRemainingCountAsync(item, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Confirmed sale of {Quantity} units for {ProductType} Id={ProductId}, OrderId={OrderId}. New Quantity={NewQuantity}",
|
||||
quantity, productType, productId, orderId, item.Quantity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stock Management Operations
|
||||
|
||||
public async Task<bool> AddStockAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int quantity,
|
||||
string? referenceNumber = null,
|
||||
string? note = null,
|
||||
long? performedByUserId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot add stock: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.Quantity;
|
||||
item.Quantity += quantity;
|
||||
item.LastRestockedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
StockMovementType.Restock,
|
||||
quantity,
|
||||
quantityBefore,
|
||||
item.Quantity,
|
||||
null,
|
||||
null,
|
||||
referenceNumber,
|
||||
note ?? "ورود کالا",
|
||||
performedByUserId,
|
||||
ct);
|
||||
|
||||
// همگامسازی با Product.RemainingCount
|
||||
await SyncRemainingCountAsync(item, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Added {Quantity} units to {ProductType} Id={ProductId}. New Quantity={NewQuantity}",
|
||||
quantity, productType, productId, item.Quantity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> AdjustStockAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int newQuantity,
|
||||
string? note = null,
|
||||
long? performedByUserId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot adjust stock: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.Quantity;
|
||||
var difference = newQuantity - quantityBefore;
|
||||
|
||||
item.Quantity = newQuantity;
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
var movementType = difference >= 0
|
||||
? StockMovementType.AdjustmentPlus
|
||||
: StockMovementType.AdjustmentMinus;
|
||||
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
movementType,
|
||||
difference,
|
||||
quantityBefore,
|
||||
newQuantity,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
note ?? "تعدیل موجودی",
|
||||
performedByUserId,
|
||||
ct);
|
||||
|
||||
// همگامسازی با Product.RemainingCount
|
||||
await SyncRemainingCountAsync(item, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Adjusted stock for {ProductType} Id={ProductId}. Before={Before}, After={After}",
|
||||
productType, productId, quantityBefore, newQuantity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ProcessReturnAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int quantity,
|
||||
long? orderId = null,
|
||||
string? note = null,
|
||||
long? performedByUserId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot process return: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.Quantity;
|
||||
item.Quantity += quantity;
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
StockMovementType.Return,
|
||||
quantity,
|
||||
quantityBefore,
|
||||
item.Quantity,
|
||||
orderId,
|
||||
productType == ProductType.DiscountProduct ? orderId : null,
|
||||
null,
|
||||
note ?? "برگشت از مشتری",
|
||||
performedByUserId,
|
||||
ct);
|
||||
|
||||
// همگامسازی با Product.RemainingCount
|
||||
await SyncRemainingCountAsync(item, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Processed return of {Quantity} units for {ProductType} Id={ProductId}. New Quantity={NewQuantity}",
|
||||
quantity, productType, productId, item.Quantity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> RecordLossAsync(
|
||||
long productId,
|
||||
ProductType productType,
|
||||
int quantity,
|
||||
StockMovementType lossType,
|
||||
string? note = null,
|
||||
long? performedByUserId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (lossType != StockMovementType.Damaged && lossType != StockMovementType.Lost)
|
||||
{
|
||||
_logger.LogWarning("Invalid loss type: {LossType}. Must be Damaged or Lost.", lossType);
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = await GetInventoryAsync(productId, productType, null, ct);
|
||||
if (item == null)
|
||||
{
|
||||
_logger.LogWarning("Cannot record loss: InventoryItem not found for {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var quantityBefore = item.Quantity;
|
||||
item.Quantity = Math.Max(0, item.Quantity - quantity);
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
|
||||
// ثبت StockMovement
|
||||
await LogMovementAsync(
|
||||
item.Id,
|
||||
lossType,
|
||||
-quantity,
|
||||
quantityBefore,
|
||||
item.Quantity,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
note ?? (lossType == StockMovementType.Damaged ? "ضایعات" : "مفقودی"),
|
||||
performedByUserId,
|
||||
ct);
|
||||
|
||||
// همگامسازی با Product.RemainingCount
|
||||
await SyncRemainingCountAsync(item, ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Recorded {LossType} of {Quantity} units for {ProductType} Id={ProductId}. New Quantity={NewQuantity}",
|
||||
lossType, quantity, productType, productId, item.Quantity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Bulk Operations
|
||||
|
||||
public async Task<bool> BulkReserveStockAsync(
|
||||
IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items,
|
||||
long? orderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
foreach (var (productId, productType, quantity) in items)
|
||||
{
|
||||
var result = await ReserveStockAsync(productId, productType, quantity, orderId, ct);
|
||||
if (!result)
|
||||
{
|
||||
// در صورت خطا، رزروهای قبلی را آزاد کنید
|
||||
_logger.LogError(
|
||||
"Bulk reserve failed at {ProductType} Id={ProductId}. Rolling back...",
|
||||
productType, productId);
|
||||
// TODO: Implement rollback logic
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> BulkReleaseReservationAsync(
|
||||
IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items,
|
||||
long? orderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
foreach (var (productId, productType, quantity) in items)
|
||||
{
|
||||
await ReleaseReservationAsync(productId, productType, quantity, orderId, ct);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> BulkConfirmSaleAsync(
|
||||
IEnumerable<(long ProductId, ProductType ProductType, int Quantity)> items,
|
||||
long? orderId = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
foreach (var (productId, productType, quantity) in items)
|
||||
{
|
||||
var result = await ConfirmSaleAsync(productId, productType, quantity, orderId, ct);
|
||||
if (!result)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Bulk confirm sale failed at {ProductType} Id={ProductId}",
|
||||
productType, productId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// همگامسازی موجودی InventoryItem با Product.RemainingCount
|
||||
/// این متد اطمینان میدهد که دادههای قدیمی (RemainingCount) همیشه با سیستم جدید sync است
|
||||
/// </summary>
|
||||
private async Task SyncRemainingCountAsync(InventoryItem item, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (item.ProductType == ProductType.RegularProduct && item.ProductId.HasValue)
|
||||
{
|
||||
var product = await _context.Products.FindAsync(new object[] { item.ProductId.Value }, ct);
|
||||
if (product != null)
|
||||
{
|
||||
product.RemainingCount = item.Quantity;
|
||||
await _context.SaveChangesAsync(ct);
|
||||
_logger.LogDebug("Synced RemainingCount for Product Id={ProductId} to {Quantity}",
|
||||
item.ProductId, item.Quantity);
|
||||
}
|
||||
}
|
||||
else if (item.ProductType == ProductType.DiscountProduct && item.DiscountProductId.HasValue)
|
||||
{
|
||||
var discountProduct = await _context.DiscountProducts.FindAsync(
|
||||
new object[] { item.DiscountProductId.Value }, ct);
|
||||
if (discountProduct != null)
|
||||
{
|
||||
discountProduct.RemainingCount = item.Quantity;
|
||||
await _context.SaveChangesAsync(ct);
|
||||
_logger.LogDebug("Synced RemainingCount for DiscountProduct Id={ProductId} to {Quantity}",
|
||||
item.DiscountProductId, item.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to sync RemainingCount for InventoryItem Id={ItemId}", item.Id);
|
||||
// Don't throw - sync failure shouldn't break the main operation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ثبت حرکت موجودی در StockMovements
|
||||
/// </summary>
|
||||
private async Task LogMovementAsync(
|
||||
long inventoryItemId,
|
||||
StockMovementType movementType,
|
||||
int quantity,
|
||||
int quantityBefore,
|
||||
int quantityAfter,
|
||||
long? orderId,
|
||||
long? discountOrderId,
|
||||
string? referenceNumber,
|
||||
string? note,
|
||||
long? performedByUserId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var movement = new StockMovement
|
||||
{
|
||||
InventoryItemId = inventoryItemId,
|
||||
MovementType = movementType,
|
||||
Quantity = quantity,
|
||||
QuantityBefore = quantityBefore,
|
||||
QuantityAfter = quantityAfter,
|
||||
OrderId = orderId,
|
||||
DiscountOrderId = discountOrderId,
|
||||
ReferenceNumber = referenceNumber,
|
||||
Note = note,
|
||||
PerformedByUserId = performedByUserId
|
||||
};
|
||||
|
||||
_context.StockMovements.Add(movement);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user