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:
2026-03-22 21:16:54 +01:00
parent d1a1b79247
commit 02d06298ea
33 changed files with 2623 additions and 117 deletions

View File

@@ -7,6 +7,7 @@ $comp = is_array($company ?? null) ? $company : [];
$services = is_array($deliveryServices ?? null) ? $deliveryServices : [];
$apaczkaSvcList = is_array($apaczkaServices ?? null) ? $apaczkaServices : [];
$packages = is_array($existingPackages ?? null) ? $existingPackages : [];
$pendingPrintIds = is_array($pendingPrintPackageIds ?? null) ? $pendingPrintPackageIds : [];
$servicesError = (string) ($deliveryServicesError ?? '');
$flashSuccessMsg = (string) ($flashSuccess ?? '');
$flashErrorMsg = (string) ($flashError ?? '');
@@ -60,83 +61,6 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
<?php endif; ?>
</section>
<?php if ($packages !== []): ?>
<section class="card mt-16">
<h3 class="section-title">Utworzone przesylki</h3>
<div class="table-wrap mt-12">
<table class="table table--details">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Nr sledzenia</th>
<th>Przewoznik</th>
<th>Etykieta</th>
<th>Utworzono</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($packages as $pkg): ?>
<?php
$pkgId = (int) ($pkg['id'] ?? 0);
$pkgStatus = (string) ($pkg['status'] ?? 'draft');
$pkgTracking = trim((string) ($pkg['tracking_number'] ?? ''));
$pkgCarrier = trim((string) ($pkg['carrier_id'] ?? ''));
$pkgLabelPath = trim((string) ($pkg['label_path'] ?? ''));
$pkgShipmentId = trim((string) ($pkg['shipment_id'] ?? ''));
$pkgError = trim((string) ($pkg['error_message'] ?? ''));
?>
<tr>
<td><?= $e((string) $pkgId) ?></td>
<td>
<span class="order-tag <?= $pkgStatus === 'label_ready' || $pkgStatus === 'created' ? 'is-success' : ($pkgStatus === 'error' ? 'is-danger' : 'is-warn') ?>">
<?= $e($pkgStatus) ?>
</span>
<?php if ($pkgError !== ''): ?>
<div class="muted mt-4" style="font-size:0.75rem"><?= $e($pkgError) ?></div>
<?php endif; ?>
</td>
<td><?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?></td>
<td><?= $e($pkgCarrier !== '' ? $pkgCarrier : '-') ?></td>
<td>
<?php if ($pkgLabelPath !== ''): ?>
<?php if ($pkgStatus === 'error'): ?>
-
<?php else: ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/<?= $e((string) $pkgId) ?>/label" style="display:inline">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button>
</form>
<?php endif; ?>
<?php elseif ($pkgShipmentId !== '' && $pkgStatus === 'created'): ?>
<span class="muted">Generowanie etykiety...</span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
<td>
<?php
$shouldCheckStatus = $pkgStatus === 'pending' || ($pkgStatus === 'created' && $pkgLabelPath === '');
?>
<?php if ($shouldCheckStatus): ?>
<button type="button"
class="btn btn--sm btn--secondary"
data-check-status="<?= $e((string) $pkgId) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
data-package-status="<?= $e($pkgStatus) ?>"
data-auto-check="1">Sprawdz status</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/create" novalidate>
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
@@ -399,6 +323,94 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
</section>
</form>
<?php if ($packages !== []): ?>
<section class="card mt-16">
<h3 class="section-title">Utworzone przesylki</h3>
<div class="table-wrap mt-12">
<table class="table table--details">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Nr sledzenia</th>
<th>Przewoznik</th>
<th>Etykieta</th>
<th>Utworzono</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($packages as $pkg): ?>
<?php
$pkgId = (int) ($pkg['id'] ?? 0);
$pkgStatus = (string) ($pkg['status'] ?? 'draft');
$pkgTracking = trim((string) ($pkg['tracking_number'] ?? ''));
$pkgCarrier = trim((string) ($pkg['carrier_id'] ?? ''));
$pkgLabelPath = trim((string) ($pkg['label_path'] ?? ''));
$pkgShipmentId = trim((string) ($pkg['shipment_id'] ?? ''));
$pkgError = trim((string) ($pkg['error_message'] ?? ''));
?>
<tr>
<td><?= $e((string) $pkgId) ?></td>
<td>
<span class="order-tag <?= $pkgStatus === 'label_ready' || $pkgStatus === 'created' ? 'is-success' : ($pkgStatus === 'error' ? 'is-danger' : 'is-warn') ?>">
<?= $e($pkgStatus) ?>
</span>
<?php if ($pkgError !== ''): ?>
<div class="muted mt-4" style="font-size:0.75rem"><?= $e($pkgError) ?></div>
<?php endif; ?>
</td>
<td><?= $e($pkgTracking !== '' ? $pkgTracking : '-') ?></td>
<td><?= $e($pkgCarrier !== '' ? $pkgCarrier : '-') ?></td>
<td>
<?php if ($pkgLabelPath !== ''): ?>
<?php if ($pkgStatus === 'error'): ?>
-
<?php else: ?>
<form method="post" action="/orders/<?= $e((string) ($orderId ?? 0)) ?>/shipment/<?= $e((string) $pkgId) ?>/label" style="display:inline">
<input type="hidden" name="_token" value="<?= $e($csrfToken ?? '') ?>">
<button type="submit" class="btn btn--sm btn--secondary">Pobierz</button>
</form>
<?php endif; ?>
<?php elseif ($pkgShipmentId !== '' && $pkgStatus === 'created'): ?>
<span class="muted">Generowanie etykiety...</span>
<?php else: ?>
-
<?php endif; ?>
<?php if (in_array($pkgStatus, ['label_ready', 'created'], true)): ?>
<?php if (in_array($pkgId, $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) $pkgId) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
title="Wyslij do drukarki">Drukuj</button>
<?php endif; ?>
<?php endif; ?>
</td>
<td class="text-nowrap"><?= $e((string) ($pkg['created_at'] ?? '')) ?></td>
<td>
<?php
$shouldCheckStatus = $pkgStatus === 'pending' || ($pkgStatus === 'created' && $pkgLabelPath === '');
?>
<?php if ($shouldCheckStatus): ?>
<button type="button"
class="btn btn--sm btn--secondary"
data-check-status="<?= $e((string) $pkgId) ?>"
data-order-id="<?= $e((string) ($orderId ?? 0)) ?>"
data-package-status="<?= $e($pkgStatus) ?>"
data-auto-check="1">Sprawdz status</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
<script>
(function () {
function enhanceSelect(selectEl) {
@@ -715,6 +727,47 @@ $defaultCodAmount = $isCod ? number_format($totalWithTax, 2, '.', '') : '0';
});
});
// 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...';
fetch('/api/print/jobs', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: '_token=' + encodeURIComponent(document.querySelector('input[name="_token"]').value)
+ '&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 — sprobuj ponownie.', type: 'error' });
}
btn.innerHTML = originalText;
btn.disabled = false;
});
});
});
var params = new URLSearchParams(window.location.search);
var autoCheckId = params.get('check');