ver. 0.295: Admin order product editing — add/remove/modify products, AJAX search, stock adjustment

- Order product CRUD in admin panel (add, delete, edit quantity/prices)
- AJAX product search endpoint for order edit form
- Automatic stock adjustment when editing order products
- Transport cost recalculation based on free delivery threshold
- Fix: promo price = 0 when equal to base price (no real promotion)
- Clean up stale temp/ build artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 19:30:38 +01:00
parent de11afb003
commit 21efe28464
73 changed files with 1037 additions and 9560 deletions

View File

@@ -4,10 +4,20 @@ namespace Domain\Order;
class OrderAdminService
{
private OrderRepository $orders;
private $productRepo;
private $settingsRepo;
private $transportRepo;
public function __construct(OrderRepository $orders)
{
public function __construct(
OrderRepository $orders,
$productRepo = null,
$settingsRepo = null,
$transportRepo = null
) {
$this->orders = $orders;
$this->productRepo = $productRepo;
$this->settingsRepo = $settingsRepo;
$this->transportRepo = $transportRepo;
}
public function details(int $orderId): array
@@ -71,6 +81,215 @@ class OrderAdminService
return $saved;
}
// =========================================================================
// Order products management (admin)
// =========================================================================
public function searchProducts(string $query, string $langId): array
{
if (!$this->productRepo || trim($query) === '') {
return [];
}
$rows = $this->productRepo->searchProductByNameAjax($query, $langId);
$results = [];
foreach ($rows as $row) {
$productId = (int)($row['product_id'] ?? 0);
if ($productId <= 0) {
continue;
}
$product = $this->productRepo->findCached($productId, $langId);
if (!is_array($product)) {
continue;
}
$name = isset($product['language']['name']) ? (string)$product['language']['name'] : '';
$img = $this->productRepo->getProductImg($productId);
$results[] = [
'product_id' => $productId,
'parent_product_id' => (int)($product['parent_id'] ?? 0),
'name' => $name,
'sku' => (string)($product['sku'] ?? ''),
'ean' => (string)($product['ean'] ?? ''),
'price_brutto' => (float)($product['price_brutto'] ?? 0),
'price_brutto_promo' => (float)($product['price_brutto_promo'] ?? 0),
'vat' => (float)($product['vat'] ?? 0),
'quantity' => (int)($product['quantity'] ?? 0),
'image' => $img,
];
}
return $results;
}
public function saveOrderProducts(int $orderId, array $productsData): bool
{
if ($orderId <= 0) {
return false;
}
$currentProducts = $this->orders->orderProducts($orderId);
$currentById = [];
foreach ($currentProducts as $cp) {
$currentById[(int)$cp['id']] = $cp;
}
$submittedIds = [];
foreach ($productsData as $item) {
$orderProductId = (int)($item['order_product_id'] ?? 0);
$deleted = !empty($item['delete']);
if ($deleted && $orderProductId > 0) {
// Usunięcie — zwrot na stan
$existing = isset($currentById[$orderProductId]) ? $currentById[$orderProductId] : null;
if ($existing) {
$this->adjustStock((int)$existing['product_id'], (int)$existing['quantity']);
}
$this->orders->deleteOrderProduct($orderProductId);
$submittedIds[] = $orderProductId;
continue;
}
if ($deleted) {
continue;
}
if ($orderProductId > 0 && isset($currentById[$orderProductId])) {
// Istniejący produkt — aktualizacja
$existing = $currentById[$orderProductId];
$newQty = max(1, (int)($item['quantity'] ?? 1));
$oldQty = (int)$existing['quantity'];
$qtyDiff = $oldQty - $newQty;
$update = [
'quantity' => $newQty,
'price_brutto' => (float)($item['price_brutto'] ?? $existing['price_brutto']),
'price_brutto_promo' => (float)($item['price_brutto_promo'] ?? $existing['price_brutto_promo']),
];
$this->orders->updateOrderProduct($orderProductId, $update);
// Korekta stanu: qtyDiff > 0 = zmniejszono ilość = zwrot na stan
if ($qtyDiff !== 0) {
$this->adjustStock((int)$existing['product_id'], $qtyDiff);
}
$submittedIds[] = $orderProductId;
} elseif ($orderProductId === 0) {
// Nowy produkt
$productId = (int)($item['product_id'] ?? 0);
$qty = max(1, (int)($item['quantity'] ?? 1));
$this->orders->addOrderProduct($orderId, [
'product_id' => $productId,
'parent_product_id' => (int)($item['parent_product_id'] ?? $productId),
'name' => (string)($item['name'] ?? ''),
'attributes' => '',
'vat' => (float)($item['vat'] ?? 0),
'price_brutto' => (float)($item['price_brutto'] ?? 0),
'price_brutto_promo' => (float)($item['price_brutto_promo'] ?? 0),
'quantity' => $qty,
'message' => '',
'custom_fields' => '',
]);
// Zmniejsz stan magazynowy
$this->adjustStock($productId, -$qty);
}
}
// Usunięte z formularza (nie przesłane) — zwrot na stan
foreach ($currentById as $cpId => $cp) {
if (!in_array($cpId, $submittedIds)) {
$this->adjustStock((int)$cp['product_id'], (int)$cp['quantity']);
$this->orders->deleteOrderProduct($cpId);
}
}
// Przelicz koszt dostawy (próg darmowej dostawy)
$this->recalculateTransportCost($orderId);
return true;
}
public function getFreeDeliveryThreshold(): float
{
if (!$this->settingsRepo) {
return 0.0;
}
return (float)$this->settingsRepo->getSingleValue('free_delivery');
}
private function adjustStock(int $productId, int $delta): void
{
if (!$this->productRepo || $productId <= 0 || $delta === 0) {
return;
}
$currentQty = $this->productRepo->getQuantity($productId);
if ($currentQty === null) {
return;
}
$newQty = max(0, $currentQty + $delta);
$this->productRepo->updateQuantity($productId, $newQty);
}
private function recalculateTransportCost(int $orderId): void
{
$order = $this->orders->findRawById($orderId);
if (!$order) {
return;
}
$transportId = (int)($order['transport_id'] ?? 0);
if ($transportId <= 0 || !$this->transportRepo || !$this->settingsRepo) {
return;
}
$transport = $this->transportRepo->findActiveById($transportId);
if (!is_array($transport)) {
return;
}
// Oblicz sumę produktów (bez dostawy)
$productsSummary = $this->calculateProductsTotal($orderId);
$freeDelivery = (float)$this->settingsRepo->getSingleValue('free_delivery');
if ((int)($transport['delivery_free'] ?? 0) === 1 && $freeDelivery > 0 && $productsSummary >= $freeDelivery) {
$transportCost = 0.0;
} else {
$transportCost = (float)($transport['cost'] ?? 0);
}
$this->orders->updateTransportCost($orderId, $transportCost);
}
private function calculateProductsTotal(int $orderId): float
{
$products = $this->orders->orderProducts($orderId);
$summary = 0.0;
foreach ($products as $row) {
$pricePromo = (float)($row['price_brutto_promo'] ?? 0);
$price = (float)($row['price_brutto'] ?? 0);
$quantity = (float)($row['quantity'] ?? 0);
if ($pricePromo > 0) {
$summary += $pricePromo * $quantity;
} else {
$summary += $price * $quantity;
}
}
return $summary;
}
public function changeStatus(int $orderId, int $status, bool $sendEmail): array
{
$order = $this->orders->findRawById($orderId);