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:
@@ -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);
|
||||
|
||||
@@ -435,6 +435,91 @@ class OrderRepository
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Order product CRUD (admin) ---
|
||||
|
||||
public function getOrderProduct(int $orderProductId): ?array
|
||||
{
|
||||
if ($orderProductId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $this->db->get('pp_shop_order_products', '*', ['id' => $orderProductId]);
|
||||
|
||||
return is_array($row) ? $row : null;
|
||||
}
|
||||
|
||||
public function addOrderProduct(int $orderId, array $data): ?int
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->db->insert('pp_shop_order_products', [
|
||||
'order_id' => $orderId,
|
||||
'product_id' => (int)($data['product_id'] ?? 0),
|
||||
'parent_product_id' => (int)($data['parent_product_id'] ?? 0),
|
||||
'name' => (string)($data['name'] ?? ''),
|
||||
'attributes' => (string)($data['attributes'] ?? ''),
|
||||
'vat' => (float)($data['vat'] ?? 0),
|
||||
'price_brutto' => (float)($data['price_brutto'] ?? 0),
|
||||
'price_brutto_promo' => (float)($data['price_brutto_promo'] ?? 0),
|
||||
'quantity' => max(1, (int)($data['quantity'] ?? 1)),
|
||||
'message' => (string)($data['message'] ?? ''),
|
||||
'custom_fields' => (string)($data['custom_fields'] ?? ''),
|
||||
]);
|
||||
|
||||
$id = $this->db->id();
|
||||
|
||||
return $id ? (int)$id : null;
|
||||
}
|
||||
|
||||
public function updateOrderProduct(int $orderProductId, array $data): bool
|
||||
{
|
||||
if ($orderProductId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$update = [];
|
||||
|
||||
if (array_key_exists('quantity', $data)) {
|
||||
$update['quantity'] = max(1, (int)$data['quantity']);
|
||||
}
|
||||
if (array_key_exists('price_brutto', $data)) {
|
||||
$update['price_brutto'] = (float)$data['price_brutto'];
|
||||
}
|
||||
if (array_key_exists('price_brutto_promo', $data)) {
|
||||
$update['price_brutto_promo'] = (float)$data['price_brutto_promo'];
|
||||
}
|
||||
|
||||
if (empty($update)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->db->update('pp_shop_order_products', $update, ['id' => $orderProductId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteOrderProduct(int $orderProductId): bool
|
||||
{
|
||||
if ($orderProductId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->db->delete('pp_shop_order_products', ['id' => $orderProductId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateTransportCost(int $orderId, float $cost): void
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->db->update('pp_shop_orders', ['transport_cost' => $cost], ['id' => $orderId]);
|
||||
}
|
||||
|
||||
// --- Frontend methods ---
|
||||
|
||||
public function findIdByHash(string $hash)
|
||||
@@ -652,6 +737,11 @@ class OrderRepository
|
||||
|
||||
$product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position, $productRepo);
|
||||
|
||||
// Cena promo = 0 gdy taka sama jak cena bazowa (brak realnej promocji/kuponu)
|
||||
$effectivePromoPrice = (float)$product_price_tmp['price_new'];
|
||||
$effectiveBasePrice = (float)$product_price_tmp['price'];
|
||||
$promoPrice = ($effectivePromoPrice != $effectiveBasePrice) ? $effectivePromoPrice : 0;
|
||||
|
||||
$this->db->insert('pp_shop_order_products', [
|
||||
'order_id' => $order_id,
|
||||
'product_id' => $basket_position['product-id'],
|
||||
@@ -659,8 +749,8 @@ class OrderRepository
|
||||
'name' => $product['language']['name'],
|
||||
'attributes' => $attributes,
|
||||
'vat' => $product['vat'],
|
||||
'price_brutto' => $product_price_tmp['price'],
|
||||
'price_brutto_promo' => $product_price_tmp['price_new'],
|
||||
'price_brutto' => $effectiveBasePrice,
|
||||
'price_brutto_promo' => $promoPrice,
|
||||
'quantity' => $basket_position['quantity'],
|
||||
'message' => $basket_position['message'],
|
||||
'custom_fields' => $product_custom_fields,
|
||||
|
||||
Reference in New Issue
Block a user