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

@@ -84,7 +84,12 @@ $orderId = (int)($this -> order['id'] ?? 0);
<? endif;?>
</div>
<div class="col-md-6">
<div>Kwota zamówienia <b><?= $this -> order[ 'summary' ];?> zł</b></div>
<div>Kwota zamówienia <b id="order-summary-display"><?= $this -> order[ 'summary' ];?> zł</b></div>
<div class="mt5">
<span>Produkty: <b id="products-total-display">0,00</b> zł</span>
&nbsp;|&nbsp;
<span>Dostawa: <b id="transport-cost-display"><?= number_format((float)($this -> order['transport_cost'] ?? 0), 2, ',', '');?></b> zł</span>
</div>
<br>
<div class="row">
<div class="col-12">
@@ -160,46 +165,75 @@ $orderId = (int)($this -> order['id'] ?? 0);
</div>
<div class="row">
<div class="col-12">
<div class="text-big mb5">Produkty zamówienia:</div>
<div class="table-responsive">
<table class="table">
<table class="table" id="order-products-table">
<thead>
<tr>
<th>Zdjęcie</th>
<th style="width:60px">Zdjęcie</th>
<th scope="col">Nazwa</th>
<th scope="col" class="tab-center">Ilość</th>
<th scope="col" class="tab-right">Cena / szt:</th>
<th scope="col" class="tab-right">Cena / szt (po rabacie):</th>
<th scope="col" class="tab-right">Suma (po rabacie):</th>
<th scope="col" class="tab-center" style="width:80px">Ilość</th>
<th scope="col" class="tab-right" style="width:130px">Cena / szt:</th>
<th scope="col" class="tab-right" style="width:130px">Cena promo:</th>
<th scope="col" class="tab-right" style="width:110px">Suma:</th>
<th scope="col" style="width:40px"></th>
</tr>
</thead>
<tbody>
<? if ( is_array( $this -> order[ 'products' ] ) ): foreach ( $this -> order[ 'products' ] as $product ):?>
<tr class="order-product-details">
<tbody id="order-products-body">
<? $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); ?>
<? if ( is_array( $this -> order[ 'products' ] ) ): foreach ( $this -> order[ 'products' ] as $i => $product ):?>
<tr class="order-product-row" data-index="<?= $i;?>">
<input type="hidden" name="products[<?= $i;?>][order_product_id]" value="<?= (int)$product['id'];?>">
<input type="hidden" name="products[<?= $i;?>][product_id]" value="<?= (int)$product['product_id'];?>">
<input type="hidden" name="products[<?= $i;?>][parent_product_id]" value="<?= (int)($product['parent_product_id'] ?? $product['product_id']);?>">
<input type="hidden" name="products[<?= $i;?>][name]" value="<?= htmlspecialchars((string)$product['name'], ENT_QUOTES, 'UTF-8');?>">
<input type="hidden" name="products[<?= $i;?>][vat]" value="<?= (float)($product['vat'] ?? 0);?>">
<td class="product-image">
<? if ( $product['product_id'] ):?>
<img src="<?= ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->getProductImg( (int)$product['product_id'] );?>">
<img src="<?= $productRepo->getProductImg( (int)$product['product_id'] );?>" style="max-width:50px;max-height:50px;">
<? endif;?>
</td>
<td>
<a href="<?= ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->getProductUrl( (int)$product['product_id'] );?>" target="_blank"><?= $product[ 'name' ];?></a>
<br />
<div class="atributes">
<?= $product[ 'attributes' ];?>
</div>
<br />
<div class="product-message">
<?= $product[ 'message' ] != '' ? '<strong>Wiadomość:</strong> ' . $product['message'] : '';?>
</div>
<a href="<?= $productRepo->getProductUrl( (int)$product['product_id'] );?>" target="_blank"><?= $product[ 'name' ];?></a>
<? if ( $product['attributes'] ):?>
<div class="atributes small text-muted"><?= $product[ 'attributes' ];?></div>
<? endif;?>
<? if ( $product[ 'message' ] != '' ):?>
<div class="product-message small"><strong>Wiadomość:</strong> <?= $product['message'];?></div>
<? endif;?>
</td>
<td class="tab-center">
<input type="number" name="products[<?= $i;?>][quantity]" value="<?= (int)$product['quantity'];?>" min="1" class="form-control form-control-sm text-center product-qty" style="width:70px">
</td>
<td class="tab-right">
<input type="number" name="products[<?= $i;?>][price_brutto]" value="<?= number_format((float)$product['price_brutto'], 2, '.', '');?>" step="0.01" min="0" class="form-control form-control-sm text-right product-price" style="width:110px">
</td>
<td class="tab-right">
<input type="number" name="products[<?= $i;?>][price_brutto_promo]" value="<?= number_format((float)$product['price_brutto_promo'], 2, '.', '');?>" step="0.01" min="0" class="form-control form-control-sm text-right product-price-promo" style="width:110px">
</td>
<td class="tab-right product-row-total">
<?= number_format(((float)$product['price_brutto_promo'] > 0 ? (float)$product['price_brutto_promo'] : (float)$product['price_brutto']) * (int)$product['quantity'], 2, ',', '');?> zł
</td>
<td class="text-center">
<button type="button" class="btn btn-xs btn-danger btn-remove-product" title="Usuń produkt"><i class="fa fa-times"></i></button>
</td>
<td class="tab-center"><?= $product[ 'quantity' ];?></td>
<td class="tab-right"><?= $product[ 'price_brutto' ];?> zł</td>
<td class="tab-right"><?= $product[ 'price_brutto_promo' ];?> zł</td>
<td class="tab-right"><?= $product[ 'price_brutto_promo' ] * $product[ 'quantity' ];?> zł</td>
</tr>
<? endforeach; endif;?>
</tbody>
</table>
</div>
</div>
<div class="mb15" id="add-product-section">
<div class="text-big mb5">Dodaj produkt:</div>
<div class="row">
<div class="col-md-6">
<div style="position:relative">
<input type="text" id="product-search-input" class="form-control" placeholder="Wpisz nazwę produktu..." autocomplete="off">
<div id="product-search-results" style="display:none;position:absolute;z-index:1000;width:100%;max-height:300px;overflow-y:auto;background:#fff;border:1px solid #ddd;border-top:none;box-shadow:0 2px 8px rgba(0,0,0,0.15)"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
@@ -222,4 +256,11 @@ $orderId = (int)($this -> order['id'] ?? 0);
</div>
<link class="footer" rel="stylesheet" type="text/css" href="https://geowidget.easypack24.net/css/easypack.css">
<script class="footer" type="text/javascript" src="https://geowidget.easypack24.net/js/sdk-for-javascript.js"></script>
<script type="text/javascript">
var orderEditConfig = {
transports: <?= $this -> transports_json ?? '[]';?>,
freeDelivery: <?= (float)($this -> free_delivery ?? 0);?>,
currentTransportCost: <?= (float)($this -> order['transport_cost'] ?? 0);?>
};
</script>
<?= \Shared\Tpl\Tpl::view('shop-order/order-edit-custom-script');?>