185 lines
8.3 KiB
C#
185 lines
8.3 KiB
C#
using FruitBank.Common.Dtos;
|
|
using FruitBank.Common.Entities;
|
|
using FruitBank.Common.Enums;
|
|
using FruitBank.Common.Interfaces;
|
|
using Nop.Core.Domain.Catalog;
|
|
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
|
using Nop.Services.Catalog;
|
|
using Nop.Services.Orders;
|
|
|
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Services;
|
|
|
|
/// <summary>
|
|
/// Extension methods for product replacement logic on PreorderConversionService.
|
|
///
|
|
/// SETUP REQUIRED: Add this as a partial class by:
|
|
/// 1. Add `partial` keyword to PreorderConversionService class declaration
|
|
/// 2. This file uses the same injected fields — no extra DI needed
|
|
/// </summary>
|
|
public partial class PreorderConversionService
|
|
{
|
|
// ── Product replacement ───────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Called from FruitBankDataController.UpdateShippingItem when ProductId changes.
|
|
/// oldItem must be loaded BEFORE the DB update to capture the previous state.
|
|
/// </summary>
|
|
public async Task ReplaceShippingItemProductAsync(
|
|
int shippingItemId,
|
|
int newProductId,
|
|
ShippingItem oldItem)
|
|
{
|
|
if (oldItem.ProductId == null || oldItem.ProductId.Value == newProductId) return;
|
|
|
|
var oldProductId = oldItem.ProductId.Value;
|
|
var qty = oldItem.QuantityOnDocument;
|
|
var isMeasured = oldItem.IsMeasured;
|
|
|
|
Console.WriteLine($"[ReplaceShippingItemProduct] #{shippingItemId}: {oldProductId}→{newProductId}, qty={qty}, measured={isMeasured}");
|
|
|
|
// ── Guard: reject if any linked order is being measured ───────────────
|
|
var affectedOrders = await GetAffectedOpenOrdersAsync(oldProductId);
|
|
var startedOrders = affectedOrders
|
|
.Where(o => o.MeasuringStatus > MeasuringStatus.NotStarted)
|
|
.ToList();
|
|
|
|
if (startedOrders.Any())
|
|
throw new InvalidOperationException(
|
|
$"Mérés folyamatban a következő rendeléseken: " +
|
|
$"#{string.Join(", #", startedOrders.Select(o => o.Id))}. " +
|
|
$"Termékcsere nem lehetséges.");
|
|
|
|
// ── Stock / IncomingQuantity swap ─────────────────────────────────────
|
|
if (!isMeasured)
|
|
{
|
|
// Item not yet on the truck — only IncomingQuantity moves
|
|
await SyncIncomingQuantityAsync(oldProductId, qty, 0);
|
|
await SyncIncomingQuantityAsync(newProductId, 0, qty);
|
|
}
|
|
else
|
|
{
|
|
// Item already measured/received into stock — adjust actual stock
|
|
await _dbContext.UpdateStockQuantityAndWeightAsync(
|
|
oldProductId, -oldItem.MeasuredQuantity,
|
|
$"Termék csere: shippingItem #{shippingItemId} — {oldProductId}→{newProductId}",
|
|
oldItem.IsMeasurable ? -oldItem.MeasuredNetWeight : 0d);
|
|
|
|
await _dbContext.UpdateStockQuantityAndWeightAsync(
|
|
newProductId, +oldItem.MeasuredQuantity,
|
|
$"Termék csere: shippingItem #{shippingItemId} — {oldProductId}→{newProductId}",
|
|
oldItem.IsMeasurable ? +oldItem.MeasuredNetWeight : 0d);
|
|
}
|
|
|
|
// ── Swap order items ──────────────────────────────────────────────────
|
|
var openOrders = affectedOrders
|
|
.Where(o => o.MeasuringStatus == MeasuringStatus.NotStarted)
|
|
.OrderBy(o => o.Id)
|
|
.ToList();
|
|
|
|
var replacementBudget = qty;
|
|
var affectedOrderIds = new List<int>();
|
|
|
|
foreach (var orderDto in openOrders)
|
|
{
|
|
if (replacementBudget <= 0) break;
|
|
|
|
var order = await _dbContext.Orders.GetByIdAsync(orderDto.Id);
|
|
if (order == null) continue;
|
|
|
|
var allItems = await _orderService.GetOrderItemsAsync(order.Id);
|
|
var oldOrderItem = allItems.FirstOrDefault(oi => oi.ProductId == oldProductId);
|
|
if (oldOrderItem == null) continue;
|
|
|
|
var swapQty = Math.Min(oldOrderItem.Quantity, replacementBudget);
|
|
|
|
// Remove old item — restores stock, deletes pallets/GAs, deletes item
|
|
await _orderItemService.RemoveOrderItemFromOrderAsync(
|
|
order, oldOrderItem,
|
|
$"Termék csere: #{oldProductId}→#{newProductId}, rendelés #{order.Id}");
|
|
|
|
// Add new item — direct insert without availability check
|
|
var newProduct = await _productService.GetProductByIdAsync(newProductId);
|
|
if (newProduct != null)
|
|
{
|
|
var newProductDto = await _dbContext.ProductDtos.GetByIdAsync(newProductId, true);
|
|
var newOrderItem = await _orderItemService.CreateOrderItemAsync(
|
|
newProduct, order,
|
|
productId : newProductId,
|
|
quantity : swapQty,
|
|
price : oldOrderItem.UnitPriceInclTax,
|
|
isMeasurable : newProductDto?.IsMeasurable ?? false,
|
|
unitPricesIncludeDiscounts : false);
|
|
|
|
await _orderItemService.AddOrderItemToOrderAsync(
|
|
order, newOrderItem,
|
|
$"Termék csere felvitel: #{newProductId}, rendelés #{order.Id}");
|
|
}
|
|
|
|
await _orderService.UpdateOrderAsync(order);
|
|
await _orderItemService.InsertOrderNoteAsync(order.Id, false,
|
|
$"Termék cserélve: #{oldProductId}→#{newProductId} ({swapQty} db), " +
|
|
$"szállítói dok. #{oldItem.ShippingDocumentId}");
|
|
|
|
replacementBudget -= swapQty;
|
|
affectedOrderIds.Add(order.Id);
|
|
}
|
|
|
|
// ── Swap preorder items ───────────────────────────────────────────────
|
|
if (affectedOrderIds.Any())
|
|
{
|
|
var preorders = (await _preorderDbContext.Preorders.GetAll(false).ToListAsync())
|
|
.Where(p => p.OrderId.HasValue && affectedOrderIds.Contains(p.OrderId.Value))
|
|
.ToList();
|
|
|
|
foreach (var preorder in preorders)
|
|
{
|
|
var piList = await _preorderDbContext.PreorderItems
|
|
.GetAllByPreorderIdAsync(preorder.Id)
|
|
.ToListAsync();
|
|
|
|
foreach (var pi in piList.Where(i => i.ProductId == oldProductId))
|
|
{
|
|
pi.ProductId = newProductId;
|
|
await _preorderDbContext.PreorderItems.UpdateAsync(pi);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Trigger conversion for new product ────────────────────────────────
|
|
// New product may now have pending preorders that can be fulfilled
|
|
await ConvertPreordersForProductsAsync(
|
|
new List<int> { newProductId },
|
|
oldItem.ShippingDocumentId);
|
|
|
|
// TODO: SignalR notification to admin hub
|
|
// TODO: SendPreorderProductReplacedNotificationAsync per affected customer
|
|
|
|
Console.WriteLine($"[ReplaceShippingItemProduct] Complete: " +
|
|
$"{affectedOrderIds.Count} orders swapped, budget remaining={replacementBudget}");
|
|
}
|
|
|
|
private async Task<List<OrderDto>> GetAffectedOpenOrdersAsync(int oldProductId)
|
|
{
|
|
// Orders that are referenced by a preorder AND contain the old product
|
|
var preorderOrderIds = (await _preorderDbContext.Preorders.GetAll(false).ToListAsync())
|
|
.Where(p => p.OrderId.HasValue)
|
|
.Select(p => p.OrderId!.Value)
|
|
.ToHashSet();
|
|
|
|
if (!preorderOrderIds.Any()) return new();
|
|
|
|
var matchingOrderIds = await _dbContext.OrderItems.Table
|
|
.Where(oi => oi.ProductId == oldProductId && preorderOrderIds.Contains(oi.OrderId))
|
|
.Select(oi => oi.OrderId)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
if (!matchingOrderIds.Any()) return new();
|
|
|
|
return await _dbContext.OrderDtos
|
|
.GetAll(false)
|
|
.Where(o => matchingOrderIds.Contains(o.Id) && !o.Deleted)
|
|
.ToListAsync();
|
|
}
|
|
}
|