feat: Implement pagination and filtering for linked offers by integration
- Refactored `listLinkedOffersByIntegration` to `paginateLinkedOffersByIntegration` in `MarketplaceRepository`. - Added pagination support with `page` and `per_page` filters. - Introduced sorting options for offers. - Created `listOfferChannelsByIntegration` method to retrieve distinct sales channels. - Enhanced SQL queries to support dynamic filtering based on provided parameters. feat: Add new fields for products and SKU generation - Introduced new fields: `new_to_date`, `additional_message`, `additional_message_required`, and `additional_message_text` in the `products` table. - Added `findAllSkus` method in `ProductRepository` to retrieve all SKUs. - Created `ProductSkuGenerator` class to handle SKU generation based on a configurable format. - Implemented `nextSku` method to generate the next available SKU. feat: Enhance product settings management in the UI - Added new settings page for product SKU format in `SettingsController`. - Implemented form handling for saving SKU format settings. - Updated the view to include SKU format configuration options. feat: Implement cron job for refreshing ShopPro offer titles - Created `ShopProOfferTitlesRefreshHandler` to handle the cron job for refreshing offer titles. - Integrated with the `OfferImportService` to import offers from ShopPro. docs: Update database schema documentation - Added documentation for new fields in the `products` table and new cron job for offer title refresh. - Documented the purpose and structure of the `app_settings` table. migrations: Add necessary migrations for new features - Created migration to add `products_sku_format` setting in `app_settings`. - Added migration to introduce new fields in the `products` table. - Created migration for the new cron job schedule for refreshing ShopPro offer titles.
This commit is contained in:
@@ -6,9 +6,18 @@
|
||||
.wysiwyg-wrap .ql-editor { min-height: var(--editor-min-height, 80px); }
|
||||
</style>
|
||||
|
||||
<?php
|
||||
$integrationEditMode = (bool) ($integrationEditMode ?? false);
|
||||
$productFormAction = (string) ($productFormAction ?? '/products/update');
|
||||
$productBackUrl = (string) ($productBackUrl ?? '/products');
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
<h1><?= $e($t('products.edit.title', ['id' => (string) ($productId ?? 0)])) ?></h1>
|
||||
<h1><?= $e((string) ($title ?? $t('products.edit.title', ['id' => (string) ($productId ?? 0)]))) ?></h1>
|
||||
<p class="muted"><?= $e($t('products.edit.description')) ?></p>
|
||||
<?php if ($integrationEditMode): ?>
|
||||
<p class="muted mt-8">Tryb integracyjny: zapis aktualizuje bezposrednio produkt w shopPRO i synchronizuje dane lokalne.</p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="card mt-16">
|
||||
@@ -21,15 +30,17 @@
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $images = is_array($productImages ?? null) ? $productImages : []; ?>
|
||||
<form class="product-form mt-16" method="post" action="/products/update" enctype="multipart/form-data">
|
||||
<form class="product-form mt-16" method="post" action="<?= $e($productFormAction) ?>" enctype="multipart/form-data">
|
||||
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
|
||||
<input type="hidden" name="id" value="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<input type="hidden" id="product-image-csrf" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<div class="form-grid">
|
||||
<label class="form-field">
|
||||
<div class="form-field">
|
||||
<span class="field-label">SKU</span>
|
||||
<input class="form-control" type="text" name="sku" value="<?= $e((string) ($form['sku'] ?? '')) ?>">
|
||||
</label>
|
||||
<input class="form-control" type="text" id="product-sku-input" name="sku" value="<?= $e((string) ($form['sku'] ?? '')) ?>">
|
||||
<button type="button" class="btn btn--secondary mt-12" id="product-generate-sku-btn"><?= $e($t('products.actions.generate_next_sku')) ?></button>
|
||||
</div>
|
||||
|
||||
<label class="form-field">
|
||||
<span class="field-label">EAN</span>
|
||||
@@ -214,10 +225,10 @@
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!$integrationEditMode): ?>
|
||||
<section class="card mt-16">
|
||||
<h3><?= $e($t('products.images.title')) ?></h3>
|
||||
<p class="muted"><?= $e($t('products.images.description')) ?></p>
|
||||
<input type="hidden" id="product-image-csrf" value="<?= $e($csrfToken ?? '') ?>">
|
||||
|
||||
<div class="product-images-grid mt-12" id="product-images-grid" data-product-id="<?= $e((string) ($productId ?? 0)) ?>">
|
||||
<?php foreach ($images as $image): ?>
|
||||
@@ -262,14 +273,68 @@
|
||||
<p class="muted" id="product-image-upload-status"></p>
|
||||
<p class="muted"><?= $e($t('products.images.main_hint')) ?></p>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-actions mt-16">
|
||||
<button class="btn btn--primary" type="submit"><?= $e($t('products.actions.save')) ?></button>
|
||||
<a class="btn btn--secondary" href="/products"><?= $e($t('products.actions.back')) ?></a>
|
||||
<a class="btn btn--secondary" href="<?= $e($productBackUrl) ?>"><?= $e($t('products.actions.back')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var skuInput = document.getElementById('product-sku-input');
|
||||
var generateSkuBtn = document.getElementById('product-generate-sku-btn');
|
||||
var tokenInput = document.getElementById('product-image-csrf');
|
||||
if (!skuInput || !generateSkuBtn || !tokenInput) return;
|
||||
|
||||
var csrfToken = tokenInput.value || '';
|
||||
var errTitle = <?= json_encode((string) $t('products.sku_generator.confirm_title'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var errDefault = <?= json_encode((string) $t('products.sku_generator.failed'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
|
||||
function showError(message) {
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.alert === 'function') {
|
||||
window.OrderProAlerts.alert({
|
||||
title: errTitle,
|
||||
message: message || errDefault,
|
||||
danger: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var uploadStatus = document.getElementById('product-image-upload-status');
|
||||
if (uploadStatus) {
|
||||
uploadStatus.textContent = message || errDefault;
|
||||
}
|
||||
}
|
||||
|
||||
generateSkuBtn.addEventListener('click', async function() {
|
||||
generateSkuBtn.disabled = true;
|
||||
try {
|
||||
var payload = new FormData();
|
||||
payload.append('_token', csrfToken);
|
||||
|
||||
var response = await fetch('/products/next-sku', {
|
||||
method: 'POST',
|
||||
body: payload,
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
var result = await response.json();
|
||||
if (!response.ok || result.ok !== true || !result.sku) {
|
||||
throw new Error(result.message || errDefault);
|
||||
}
|
||||
|
||||
skuInput.value = String(result.sku);
|
||||
} catch (error) {
|
||||
showError((error && error.message) ? error.message : errDefault);
|
||||
} finally {
|
||||
generateSkuBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var grid = document.getElementById('product-images-grid');
|
||||
@@ -524,17 +589,15 @@
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var initialTab = <?= json_encode((string) ($initialContentTab ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
var nav = document.getElementById('content-tabs-nav');
|
||||
if (!nav) return;
|
||||
|
||||
nav.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.content-tab-btn');
|
||||
function setTab(tabId) {
|
||||
if (!tabId) return;
|
||||
var btn = nav.querySelector('.content-tab-btn[data-tab="' + tabId.replace(/"/g, '\\"') + '"]');
|
||||
if (!btn) return;
|
||||
|
||||
var tabId = btn.getAttribute('data-tab');
|
||||
if (!tabId) return;
|
||||
|
||||
// deactivate all
|
||||
nav.querySelectorAll('.content-tab-btn').forEach(function(b) {
|
||||
b.classList.remove('is-active');
|
||||
});
|
||||
@@ -542,10 +605,22 @@
|
||||
p.classList.remove('is-active');
|
||||
});
|
||||
|
||||
// activate selected
|
||||
btn.classList.add('is-active');
|
||||
var panel = document.getElementById('content-tab-' + tabId);
|
||||
if (panel) panel.classList.add('is-active');
|
||||
}
|
||||
|
||||
nav.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.content-tab-btn');
|
||||
if (!btn) return;
|
||||
|
||||
var tabId = btn.getAttribute('data-tab');
|
||||
if (!tabId) return;
|
||||
setTab(tabId);
|
||||
});
|
||||
|
||||
if (initialTab) {
|
||||
setTab(initialTab);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user