feat(19-ui-integration): przycisk Drukuj, bulk print, kolejka wydruku
- Przycisk "Drukuj" w prepare.php i show.php z AJAX + duplikat protection - Bulk print z listy zamówień (checkboxy + header action) - Kolejka wydruku w Ustawienia > Drukowanie (filtr statusu, retry) - POST /api/print/jobs/bulk endpoint (package_ids + order_ids) - ensureLabel() auto-download przez ShipmentProviderRegistry - Apaczka carrier_id = nazwa usługi, kolumna Przewoznik - Tab persistence (localStorage), label file_exists check - Fix use statement ApaczkaApiClient, redirect po utworzeniu przesyłki - Phase 17 (receipt duplicate guard) + Phase 18 (print queue backend) docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,54 @@
|
||||
popup.style.left = left + 'px';
|
||||
popup.style.top = top + 'px';
|
||||
}, true);
|
||||
|
||||
// Bulk print labels
|
||||
var bulkPrintBtn = document.querySelector('.js-bulk-print-labels');
|
||||
if (bulkPrintBtn) {
|
||||
bulkPrintBtn.addEventListener('click', function () {
|
||||
var checked = document.querySelectorAll('.js-table-select-item:checked');
|
||||
if (checked.length === 0) {
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: 'Zaznacz co najmniej jedno zamowienie.', type: 'warning' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var orderIds = [];
|
||||
checked.forEach(function (cb) { orderIds.push(cb.value); });
|
||||
var csrf = bulkPrintBtn.getAttribute('data-csrf') || '';
|
||||
|
||||
bulkPrintBtn.disabled = true;
|
||||
bulkPrintBtn.textContent = 'Wysylam...';
|
||||
|
||||
fetch('/api/print/jobs/bulk', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ order_ids: orderIds, _token: csrf })
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
var created = (data.created || []).length;
|
||||
var skipped = (data.skipped || []).length;
|
||||
var msg = 'Wyslano ' + created + ' zlecen do drukarki.';
|
||||
if (skipped > 0) {
|
||||
msg += ' Pominieto ' + skipped + ' (brak etykiety lub juz w kolejce).';
|
||||
}
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: msg, type: 'success' });
|
||||
}
|
||||
bulkPrintBtn.disabled = false;
|
||||
bulkPrintBtn.textContent = 'Drukuj etykiety';
|
||||
})
|
||||
.catch(function () {
|
||||
if (window.OrderProAlerts) {
|
||||
window.OrderProAlerts.show({ message: 'Blad sieci — sprobuj ponownie.', type: 'error' });
|
||||
}
|
||||
bulkPrintBtn.disabled = false;
|
||||
bulkPrintBtn.textContent = 'Drukuj etykiety';
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ $configsList = is_array($configs ?? null) ? $configs : [];
|
||||
$sellerData = is_array($seller ?? null) ? $seller : [];
|
||||
$totalGrossVal = (float) ($totalGross ?? 0);
|
||||
$orderIdVal = (int) ($orderId ?? 0);
|
||||
$existingReceiptsList = is_array($existingReceipts ?? null) ? $existingReceipts : [];
|
||||
$hasExistingReceipts = $existingReceiptsList !== [];
|
||||
?>
|
||||
|
||||
<section class="card">
|
||||
@@ -18,7 +20,23 @@ $orderIdVal = (int) ($orderId ?? 0);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/orders/<?= $e((string) $orderIdVal) ?>/receipt/store" class="mt-16">
|
||||
<?php if ($hasExistingReceipts): ?>
|
||||
<div class="alert alert--warning mt-12">
|
||||
<strong>Uwaga!</strong> Do tego zamówienia wystawiono już <?= $e((string) count($existingReceiptsList)) ?> paragon(ów):
|
||||
<ul class="mt-4">
|
||||
<?php foreach ($existingReceiptsList as $er): ?>
|
||||
<li>
|
||||
<strong><?= $e((string) ($er['receipt_number'] ?? '-')) ?></strong>
|
||||
— data: <?= $e((string) ($er['issue_date'] ?? '-')) ?>,
|
||||
kwota: <?= $e(number_format((float) ($er['total_gross'] ?? 0), 2, '.', ' ')) ?> PLN
|
||||
(<?= $e((string) ($er['config_name'] ?? '-')) ?>)
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form id="receipt-create-form" method="post" action="/orders/<?= $e((string) $orderIdVal) ?>/receipt/store" class="mt-16">
|
||||
<input type="hidden" name="_token" value="<?= $e((string) ($csrfToken ?? '')) ?>">
|
||||
|
||||
<div class="form-grid-2">
|
||||
@@ -97,8 +115,25 @@ $orderIdVal = (int) ($orderId ?? 0);
|
||||
</div>
|
||||
|
||||
<div class="mt-16">
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('receipts.create.submit')) ?></button>
|
||||
<?php if ($hasExistingReceipts): ?>
|
||||
<button type="button" id="receipt-submit-btn" class="btn btn--primary"><?= $e($t('receipts.create.submit')) ?></button>
|
||||
<?php else: ?>
|
||||
<button type="submit" class="btn btn--primary"><?= $e($t('receipts.create.submit')) ?></button>
|
||||
<?php endif; ?>
|
||||
<a href="/orders/<?= $e((string) $orderIdVal) ?>" class="btn btn--secondary ml-8"><?= $e($t('receipts.create.cancel')) ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<?php if ($hasExistingReceipts): ?>
|
||||
<script>
|
||||
document.getElementById('receipt-submit-btn').addEventListener('click', function() {
|
||||
window.OrderProAlerts.confirm(
|
||||
'Do tego zamówienia wystawiono już paragon. Czy na pewno chcesz wystawić kolejny?',
|
||||
function() {
|
||||
document.getElementById('receipt-create-form').submit();
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -355,7 +355,7 @@ foreach ($addressesList as $address) {
|
||||
<div class="order-tab-panel" data-order-tab-panel="shipments">
|
||||
<?php if ($packagesList !== []): ?>
|
||||
<section class="card mt-16">
|
||||
<h3 class="section-title">Wygenerowane przesylki (WZA)</h3>
|
||||
<h3 class="section-title">Wygenerowane przesylki</h3>
|
||||
<div class="table-wrap mt-12">
|
||||
<table class="table table--details">
|
||||
<thead>
|
||||
@@ -369,12 +369,21 @@ foreach ($addressesList as $address) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $storageBase = dirname(__DIR__, 3) . '/storage/'; ?>
|
||||
<?php $pendingPrintIds = is_array($pendingPrintPackageIds ?? null) ? $pendingPrintPackageIds : []; ?>
|
||||
<?php foreach ($packagesList as $pkg): ?>
|
||||
<?php
|
||||
$pkgStatus = (string) ($pkg['status'] ?? 'draft');
|
||||
$pkgTracking = trim((string) ($pkg['tracking_number'] ?? ''));
|
||||
$pkgCarrier = trim((string) ($pkg['carrier_id'] ?? ''));
|
||||
$pkgCarrierId = trim((string) ($pkg['carrier_id'] ?? ''));
|
||||
$pkgProvider = trim((string) ($pkg['provider'] ?? ''));
|
||||
$providerLabels = ['apaczka' => 'Apaczka', 'allegro_wza' => 'Allegro', 'inpost' => 'InPost'];
|
||||
$pkgProviderLabel = $providerLabels[$pkgProvider] ?? $pkgProvider;
|
||||
$pkgCarrier = $pkgCarrierId !== '' ? ($pkgProviderLabel . ' → ' . $pkgCarrierId) : $pkgProviderLabel;
|
||||
$pkgLabelPath = trim((string) ($pkg['label_path'] ?? ''));
|
||||
if ($pkgLabelPath !== '' && !file_exists($storageBase . $pkgLabelPath)) {
|
||||
$pkgLabelPath = '';
|
||||
}
|
||||
$pkgError = trim((string) ($pkg['error_message'] ?? ''));
|
||||
?>
|
||||
<tr>
|
||||
@@ -388,13 +397,26 @@ foreach ($addressesList as $address) {
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?></td>
|
||||
<td><?= $e($pkgCarrier !== '' ? $pkgCarrier : '-') ?></td>
|
||||
<td><?php if ($pkgCarrierId !== ''): ?><?= $e($pkgProviderLabel) ?> → <?= $e($pkgCarrierId) ?><?php elseif ($pkgProviderLabel !== ''): ?><?= $e($pkgProviderLabel) ?><?php else: ?>-<?php endif; ?></td>
|
||||
<td>
|
||||
<?php if ($pkgLabelPath !== ''): ?>
|
||||
<span style="display:inline-flex;gap:4px;align-items:center">
|
||||
<?php if ($pkgLabelPath !== '' && $pkgStatus !== 'error'): ?>
|
||||
<a href="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/prepare" class="btn btn--sm btn--secondary">Pobierz</a>
|
||||
<?php else: ?>
|
||||
<?php elseif ($pkgStatus !== 'error'): ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($pkgStatus, ['label_ready', 'created'], true)): ?>
|
||||
<?php if (in_array((int) ($pkg['id'] ?? 0), $pendingPrintIds, true)): ?>
|
||||
<button type="button" class="btn btn--sm btn--danger" disabled style="white-space:nowrap">W kolejce</button>
|
||||
<?php else: ?>
|
||||
<button type="button"
|
||||
class="btn btn--sm btn--secondary btn-print-label"
|
||||
data-package-id="<?= $e((string) ($pkg['id'] ?? 0)) ?>"
|
||||
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
|
||||
title="Wyslij do drukarki">Drukuj</button>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
|
||||
</tr>
|
||||
@@ -615,14 +637,58 @@ foreach ($addressesList as $address) {
|
||||
});
|
||||
}
|
||||
|
||||
var storageKey = 'orderDetailTab';
|
||||
|
||||
tabButtons.forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
setActiveTab(button.getAttribute('data-order-tab-target') || 'details');
|
||||
var target = button.getAttribute('data-order-tab-target') || 'details';
|
||||
setActiveTab(target);
|
||||
try { localStorage.setItem(storageKey, target); } catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
setActiveTab('details');
|
||||
var forceTab = <?= json_encode($flashSuccessMsg !== '' && strpos($flashSuccessMsg, 'Przesylka') !== false ? 'shipments' : '') ?>;
|
||||
var savedTab = null;
|
||||
try { savedTab = localStorage.getItem(storageKey); } catch (e) {}
|
||||
setActiveTab(forceTab || savedTab || 'details');
|
||||
|
||||
// Print label button handler
|
||||
document.querySelectorAll('.btn-print-label').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var packageId = btn.getAttribute('data-package-id');
|
||||
if (!packageId) return;
|
||||
btn.disabled = true;
|
||||
var originalText = btn.innerHTML;
|
||||
btn.innerHTML = 'Wysylam...';
|
||||
var csrfInput = document.querySelector('input[name="_token"]');
|
||||
var csrf = csrfInput ? csrfInput.value : '<?= $e($csrfToken ?? '') ?>';
|
||||
|
||||
fetch('/api/print/jobs', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: '_token=' + encodeURIComponent(csrf) + '&package_id=' + encodeURIComponent(packageId)
|
||||
})
|
||||
.then(function (r) { return r.json().then(function (d) { return { status: r.status, data: d }; }); })
|
||||
.then(function (res) {
|
||||
if (res.status === 201 || res.status === 409) {
|
||||
btn.innerHTML = 'W kolejce';
|
||||
btn.disabled = true;
|
||||
btn.classList.remove('btn--secondary');
|
||||
btn.classList.add('btn--danger');
|
||||
} else {
|
||||
var msg = (res.data && res.data.error) ? res.data.error : 'Nieznany blad';
|
||||
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: msg, type: 'error' }); }
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (window.OrderProAlerts) { window.OrderProAlerts.show({ message: 'Blad sieci.', type: 'error' }); }
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user