ver. 0.276: ShopOrder migration, Integrations cleanup, global admin search
This commit is contained in:
@@ -270,6 +270,19 @@ $isCompactColumn = function(array $column): bool {
|
||||
</div>
|
||||
|
||||
<style type="text/css">
|
||||
.table-list-table th,
|
||||
.table-list-table td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.table-list-table th.text-right,
|
||||
.table-list-table td.text-right {
|
||||
display: table-cell !important;
|
||||
text-align: right !important;
|
||||
justify-content: initial !important;
|
||||
align-items: initial !important;
|
||||
}
|
||||
|
||||
.table-list-table th:first-child,
|
||||
.table-list-table td:first-child {
|
||||
width: 70px;
|
||||
|
||||
432
admin/templates/shop-order/order-details-custom-script.php
Normal file
432
admin/templates/shop-order/order-details-custom-script.php
Normal file
@@ -0,0 +1,432 @@
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var orderId = <?= (int)($this->order_id ?? 0);?>;
|
||||
var currentTrustmateState = <?= ((int)($this->trustmate_send ?? 0) === 1) ? 'true' : 'false';?>;
|
||||
|
||||
$(function() {
|
||||
var btn = $('#integrationsDropdownBtn');
|
||||
var menu = $('#integrationsDropdownMenu');
|
||||
|
||||
btn.wrap('<div class="dropdown d-inline-block pull-right"></div>');
|
||||
menu.appendTo(btn.parent());
|
||||
|
||||
btn.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
menu.toggleClass('show');
|
||||
});
|
||||
|
||||
$(document).on('click', function(e) {
|
||||
if (!btn.is(e.target) && !menu.is(e.target) && menu.has(e.target).length === 0) {
|
||||
menu.removeClass('show');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(function() {
|
||||
var timer = '';
|
||||
|
||||
$('#notes').keyup(function() {
|
||||
var textarea = $(this);
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/notes_save/',
|
||||
data: {
|
||||
order_id: orderId,
|
||||
notes: textarea.val()
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
$('body').on('click', '.btn-send-order-to-apilo', function(e) {
|
||||
e.preventDefault();
|
||||
var href = $(this).attr('href');
|
||||
$.alert({
|
||||
title: 'Potwierdź',
|
||||
content: 'Czy na pewno chcesz wysłać zamówienie do apilo.com?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('body').on('click', '.set_order_as_unpaid', function() {
|
||||
var href = $(this).attr('href');
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Zmienić zamówienie na nieopłacone?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.set_order_as_paid', function() {
|
||||
var href = $(this).attr('href');
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Zmienić zamówienie na opłacone?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
},
|
||||
confirm2: {
|
||||
text: 'Tak (wyślij mail)',
|
||||
btnClass: 'btn-primary',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href + '&send_mail=1';
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.order_status_change_email', function() {
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wysłać mail o zmianie statusu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
order_status_change(orderId, $('#order-status').val(), true);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.resend_order_confirmation_email button', function() {
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wysłać mail o złożonym zamówieniu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/order_resend_confirmation_email/',
|
||||
data: {
|
||||
order_id: orderId
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#overlay').show();
|
||||
},
|
||||
success: function(response) {
|
||||
$('#overlay').hide();
|
||||
var data = jQuery.parseJSON(response);
|
||||
|
||||
if (data.result === true) {
|
||||
return $.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Wiadomość została wysłana',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
autoClose: 'confirm|10000',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-info',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.result === false) {
|
||||
return $.alert({
|
||||
title: 'Błąd',
|
||||
content: 'Podczas wysyłania wiadomości wystąpił błąd',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.order_status_change', function() {
|
||||
order_status_change(orderId, $('#order-status').val(), false);
|
||||
return false;
|
||||
});
|
||||
|
||||
function order_status_change(order_id, status, email) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/order_status_change/',
|
||||
data: {
|
||||
order_id: order_id,
|
||||
status: status,
|
||||
email: email
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#overlay').show();
|
||||
},
|
||||
success: function(response) {
|
||||
$('#overlay').hide();
|
||||
var data = jQuery.parseJSON(response);
|
||||
|
||||
if (data.email === true) {
|
||||
return $.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Wiadomość o zmiane statusu została wysłana',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
autoClose: 'confirm|10000',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-info',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.email === false) {
|
||||
return $.alert({
|
||||
title: 'Błąd',
|
||||
content: 'Podczas wysyłania wiadomości wystąpił błąd',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.result == true) {
|
||||
return $.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Status zamówienia został zmieniony',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
autoClose: 'confirm|10000',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-info',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('body').on('click', '.btn-toggle-trustmate', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.alert({
|
||||
title: 'Potwierdź',
|
||||
content: currentTrustmateState
|
||||
? 'Czy na pewno chcesz odznaczyć zamówienie jako wysłane do trustmate.io?'
|
||||
: 'Czy na pewno chcesz zaznaczyć zamówienie jako wysłane do trustmate.io?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/toggle_trustmate_send/',
|
||||
data: {
|
||||
order_id: orderId
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#overlay').show();
|
||||
},
|
||||
success: function(response) {
|
||||
$('#overlay').hide();
|
||||
var data = jQuery.parseJSON(response);
|
||||
if (data.result === true) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -1,8 +1,36 @@
|
||||
<?
|
||||
global $db;
|
||||
ob_start();
|
||||
$orderId = (int)($this -> order['id'] ?? 0);
|
||||
?>
|
||||
<div class="details order-details">
|
||||
|
||||
<div class="site-title">Szczegóły zamówienia: <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?></div>
|
||||
|
||||
<div class="mb15">
|
||||
<a href="/admin/shop_order/list/" class="btn btn-dark btn-sm mr5">
|
||||
<i class="fa fa-reply"></i> Wstecz
|
||||
</a>
|
||||
<a href="/admin/shop_order/order_edit/order_id=<?= $orderId;?>" class="btn btn-danger btn-sm mr5">
|
||||
<i class="fa fa-pencil"></i> Edytuj zamówienie
|
||||
</a>
|
||||
|
||||
<? if ( $this -> prev_order_id ):?>
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= (int)$this -> prev_order_id;?>" class="btn btn-success btn-sm mr5">
|
||||
<i class="fa fa-arrow-left"></i> Poprzednie zamówienie
|
||||
</a>
|
||||
<? endif;?>
|
||||
|
||||
<? if ( $this -> next_order_id ):?>
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= (int)$this -> next_order_id;?>" class="btn btn-success btn-sm mr5">
|
||||
<i class="fa fa-arrow-right"></i> Następne zamówienie
|
||||
</a>
|
||||
<? endif;?>
|
||||
|
||||
<button id="integrationsDropdownBtn" type="button" class="btn btn-primary btn-sm pull-right">
|
||||
<i class="fa fa-ellipsis-v"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="details order-details panel">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="row">
|
||||
@@ -66,12 +94,12 @@ ob_start();
|
||||
<div class="paid-status panel">
|
||||
<div class="panel-body">
|
||||
<? if ( $this -> order['paid'] ):?>
|
||||
<a href="/admin/shop_order/set_order_as_unpaid/order_id=<?= $this -> order['id'];?>" class="set_order_as_unpaid">
|
||||
<a href="/admin/shop_order/set_order_as_unpaid/order_id=<?= $orderId;?>" class="set_order_as_unpaid">
|
||||
<span><i class="fa fa-dollar"></i></span>
|
||||
<b>Oznacz zamówienie jako nieopłacone</b>
|
||||
</a>
|
||||
<? else:?>
|
||||
<a href="/admin/shop_order/set_order_as_paid/order_id=<?= $this -> order['id'];?>" class="set_order_as_paid">
|
||||
<a href="/admin/shop_order/set_order_as_paid/order_id=<?= $orderId;?>" class="set_order_as_paid">
|
||||
<span class="danger"><i class="fa fa-dollar"></i></span>
|
||||
<b>Oznacz zamówienie jako opłacone</b>
|
||||
</a>
|
||||
@@ -177,59 +205,11 @@ ob_start();
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?
|
||||
$out = ob_get_clean();
|
||||
|
||||
$grid = new \gridEdit;
|
||||
$grid -> id = 'order-details';
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Szczegóły zamówienia: ' . $this -> order[ 'number' ];
|
||||
$grid -> buttons = [
|
||||
[
|
||||
'label' => 'Wstecz',
|
||||
'url' => '/admin/shop_order/view_list/',
|
||||
'icon' => 'fa-reply',
|
||||
'class' => 'btn btn-dark btn-sm mr5'
|
||||
], [
|
||||
'label' => 'Edytuj zamówienie',
|
||||
'url' => '/admin/shop_order/order_edit/order_id=' . $this -> order['id'],
|
||||
'icon' => 'fa-pencil',
|
||||
'class' => 'btn btn-danger btn-sm mr5 ml5'
|
||||
]
|
||||
];
|
||||
if ( $this -> prev_order_id )
|
||||
{
|
||||
$grid -> buttons[] = [
|
||||
'label' => 'Poprzednie zamówienie',
|
||||
'url' => '/admin/shop_order/order_details/order_id=' . $this -> prev_order_id,
|
||||
'icon' => 'fa-arrow-left',
|
||||
'class' => 'btn btn-success btn-sm mr5 ml5'
|
||||
];
|
||||
}
|
||||
if ( $this -> next_order_id )
|
||||
{
|
||||
$grid -> buttons[] = [
|
||||
'label' => 'Następne zamówienie',
|
||||
'url' => '/admin/shop_order/order_details/order_id=' . $this -> next_order_id,
|
||||
'icon' => 'fa-arrow-right',
|
||||
'class' => 'btn btn-success btn-sm mr5 ml5'
|
||||
];
|
||||
}
|
||||
$grid -> buttons[] = [
|
||||
'label' => '',
|
||||
'url' => '#',
|
||||
'icon' => 'fa-ellipsis-v',
|
||||
'class' => 'btn btn-primary',
|
||||
'id' => 'integrationsDropdownBtn'
|
||||
];
|
||||
$grid -> default_buttons = false;
|
||||
$grid -> external_code = $out;
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<div class="dropdown-menu dropdown-menu-right" id="integrationsDropdownMenu">
|
||||
<a class="dropdown-item btn-send-order-to-apilo" href="/admin/shop_order/send_order_to_apilo/order_id=<?= $this -> order['id'];?>">
|
||||
<a class="dropdown-item btn-send-order-to-apilo" href="/admin/shop_order/send_order_to_apilo/order_id=<?= $orderId;?>">
|
||||
<i class="fa fa-refresh"></i> Wyślij ponownie zamówienie do apilo.com
|
||||
</a>
|
||||
<a class="dropdown-item btn-toggle-trustmate" href="#">
|
||||
@@ -237,452 +217,7 @@ echo $grid -> draw();
|
||||
<?= $this -> order['trustmate_send'] ? 'Odznacz zamówienie jako wysłane do trustmate.io' : 'Zaznacz zamówienie jako wysłane do trustmate.io';?>
|
||||
</a>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$( function() {
|
||||
var btn = $( '#integrationsDropdownBtn' );
|
||||
var menu = $( '#integrationsDropdownMenu' );
|
||||
|
||||
// Opakuj przycisk w dropdown wrapper
|
||||
btn.wrap( '<div class="dropdown d-inline-block pull-right"></div>' );
|
||||
menu.appendTo( btn.parent() );
|
||||
|
||||
// Ręczna obsługa toggle dropdown
|
||||
btn.on( 'click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
menu.toggleClass( 'show' );
|
||||
});
|
||||
|
||||
// Zamknij dropdown po kliknięciu poza nim
|
||||
$( document ).on( 'click', function(e) {
|
||||
if ( !btn.is( e.target ) && !menu.is( e.target ) && menu.has( e.target ).length === 0 ) {
|
||||
menu.removeClass( 'show' );
|
||||
}
|
||||
});
|
||||
});
|
||||
$( function()
|
||||
{
|
||||
var timer = '';
|
||||
$( '#notes' ).keyup( function()
|
||||
{
|
||||
var _this = $( this);
|
||||
clearTimeout( timer );
|
||||
timer = setTimeout( function()
|
||||
{
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/notes_save/',
|
||||
data:
|
||||
{
|
||||
order_id: <?= $this -> order['id'];?>,
|
||||
notes: _this.val()
|
||||
},
|
||||
beforeSend: function()
|
||||
{
|
||||
|
||||
},
|
||||
success: function( response )
|
||||
{
|
||||
var time = 0;
|
||||
}
|
||||
});
|
||||
}, 500 );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.btn-send-order-to-apilo', function(e) {
|
||||
e.preventDefault();
|
||||
var href = $( this ).attr( 'href' );
|
||||
$.alert({
|
||||
title: 'Potwierdź',
|
||||
content: 'Czy na pewno chcesz wysłać zamówienie do apilo.com?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// set_order_as_unpaid
|
||||
$( 'body' ).on( 'click', '.set_order_as_unpaid', function(e) {
|
||||
var href = $( this ).attr( 'href' );
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Zmienić zamówienie na nieopłacone?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// set_order_as_paid
|
||||
$( 'body' ).on( 'click', '.set_order_as_paid', function(e) {
|
||||
var href = $( this ).attr( 'href' );
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Zmienić zamówienie na opłacone?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
},
|
||||
confirm2: {
|
||||
text: 'Tak (wyślij mail)',
|
||||
btnClass: 'btn-primary',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href + '&send_mail=1';
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.order_status_change_email', function() {
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wysłać mail o zmianie statusu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
order_status_change(<?= $this -> order[ 'id' ];?>, $('#order-status').val(), true);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.resend_order_confirmation_email button', function() {
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wysłać mail o złożonym zamówieniu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function()
|
||||
{
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/order_resend_confirmation_email/',
|
||||
data: {
|
||||
order_id: <?= $this -> order['id'];?>
|
||||
},
|
||||
beforeSend: function()
|
||||
{
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function(response)
|
||||
{
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
data = jQuery.parseJSON(response);
|
||||
|
||||
if ( data.result === true )
|
||||
{
|
||||
return $.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Wiadomość została wysłana',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
autoClose: 'confirm|10000',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-info',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ( data.result === false)
|
||||
{
|
||||
return $.alert({
|
||||
title: 'Błąd',
|
||||
content: 'Podczas wysyłania wiadomości wystąpił błąd',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.order_status_change', function() {
|
||||
order_status_change(<?= $this -> order[ 'id' ];?>, $('#order-status').val(), false);
|
||||
return false;
|
||||
});
|
||||
|
||||
function order_status_change($order_id, $status, $email) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/order_status_change/',
|
||||
data: {
|
||||
order_id: $order_id,
|
||||
status: $status,
|
||||
email: $email
|
||||
},
|
||||
beforeSend: function() {
|
||||
$('#overlay').show();
|
||||
},
|
||||
success: function(response) {
|
||||
$('#overlay').hide();
|
||||
|
||||
data = jQuery.parseJSON(response);
|
||||
|
||||
if (data.email === true) {
|
||||
return $.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Wiadomość o zmiane statusu została wysłana',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
autoClose: 'confirm|10000',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-info',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.email === false) {
|
||||
return $.alert({
|
||||
title: 'Błąd',
|
||||
content: 'Podczas wysyłania wiadomości wystąpił błąd',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.result == true) {
|
||||
return $.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Status zamówienia został zmieniony',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-close',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
autoClose: 'confirm|10000',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-info',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Zamknij',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$( 'body' ).on( 'click', '.btn-toggle-trustmate', function(e) {
|
||||
e.preventDefault();
|
||||
var currentState = <?= $this -> order['trustmate_send'] ? 'true' : 'false';?>;
|
||||
|
||||
$.alert({
|
||||
title: 'Potwierdź',
|
||||
content: currentState ? 'Czy na pewno chcesz odznaczyć zamówienie jako wysłane do trustmate.io?' : 'Czy na pewno chcesz zaznaczyć zamówienie jako wysłane do trustmate.io?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_order/toggle_trustmate_send/',
|
||||
data: {
|
||||
order_id: <?= $this -> order['id'];?>
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
var data = jQuery.parseJSON( response );
|
||||
if ( data.result === true ) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= \Tpl::view('shop-order/order-details-custom-script', [
|
||||
'order_id' => $orderId,
|
||||
'trustmate_send' => (int)($this -> order['trustmate_send'] ?? 0),
|
||||
]);?>
|
||||
38
admin/templates/shop-order/order-edit-custom-script.php
Normal file
38
admin/templates/shop-order/order-edit-custom-script.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
function toggleInpostField() {
|
||||
if ($('#transport_id').val() != '2') {
|
||||
$('#inpost_paczkomat').closest('.row').hide();
|
||||
} else {
|
||||
$('#inpost_paczkomat').closest('.row').show();
|
||||
}
|
||||
}
|
||||
|
||||
toggleInpostField();
|
||||
|
||||
$('body').on('change', '#transport_id', function() {
|
||||
toggleInpostField();
|
||||
});
|
||||
|
||||
$('body').on('click', '.btn-paczkomat', function() {
|
||||
window.easyPackAsyncInit = function () {
|
||||
easyPack.init({
|
||||
mapType: 'osm',
|
||||
searchType: 'osm',
|
||||
});
|
||||
|
||||
easyPack.mapWidget('inpost-map', function(point) {
|
||||
$('#inpost_paczkomat').val(point.name + ' | ' + point.address.line1 + ', ' + point.address.line2);
|
||||
$('.inpost-map-container').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$('.inpost-map-container').show();
|
||||
});
|
||||
|
||||
$('body').on('click', '#order-save', function(e) {
|
||||
e.preventDefault();
|
||||
$('#fg-order-details').attr('method', 'POST').attr('action', '/admin/shop_order/order_save/').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -1,9 +1,22 @@
|
||||
<?
|
||||
global $db;
|
||||
ob_start();
|
||||
$orderId = (int)($this -> order['id'] ?? 0);
|
||||
?>
|
||||
<input type="hidden" name="order_id" value="<?= $this -> order['id'];?>">
|
||||
<div class="details">
|
||||
|
||||
<div class="site-title">Edycja zamówienia: <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?></div>
|
||||
|
||||
<div class="mb15">
|
||||
<button type="button" id="order-save" class="btn btn-success btn-sm mr5 ml5">
|
||||
<i class="fa fa-save"></i> Zapisz zamówienie
|
||||
</button>
|
||||
<a href="/admin/shop_order/order_details/order_id=<?= $orderId;?>" class="btn btn-dark btn-sm mr5">
|
||||
<i class="fa fa-reply"></i> Wstecz
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form id="fg-order-details" method="POST" action="/admin/shop_order/order_save/">
|
||||
<input type="hidden" name="order_id" value="<?= $orderId;?>">
|
||||
<div class="details panel">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xl-8">
|
||||
<div class="row">
|
||||
@@ -199,71 +212,14 @@ ob_start();
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?
|
||||
$out = ob_get_clean();
|
||||
</form>
|
||||
|
||||
$grid = new \gridEdit;
|
||||
$grid -> id = 'order-details';
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Szczegóły zamówienia: ' . $this -> order[ 'number' ];
|
||||
$grid -> buttons = [
|
||||
[
|
||||
'label' => 'Zapisz zamówienie',
|
||||
'icon' => 'fa-save',
|
||||
'class' => 'btn btn-success btn-sm mr5 ml5',
|
||||
'id' => 'order-save'
|
||||
],
|
||||
[
|
||||
'label' => 'Wstecz',
|
||||
'url' => '/admin/shop_order/order_details/order_id=' . $this -> order['id'],
|
||||
'icon' => 'fa-reply',
|
||||
'class' => 'btn btn-dark btn-sm mr5'
|
||||
]
|
||||
];
|
||||
$grid -> default_buttons = false;
|
||||
$grid -> external_code = $out;
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<div class="inpost-map-container">
|
||||
<a href="#" onclick="$( '.inpost-map-container' ).hide(); return false;" class="inpost-hide"><?= \S::lang( 'zamknij' );?></a>
|
||||
<div id="inpost-map"></div>
|
||||
</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">
|
||||
$( function()
|
||||
{
|
||||
$( 'body' ).on( 'change', '#transport_id', function()
|
||||
{
|
||||
if ( $( this ).val() != '2' )
|
||||
$( '#inpost_paczkomat' ).closest( '.row' ).hide();
|
||||
else
|
||||
$( '#inpost_paczkomat' ).closest( '.row' ).show();
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.btn-paczkomat', function()
|
||||
{
|
||||
window.easyPackAsyncInit = function () {
|
||||
easyPack.init({
|
||||
mapType: 'osm',
|
||||
searchType: 'osm',
|
||||
});
|
||||
|
||||
var map = easyPack.mapWidget( 'inpost-map', function(point)
|
||||
{
|
||||
$( '#inpost_paczkomat' ).val( point.name + ' | ' + point.address.line1 + ', ' + point.address.line2 );
|
||||
$( '.inpost-map-container' ).hide();
|
||||
});
|
||||
};
|
||||
$( '.inpost-map-container' ).show();
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '#order-save', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
$( '#fg-order-details' ).attr( 'method', 'POST' ).attr( 'action', '/admin/shop_order/order_save/' ).submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= \Tpl::view('shop-order/order-edit-custom-script');?>
|
||||
2
admin/templates/shop-order/orders-list.php
Normal file
2
admin/templates/shop-order/orders-list.php
Normal file
@@ -0,0 +1,2 @@
|
||||
<div class="site-title">Lista zamówień</div>
|
||||
<?= \Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
|
||||
@@ -1,147 +0,0 @@
|
||||
<?php
|
||||
global $gdb;
|
||||
|
||||
$grid = new \grid( 'pp_shop_order' );
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid->sql = '
|
||||
SELECT
|
||||
q1.*,
|
||||
shop_order.total_orders
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
number,
|
||||
date_order,
|
||||
CONCAT(client_name, \' \', client_surname) AS client,
|
||||
client_email AS order_email,
|
||||
CONCAT(client_street, \', \', client_postal_code, \' \', client_city) AS address,
|
||||
status,
|
||||
client_phone,
|
||||
transport,
|
||||
payment_method,
|
||||
summary,
|
||||
paid
|
||||
FROM
|
||||
pp_shop_orders AS pso
|
||||
) AS q1
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
client_email,
|
||||
COUNT(*) AS total_orders
|
||||
FROM
|
||||
pp_shop_orders
|
||||
WHERE
|
||||
client_name IS NOT NULL AND client_surname IS NOT NULL AND client_email IS NOT NULL
|
||||
GROUP BY
|
||||
client_email
|
||||
) AS shop_order ON q1.order_email = shop_order.client_email
|
||||
WHERE
|
||||
1=1 [where]
|
||||
ORDER BY
|
||||
[order_p1] [order_p2]
|
||||
';
|
||||
|
||||
$grid->sql_count = '
|
||||
SELECT COUNT(0)
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
number,
|
||||
date_order,
|
||||
CONCAT(client_name, \' \', client_surname) AS client,
|
||||
client_email AS order_email,
|
||||
CONCAT(client_street, \', \', client_postal_code, \' \', client_city) AS address,
|
||||
status,
|
||||
client_phone,
|
||||
transport,
|
||||
payment_method,
|
||||
summary,
|
||||
paid
|
||||
FROM
|
||||
pp_shop_orders AS pso
|
||||
) AS q1
|
||||
WHERE
|
||||
1=1 [where]
|
||||
';
|
||||
|
||||
$grid -> debug = true;
|
||||
$grid -> order = [ 'column' => 'date_order', 'type' => 'DESC' ];
|
||||
$grid -> search = [
|
||||
[ 'name' => 'Nr zamówienia', 'db' => 'number', 'type' => 'text' ],
|
||||
[ 'name' => 'Data zamówienia', 'db' => 'date_order', 'type' => 'date_range' ],
|
||||
[ 'name' => 'Status', 'db' => 'status', 'type' => 'select', 'replace' => [ 'array' => \shop\Order::order_statuses() ] ],
|
||||
[ 'name' => 'Klient', 'db' => 'client', 'type' => 'text' ],
|
||||
[ 'name' => 'Adres', 'db' => 'address', 'type' => 'text' ],
|
||||
[ 'name' => 'Email', 'db' => 'order_email', 'type' => 'text' ],
|
||||
[ 'name' => 'Telefon', 'db' => 'client_phone', 'type' => 'text' ],
|
||||
[ 'name' => 'Dostawa', 'db' => 'transport', 'type' => 'text' ],
|
||||
[ 'name' => 'Płatność', 'db' => 'payment_method', 'type' => 'text' ]
|
||||
];
|
||||
$grid -> columns_view = [
|
||||
[
|
||||
'name' => 'Lp.',
|
||||
'th' => [ 'class' => 'g-lp' ],
|
||||
'td' => [ 'class' => 'g-center' ],
|
||||
'autoincrement' => true
|
||||
], [
|
||||
'name' => 'Data dodania',
|
||||
'db' => 'date_order',
|
||||
'td' => [ 'class' => 'g-center' ],
|
||||
'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ],
|
||||
'php' => 'echo date( "Y-m-d H:i", strtotime( "[date_order]" ) );',
|
||||
'sort' => true
|
||||
], [
|
||||
'name' => 'Nr zamówienia',
|
||||
'db' => 'number',
|
||||
'sort' => true,
|
||||
'td' => [ 'class' => 'g-center' ],
|
||||
'th' => [ 'class' => 'g-center', 'style' => 'width: 150px;' ],
|
||||
'php' => 'echo "<a href=\'/admin/shop_order/order_details/order_id=[id]\'>" . htmlspecialchars( \'[number]\' ) . "</a>";'
|
||||
], [
|
||||
'name' => '',
|
||||
'db' => 'paid',
|
||||
'sort' => true,
|
||||
'td' => [ 'class' => 'g-center' ],
|
||||
'th' => [ 'class' => 'g-center', 'style' => 'width: 25px;' ],
|
||||
'php' => 'if ( [paid] === 1 ) echo "<i class=\'fa fa-check text-success\'></i>"; else echo "<i class=\'fa fa-times text-dark\'></i>";'
|
||||
], [
|
||||
'name' => 'Status',
|
||||
'db' => 'status',
|
||||
'sort' => true,
|
||||
'th' => [ 'style' => 'width: 250px;' ],
|
||||
'td' => [ 'class' => 'order-status' ],
|
||||
'replace' => [ 'array' => \shop\Order::order_statuses() ]
|
||||
], [
|
||||
'name' => 'Wartość',
|
||||
'db' => 'summary',
|
||||
'sort' => true,
|
||||
'td' => [ 'class' => 'g-right' ],
|
||||
'th' => [ 'class' => 'g-right', 'style' => 'width: 90px;' ],
|
||||
'php' => 'echo "[summary] zł"; echo "<script>$( \"tr#[id]\" ).addClass( \"status-[status]\" );</script>";'
|
||||
], [
|
||||
'name' => 'Klient',
|
||||
'db' => 'client',
|
||||
'php' => 'echo "[client] | zamówienia: <strong>[total_orders]</strong>";'
|
||||
], [
|
||||
'name' => 'Adres',
|
||||
'db' => 'address',
|
||||
], [
|
||||
'name' => 'Email',
|
||||
'db' => 'order_email',
|
||||
], [
|
||||
'name' => 'Telefon',
|
||||
'db' => 'client_phone',
|
||||
], [
|
||||
'name' => 'Dostawa',
|
||||
'db' => 'transport',
|
||||
], [
|
||||
'name' => 'Płatność',
|
||||
'db' => 'payment_method',
|
||||
], [
|
||||
'name' => 'Usuń',
|
||||
'action' => [ 'type' => 'delete', 'url' => '/admin/shop_order/order_delete/id=[id]' ],
|
||||
'th' => [ 'class' => 'g-center', 'style' => 'width: 70px;' ],
|
||||
'td' => [ 'class' => 'g-center' ]
|
||||
]
|
||||
];
|
||||
echo $grid -> draw();
|
||||
@@ -54,7 +54,7 @@
|
||||
Sklep
|
||||
</div>
|
||||
<ul>
|
||||
<li> <a href="/admin/shop_order/view_list/"><img src="/admin/layout/icon/icon-menu/shopping-cart.svg">Zamówienia</a></li>
|
||||
<li> <a href="/admin/shop_order/list/"><img src="/admin/layout/icon/icon-menu/shopping-cart.svg">Zamówienia</a></li>
|
||||
<li> <a href="/admin/shop_clients/list/"><img src="/admin/layout/icon/icon-menu/people-fill.svg">Klienci</a></li>
|
||||
<li><a href="/admin/shop_category/list/"><img src="/admin/layout/icon/icon-menu/bxs-category-alt.svg">Kategorie</a></li>
|
||||
<li><a href="/admin/shop_product/view_list/"><img src="/admin/layout/icon/icon-menu/shopping-basket.svg">Produkty</a></li>
|
||||
@@ -159,7 +159,19 @@
|
||||
<div class="col-12 col-md-3 col-lg-2">
|
||||
<button id="clear-cache-btn" class="btn btn-danger mt-3">Wyczyść cache</button>
|
||||
</div>
|
||||
<div class="col-12 col-md-9 col-lg-10 top-user">
|
||||
<div class="col-12 col-md-6 col-lg-7 mt-3">
|
||||
<div class="admin-global-search" id="admin-global-search-wrap">
|
||||
<input
|
||||
type="text"
|
||||
id="admin-global-search-input"
|
||||
class="form-control"
|
||||
placeholder="Szukaj produktu (EAN, Nazwa, SKU) lub zamówienia (email, imię, nazwisko, telefon, numer)"
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="admin-global-search-results" id="admin-global-search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3 col-lg-3 top-user">
|
||||
<div class="dropdown">
|
||||
<?
|
||||
if ( $user[ 'name' ] or $user[ 'surname' ] )
|
||||
@@ -246,6 +258,101 @@
|
||||
bindClearCacheButton();
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var $input = $('#admin-global-search-input');
|
||||
var $results = $('#admin-global-search-results');
|
||||
var $wrap = $('#admin-global-search-wrap');
|
||||
var timer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
return $('<div>').text(value || '').html();
|
||||
}
|
||||
|
||||
function hideResults() {
|
||||
$results.removeClass('open').empty();
|
||||
}
|
||||
|
||||
function renderResults(items) {
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
$results
|
||||
.html('<div class="admin-global-search-empty">Brak wyników</div>')
|
||||
.addClass('open');
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
items.forEach(function(item) {
|
||||
var title = escapeHtml(item.title || '');
|
||||
var subtitle = escapeHtml(item.subtitle || '');
|
||||
var type = item.type === 'order' ? 'Zamówienie' : 'Produkt';
|
||||
var url = escapeHtml(item.url || '#');
|
||||
|
||||
html += ''
|
||||
+ '<a class="admin-global-search-item" href="' + url + '">'
|
||||
+ ' <div class="admin-global-search-item-title">' + title + '</div>'
|
||||
+ ' <div class="admin-global-search-item-subtitle">' + escapeHtml(type) + (subtitle ? ' | ' + subtitle : '') + '</div>'
|
||||
+ '</a>';
|
||||
});
|
||||
|
||||
$results.html(html).addClass('open');
|
||||
}
|
||||
|
||||
function searchNow() {
|
||||
var phrase = ($input.val() || '').trim();
|
||||
if (phrase.length < 2) {
|
||||
hideResults();
|
||||
return;
|
||||
}
|
||||
|
||||
$results.html('<div class="admin-global-search-empty">Szukam...</div>').addClass('open');
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/settings/globalSearchAjax/',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: { q: phrase },
|
||||
success: function(response) {
|
||||
if (!response || response.status !== 'ok') {
|
||||
$results.html('<div class="admin-global-search-empty">Wystąpił błąd wyszukiwania</div>').addClass('open');
|
||||
return;
|
||||
}
|
||||
|
||||
renderResults(response.items || []);
|
||||
},
|
||||
error: function() {
|
||||
$results.html('<div class="admin-global-search-empty">Błąd połączenia</div>').addClass('open');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document)
|
||||
.off('input.adminGlobalSearch', '#admin-global-search-input')
|
||||
.on('input.adminGlobalSearch', '#admin-global-search-input', function() {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(searchNow, 250);
|
||||
});
|
||||
|
||||
$(document)
|
||||
.off('focus.adminGlobalSearch', '#admin-global-search-input')
|
||||
.on('focus.adminGlobalSearch', '#admin-global-search-input', function() {
|
||||
if (($input.val() || '').trim().length >= 2 && $results.children().length > 0) {
|
||||
$results.addClass('open');
|
||||
}
|
||||
});
|
||||
|
||||
$(document)
|
||||
.off('click.adminGlobalSearch')
|
||||
.on('click.adminGlobalSearch', function(e) {
|
||||
if ($wrap.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(e.target).closest('#admin-global-search-wrap').length === 0) {
|
||||
hideResults();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
$(document).ready(function () {
|
||||
var user_agent = navigator.userAgent.toLowerCase();
|
||||
var click_event = user_agent.match(/(iphone|ipod|ipad)/) ? "touchend" : "click";
|
||||
@@ -269,5 +376,62 @@
|
||||
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
.admin-global-search {
|
||||
position: relative;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.admin-global-search-results {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: calc(100% + 4px);
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 4px;
|
||||
max-height: 420px;
|
||||
overflow: auto;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.admin-global-search-results.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.admin-global-search-item {
|
||||
display: block;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.admin-global-search-item:hover,
|
||||
.admin-global-search-item:focus {
|
||||
background: #f7f9fc;
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.admin-global-search-item-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.admin-global-search-item-subtitle {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.admin-global-search-empty {
|
||||
padding: 10px 12px;
|
||||
color: #6c757d;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
207
autoload/Domain/Order/OrderAdminService.php
Normal file
207
autoload/Domain/Order/OrderAdminService.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
namespace Domain\Order;
|
||||
|
||||
class OrderAdminService
|
||||
{
|
||||
private OrderRepository $orders;
|
||||
|
||||
public function __construct(OrderRepository $orders)
|
||||
{
|
||||
$this->orders = $orders;
|
||||
}
|
||||
|
||||
public function details(int $orderId): array
|
||||
{
|
||||
return $this->orders->findForAdmin($orderId);
|
||||
}
|
||||
|
||||
public function statuses(): array
|
||||
{
|
||||
return $this->orders->orderStatuses();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{items: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
public function listForAdmin(
|
||||
array $filters,
|
||||
string $sortColumn = 'date_order',
|
||||
string $sortDir = 'DESC',
|
||||
int $page = 1,
|
||||
int $perPage = 15
|
||||
): array {
|
||||
return $this->orders->listForAdmin($filters, $sortColumn, $sortDir, $page, $perPage);
|
||||
}
|
||||
|
||||
public function nextOrderId(int $orderId): ?int
|
||||
{
|
||||
return $this->orders->nextOrderId($orderId);
|
||||
}
|
||||
|
||||
public function prevOrderId(int $orderId): ?int
|
||||
{
|
||||
return $this->orders->prevOrderId($orderId);
|
||||
}
|
||||
|
||||
public function saveNotes(int $orderId, string $notes): bool
|
||||
{
|
||||
return $this->orders->saveNotes($orderId, $notes);
|
||||
}
|
||||
|
||||
public function saveOrderByAdmin(array $input): bool
|
||||
{
|
||||
$saved = $this->orders->saveOrderByAdmin(
|
||||
(int)($input['order_id'] ?? 0),
|
||||
(string)($input['client_name'] ?? ''),
|
||||
(string)($input['client_surname'] ?? ''),
|
||||
(string)($input['client_street'] ?? ''),
|
||||
(string)($input['client_postal_code'] ?? ''),
|
||||
(string)($input['client_city'] ?? ''),
|
||||
(string)($input['client_email'] ?? ''),
|
||||
(string)($input['firm_name'] ?? ''),
|
||||
(string)($input['firm_street'] ?? ''),
|
||||
(string)($input['firm_postal_code'] ?? ''),
|
||||
(string)($input['firm_city'] ?? ''),
|
||||
(string)($input['firm_nip'] ?? ''),
|
||||
(int)($input['transport_id'] ?? 0),
|
||||
(string)($input['inpost_paczkomat'] ?? ''),
|
||||
(int)($input['payment_method_id'] ?? 0)
|
||||
);
|
||||
|
||||
if ($saved && isset($GLOBALS['user']['id'])) {
|
||||
\Log::save_log('Zamówienie zmienione przez administratora | ID: ' . (int)($input['order_id'] ?? 0), (int)$GLOBALS['user']['id']);
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
public function changeStatus(int $orderId, int $status, bool $sendEmail): array
|
||||
{
|
||||
$order = new \shop\Order($orderId);
|
||||
$response = $order->update_status($status, $sendEmail ? 1 : 0);
|
||||
|
||||
return is_array($response) ? $response : ['result' => false];
|
||||
}
|
||||
|
||||
public function resendConfirmationEmail(int $orderId): bool
|
||||
{
|
||||
$order = new \shop\Order($orderId);
|
||||
|
||||
return (bool)$order->order_resend_confirmation_email();
|
||||
}
|
||||
|
||||
public function setOrderAsUnpaid(int $orderId): bool
|
||||
{
|
||||
$order = new \shop\Order($orderId);
|
||||
|
||||
return (bool)$order->set_as_unpaid();
|
||||
}
|
||||
|
||||
public function setOrderAsPaid(int $orderId, bool $sendMail): bool
|
||||
{
|
||||
$order = new \shop\Order($orderId);
|
||||
if (!$order->set_as_paid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order->update_status(4, $sendMail ? 1 : 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function sendOrderToApilo(int $orderId): bool
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ($orderId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = $this->orders->findForAdmin($orderId);
|
||||
if (empty($order) || empty($order['apilo_order_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
$accessToken = $integrationsRepository -> apiloGetAccessToken();
|
||||
if (!$accessToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newStatus = 8;
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, 'https://projectpro.apilo.com/rest/api/orders/' . $order['apilo_order_id'] . '/status/');
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||
'id' => (int)$order['apilo_order_id'],
|
||||
'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id($newStatus),
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Accept: application/json',
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$apiloResultRaw = curl_exec($ch);
|
||||
$apiloResult = json_decode((string)$apiloResultRaw, true);
|
||||
|
||||
if (!is_array($apiloResult) || (int)($apiloResult['updates'] ?? 0) !== 1) {
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_orders' AND COLUMN_NAME != 'id'";
|
||||
$columns = $mdb->query($query)->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$columnsList = implode(', ', $columns);
|
||||
$mdb->query('INSERT INTO pp_shop_orders (' . $columnsList . ') SELECT ' . $columnsList . ' FROM pp_shop_orders pso WHERE pso.id = ' . $orderId);
|
||||
$newOrderId = (int)$mdb->id();
|
||||
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_order_products' AND COLUMN_NAME != 'id' AND COLUMN_NAME != 'order_id'";
|
||||
$columns = $mdb->query($query)->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$columnsList = implode(', ', $columns);
|
||||
$mdb->query('INSERT INTO pp_shop_order_products (order_id, ' . $columnsList . ') SELECT ' . $newOrderId . ', ' . $columnsList . ' FROM pp_shop_order_products psop WHERE psop.order_id = ' . $orderId);
|
||||
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_order_statuses' AND COLUMN_NAME != 'id' AND COLUMN_NAME != 'order_id'";
|
||||
$columns = $mdb->query($query)->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$columnsList = implode(', ', $columns);
|
||||
$mdb->query('INSERT INTO pp_shop_order_statuses (order_id, ' . $columnsList . ') SELECT ' . $newOrderId . ', ' . $columnsList . ' FROM pp_shop_order_statuses psos WHERE psos.order_id = ' . $orderId);
|
||||
|
||||
$mdb->delete('pp_shop_orders', ['id' => $orderId]);
|
||||
$mdb->delete('pp_shop_order_products', ['order_id' => $orderId]);
|
||||
$mdb->delete('pp_shop_order_statuses', ['order_id' => $orderId]);
|
||||
|
||||
$mdb->update('pp_shop_orders', ['apilo_order_id' => null], ['id' => $newOrderId]);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function toggleTrustmateSend(int $orderId): array
|
||||
{
|
||||
$newValue = $this->orders->toggleTrustmateSend($orderId);
|
||||
if ($newValue === null) {
|
||||
return [
|
||||
'result' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'trustmate_send' => $newValue,
|
||||
];
|
||||
}
|
||||
|
||||
public function deleteOrder(int $orderId): bool
|
||||
{
|
||||
$deleted = $this->orders->deleteOrder($orderId);
|
||||
if ($deleted && isset($GLOBALS['user']['id'])) {
|
||||
\Log::save_log('Usunięcie zamówienia | ID: ' . $orderId, (int)$GLOBALS['user']['id']);
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
}
|
||||
472
autoload/Domain/Order/OrderRepository.php
Normal file
472
autoload/Domain/Order/OrderRepository.php
Normal file
@@ -0,0 +1,472 @@
|
||||
<?php
|
||||
namespace Domain\Order;
|
||||
|
||||
class OrderRepository
|
||||
{
|
||||
private const MAX_PER_PAGE = 100;
|
||||
|
||||
private $db;
|
||||
|
||||
public function __construct($db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{items: array<int, array<string, mixed>>, total: int}
|
||||
*/
|
||||
public function listForAdmin(
|
||||
array $filters,
|
||||
string $sortColumn = 'date_order',
|
||||
string $sortDir = 'DESC',
|
||||
int $page = 1,
|
||||
int $perPage = 15
|
||||
): array {
|
||||
$allowedSortColumns = [
|
||||
'id' => 'q1.id',
|
||||
'number' => 'q1.number',
|
||||
'date_order' => 'q1.date_order',
|
||||
'status' => 'q1.status',
|
||||
'summary' => 'q1.summary',
|
||||
'client' => 'q1.client',
|
||||
'order_email' => 'q1.order_email',
|
||||
'client_phone' => 'q1.client_phone',
|
||||
'transport' => 'q1.transport',
|
||||
'payment_method' => 'q1.payment_method',
|
||||
'total_orders' => 'shop_order.total_orders',
|
||||
'paid' => 'q1.paid',
|
||||
];
|
||||
|
||||
$sortSql = $allowedSortColumns[$sortColumn] ?? 'q1.date_order';
|
||||
$sortDir = strtoupper(trim($sortDir)) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$page = max(1, $page);
|
||||
$perPage = min(self::MAX_PER_PAGE, max(1, $perPage));
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
$number = $this->normalizeTextFilter($filters['number'] ?? '');
|
||||
if ($number !== '') {
|
||||
$where[] = 'q1.number LIKE :number';
|
||||
$params[':number'] = '%' . $number . '%';
|
||||
}
|
||||
|
||||
$dateFrom = $this->normalizeDateFilter($filters['date_from'] ?? '');
|
||||
if ($dateFrom !== null) {
|
||||
$where[] = 'q1.date_order >= :date_from';
|
||||
$params[':date_from'] = $dateFrom . ' 00:00:00';
|
||||
}
|
||||
|
||||
$dateTo = $this->normalizeDateFilter($filters['date_to'] ?? '');
|
||||
if ($dateTo !== null) {
|
||||
$where[] = 'q1.date_order <= :date_to';
|
||||
$params[':date_to'] = $dateTo . ' 23:59:59';
|
||||
}
|
||||
|
||||
$status = trim((string)($filters['status'] ?? ''));
|
||||
if ($status !== '' && is_numeric($status)) {
|
||||
$where[] = 'q1.status = :status';
|
||||
$params[':status'] = (int)$status;
|
||||
}
|
||||
|
||||
$client = $this->normalizeTextFilter($filters['client'] ?? '');
|
||||
if ($client !== '') {
|
||||
$where[] = 'q1.client LIKE :client';
|
||||
$params[':client'] = '%' . $client . '%';
|
||||
}
|
||||
|
||||
$address = $this->normalizeTextFilter($filters['address'] ?? '');
|
||||
if ($address !== '') {
|
||||
$where[] = 'q1.address LIKE :address';
|
||||
$params[':address'] = '%' . $address . '%';
|
||||
}
|
||||
|
||||
$email = $this->normalizeTextFilter($filters['order_email'] ?? '');
|
||||
if ($email !== '') {
|
||||
$where[] = 'q1.order_email LIKE :order_email';
|
||||
$params[':order_email'] = '%' . $email . '%';
|
||||
}
|
||||
|
||||
$phone = $this->normalizeTextFilter($filters['client_phone'] ?? '');
|
||||
if ($phone !== '') {
|
||||
$where[] = 'q1.client_phone LIKE :client_phone';
|
||||
$params[':client_phone'] = '%' . $phone . '%';
|
||||
}
|
||||
|
||||
$transport = $this->normalizeTextFilter($filters['transport'] ?? '');
|
||||
if ($transport !== '') {
|
||||
$where[] = 'q1.transport LIKE :transport';
|
||||
$params[':transport'] = '%' . $transport . '%';
|
||||
}
|
||||
|
||||
$payment = $this->normalizeTextFilter($filters['payment_method'] ?? '');
|
||||
if ($payment !== '') {
|
||||
$where[] = 'q1.payment_method LIKE :payment_method';
|
||||
$params[':payment_method'] = '%' . $payment . '%';
|
||||
}
|
||||
|
||||
$whereSql = '';
|
||||
if (!empty($where)) {
|
||||
$whereSql = ' WHERE ' . implode(' AND ', $where);
|
||||
}
|
||||
|
||||
$baseSql = "
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
number,
|
||||
date_order,
|
||||
CONCAT(client_name, ' ', client_surname) AS client,
|
||||
client_email AS order_email,
|
||||
CONCAT(client_street, ', ', client_postal_code, ' ', client_city) AS address,
|
||||
status,
|
||||
client_phone,
|
||||
transport,
|
||||
payment_method,
|
||||
summary,
|
||||
paid
|
||||
FROM pp_shop_orders AS pso
|
||||
) AS q1
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
client_email,
|
||||
COUNT(*) AS total_orders
|
||||
FROM pp_shop_orders
|
||||
WHERE client_name IS NOT NULL AND client_surname IS NOT NULL AND client_email IS NOT NULL
|
||||
GROUP BY client_email
|
||||
) AS shop_order ON q1.order_email = shop_order.client_email
|
||||
";
|
||||
|
||||
$sqlCount = 'SELECT COUNT(0) ' . $baseSql . $whereSql;
|
||||
$stmtCount = $this->db->query($sqlCount, $params);
|
||||
$countRows = $stmtCount ? $stmtCount->fetchAll() : [];
|
||||
$total = 0;
|
||||
if (is_array($countRows) && isset($countRows[0]) && is_array($countRows[0])) {
|
||||
$firstRow = $countRows[0];
|
||||
$firstValue = reset($firstRow);
|
||||
$total = $firstValue !== false ? (int)$firstValue : 0;
|
||||
}
|
||||
|
||||
$sql = '
|
||||
SELECT
|
||||
q1.*,
|
||||
COALESCE(shop_order.total_orders, 0) AS total_orders
|
||||
'
|
||||
. $baseSql
|
||||
. $whereSql
|
||||
. ' ORDER BY ' . $sortSql . ' ' . $sortDir . ', q1.id DESC'
|
||||
. ' LIMIT ' . $perPage . ' OFFSET ' . $offset;
|
||||
|
||||
$stmt = $this->db->query($sql, $params);
|
||||
if (!$stmt) {
|
||||
return [
|
||||
'items' => [],
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
$items = $stmt ? $stmt->fetchAll() : [];
|
||||
if (!is_array($items)) {
|
||||
$items = [];
|
||||
}
|
||||
|
||||
foreach ($items as &$item) {
|
||||
$item['id'] = (int)($item['id'] ?? 0);
|
||||
$item['status'] = (int)($item['status'] ?? 0);
|
||||
$item['paid'] = (int)($item['paid'] ?? 0);
|
||||
$item['summary'] = (float)($item['summary'] ?? 0);
|
||||
$item['total_orders'] = (int)($item['total_orders'] ?? 0);
|
||||
$item['number'] = (string)($item['number'] ?? '');
|
||||
$item['date_order'] = (string)($item['date_order'] ?? '');
|
||||
$item['client'] = trim((string)($item['client'] ?? ''));
|
||||
$item['order_email'] = (string)($item['order_email'] ?? '');
|
||||
$item['address'] = trim((string)($item['address'] ?? ''));
|
||||
$item['client_phone'] = (string)($item['client_phone'] ?? '');
|
||||
$item['transport'] = (string)($item['transport'] ?? '');
|
||||
$item['payment_method'] = (string)($item['payment_method'] ?? '');
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
public function findForAdmin(int $orderId): array
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$order = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]);
|
||||
if (!is_array($order)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$order['id'] = (int)($order['id'] ?? 0);
|
||||
$order['status'] = (int)($order['status'] ?? 0);
|
||||
$order['paid'] = (int)($order['paid'] ?? 0);
|
||||
$order['summary'] = (float)($order['summary'] ?? 0);
|
||||
$order['transport_cost'] = (float)($order['transport_cost'] ?? 0);
|
||||
$order['products'] = $this->orderProducts($orderId);
|
||||
$order['statuses'] = $this->orderStatusHistory($orderId);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
public function orderProducts(int $orderId): array
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = $this->db->select('pp_shop_order_products', '*', [
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
public function orderStatusHistory(int $orderId): array
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = $this->db->select('pp_shop_order_statuses', '*', [
|
||||
'order_id' => $orderId,
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
]);
|
||||
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
public function orderStatuses(): array
|
||||
{
|
||||
$rows = $this->db->select('pp_shop_statuses', ['id', 'status'], [
|
||||
'ORDER' => ['o' => 'ASC'],
|
||||
]);
|
||||
|
||||
if (!is_array($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$id = (int)($row['id'] ?? 0);
|
||||
if ($id < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$id] = (string)($row['status'] ?? '');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function nextOrderId(int $orderId): ?int
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$next = $this->db->get('pp_shop_orders', 'id', [
|
||||
'id[>]' => $orderId,
|
||||
'ORDER' => ['id' => 'ASC'],
|
||||
'LIMIT' => 1,
|
||||
]);
|
||||
|
||||
if (!$next) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$next;
|
||||
}
|
||||
|
||||
public function prevOrderId(int $orderId): ?int
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prev = $this->db->get('pp_shop_orders', 'id', [
|
||||
'id[<]' => $orderId,
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
'LIMIT' => 1,
|
||||
]);
|
||||
|
||||
if (!$prev) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$prev;
|
||||
}
|
||||
|
||||
public function saveNotes(int $orderId, string $notes): bool
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->db->update('pp_shop_orders', ['notes' => $notes], ['id' => $orderId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function saveOrderByAdmin(
|
||||
int $orderId,
|
||||
string $clientName,
|
||||
string $clientSurname,
|
||||
string $clientStreet,
|
||||
string $clientPostalCode,
|
||||
string $clientCity,
|
||||
string $clientEmail,
|
||||
string $firmName,
|
||||
string $firmStreet,
|
||||
string $firmPostalCode,
|
||||
string $firmCity,
|
||||
string $firmNip,
|
||||
int $transportId,
|
||||
string $inpostPaczkomat,
|
||||
int $paymentMethodId
|
||||
): bool {
|
||||
if ($orderId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transportName = $this->db->get('pp_shop_transports', 'name_visible', ['id' => $transportId]);
|
||||
$transportCost = $this->db->get('pp_shop_transports', 'cost', ['id' => $transportId]);
|
||||
$transportDescription = $this->db->get('pp_shop_transports', 'description', ['id' => $transportId]);
|
||||
$paymentMethodName = $this->db->get('pp_shop_payment_methods', 'name', ['id' => $paymentMethodId]);
|
||||
|
||||
$this->db->update('pp_shop_orders', [
|
||||
'client_name' => $clientName,
|
||||
'client_surname' => $clientSurname,
|
||||
'client_street' => $clientStreet,
|
||||
'client_postal_code' => $clientPostalCode,
|
||||
'client_city' => $clientCity,
|
||||
'client_email' => $clientEmail,
|
||||
'firm_name' => $this->nullableString($firmName),
|
||||
'firm_street' => $this->nullableString($firmStreet),
|
||||
'firm_postal_code' => $this->nullableString($firmPostalCode),
|
||||
'firm_city' => $this->nullableString($firmCity),
|
||||
'firm_nip' => $this->nullableString($firmNip),
|
||||
'transport_id' => $transportId,
|
||||
'transport' => $transportName ?: null,
|
||||
'transport_cost' => $transportCost !== null ? $transportCost : 0,
|
||||
'transport_description' => $transportDescription ?: null,
|
||||
'inpost_paczkomat' => $inpostPaczkomat,
|
||||
'payment_method_id' => $paymentMethodId,
|
||||
'payment_method' => $paymentMethodName ?: null,
|
||||
], [
|
||||
'id' => $orderId,
|
||||
]);
|
||||
|
||||
$this->db->update('pp_shop_orders', [
|
||||
'summary' => $this->calculateOrderSummaryByAdmin($orderId),
|
||||
], [
|
||||
'id' => $orderId,
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function calculateOrderSummaryByAdmin(int $orderId): float
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$rows = $this->db->select('pp_shop_order_products', [
|
||||
'price_brutto',
|
||||
'price_brutto_promo',
|
||||
'quantity',
|
||||
], [
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
|
||||
$summary = 0.0;
|
||||
if (is_array($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$quantity = (float)($row['quantity'] ?? 0);
|
||||
$pricePromo = (float)($row['price_brutto_promo'] ?? 0);
|
||||
$price = (float)($row['price_brutto'] ?? 0);
|
||||
|
||||
if ($pricePromo > 0) {
|
||||
$summary += $pricePromo * $quantity;
|
||||
} else {
|
||||
$summary += $price * $quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$transportCost = (float)$this->db->get('pp_shop_orders', 'transport_cost', ['id' => $orderId]);
|
||||
|
||||
return (float)$summary + $transportCost;
|
||||
}
|
||||
|
||||
public function toggleTrustmateSend(int $orderId): ?int
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$order = $this->db->get('pp_shop_orders', ['trustmate_send'], ['id' => $orderId]);
|
||||
if (!is_array($order)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newValue = ((int)($order['trustmate_send'] ?? 0) === 1) ? 0 : 1;
|
||||
$this->db->update('pp_shop_orders', ['trustmate_send' => $newValue], ['id' => $orderId]);
|
||||
|
||||
return $newValue;
|
||||
}
|
||||
|
||||
public function deleteOrder(int $orderId): bool
|
||||
{
|
||||
if ($orderId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->db->delete('pp_shop_orders', ['id' => $orderId]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function nullableString(string $value): ?string
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
return $value === '' ? null : $value;
|
||||
}
|
||||
|
||||
private function normalizeTextFilter($value): string
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (strlen($value) > 255) {
|
||||
return substr($value, 0, 255);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function normalizeDateFilter($value): ?string
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,159 @@ class SettingsController
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Globalna wyszukiwarka admin (produkty + zamowienia) - AJAX.
|
||||
*/
|
||||
public function globalSearchAjax(): void
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$phrase = trim((string)\S::get('q'));
|
||||
if ($phrase === '' || mb_strlen($phrase) < 2) {
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'items' => [],
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$phrase = mb_substr($phrase, 0, 120);
|
||||
$phraseNormalized = preg_replace('/\s+/', ' ', $phrase);
|
||||
$phraseNormalized = trim((string)$phraseNormalized);
|
||||
$like = '%' . $phrase . '%';
|
||||
$likeNormalized = '%' . $phraseNormalized . '%';
|
||||
|
||||
$items = [];
|
||||
$defaultLang = (string)\front\factory\Languages::default_language();
|
||||
|
||||
try {
|
||||
$productStmt = $mdb->query(
|
||||
'SELECT '
|
||||
. 'p.id, p.ean, p.sku, p.parent_id, psl.name '
|
||||
. 'FROM pp_shop_products AS p '
|
||||
. 'LEFT JOIN pp_shop_products_langs AS psl ON psl.product_id = p.id AND psl.lang_id = :lang_id '
|
||||
. 'WHERE '
|
||||
. '(p.ean LIKE :q1 OR p.sku LIKE :q2 OR psl.name LIKE :q3) '
|
||||
. 'AND p.archive != 1 '
|
||||
. 'ORDER BY p.id DESC '
|
||||
. 'LIMIT 15',
|
||||
[
|
||||
':lang_id' => $defaultLang,
|
||||
':q1' => $like,
|
||||
':q2' => $like,
|
||||
':q3' => $like,
|
||||
]
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$productStmt = false;
|
||||
}
|
||||
|
||||
$productRows = $productStmt ? $productStmt->fetchAll() : [];
|
||||
if (is_array($productRows)) {
|
||||
foreach ($productRows as $row) {
|
||||
$productId = (int)($row['id'] ?? 0);
|
||||
if ($productId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = trim((string)($row['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = 'Produkt #' . $productId;
|
||||
}
|
||||
|
||||
$meta = [];
|
||||
$sku = trim((string)($row['sku'] ?? ''));
|
||||
$ean = trim((string)($row['ean'] ?? ''));
|
||||
if ($sku !== '') {
|
||||
$meta[] = 'SKU: ' . $sku;
|
||||
}
|
||||
if ($ean !== '') {
|
||||
$meta[] = 'EAN: ' . $ean;
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'type' => 'product',
|
||||
'title' => $name,
|
||||
'subtitle' => implode(' | ', $meta),
|
||||
'url' => '/admin/shop_product/product_edit/id=' . $productId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$orderStmt = $mdb->query(
|
||||
'SELECT '
|
||||
. 'id, number, client_name, client_surname, client_email, client_phone '
|
||||
. 'FROM pp_shop_orders '
|
||||
. 'WHERE '
|
||||
. '('
|
||||
. 'number LIKE :q1 '
|
||||
. 'OR client_email LIKE :q2 '
|
||||
. 'OR client_name LIKE :q3 '
|
||||
. 'OR client_surname LIKE :q4 '
|
||||
. 'OR client_phone LIKE :q5 '
|
||||
. "OR CONCAT_WS(' ', TRIM(client_name), TRIM(client_surname)) LIKE :q6 "
|
||||
. "OR CONCAT_WS(' ', TRIM(client_surname), TRIM(client_name)) LIKE :q7 "
|
||||
. ') '
|
||||
. 'ORDER BY id DESC '
|
||||
. 'LIMIT 15',
|
||||
[
|
||||
':q1' => $like,
|
||||
':q2' => $like,
|
||||
':q3' => $like,
|
||||
':q4' => $like,
|
||||
':q5' => $like,
|
||||
':q6' => $likeNormalized,
|
||||
':q7' => $likeNormalized,
|
||||
]
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$orderStmt = false;
|
||||
}
|
||||
|
||||
$orderRows = $orderStmt ? $orderStmt->fetchAll() : [];
|
||||
if (is_array($orderRows)) {
|
||||
foreach ($orderRows as $row) {
|
||||
$orderId = (int)($row['id'] ?? 0);
|
||||
if ($orderId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orderNumber = trim((string)($row['number'] ?? ''));
|
||||
$clientName = trim((string)($row['client_name'] ?? ''));
|
||||
$clientSurname = trim((string)($row['client_surname'] ?? ''));
|
||||
$clientEmail = trim((string)($row['client_email'] ?? ''));
|
||||
$clientPhone = trim((string)($row['client_phone'] ?? ''));
|
||||
|
||||
$title = $orderNumber !== '' ? 'Zamówienie ' . $orderNumber : 'Zamówienie #' . $orderId;
|
||||
$subtitleParts = [];
|
||||
$fullName = trim($clientName . ' ' . $clientSurname);
|
||||
if ($fullName !== '') {
|
||||
$subtitleParts[] = $fullName;
|
||||
}
|
||||
if ($clientEmail !== '') {
|
||||
$subtitleParts[] = $clientEmail;
|
||||
}
|
||||
if ($clientPhone !== '') {
|
||||
$subtitleParts[] = $clientPhone;
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'type' => 'order',
|
||||
'title' => $title,
|
||||
'subtitle' => implode(' | ', $subtitleParts),
|
||||
'url' => '/admin/shop_order/order_details/order_id=' . $orderId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'items' => array_slice($items, 0, 20),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapis ustawien (AJAX).
|
||||
*/
|
||||
|
||||
323
autoload/admin/Controllers/ShopOrderController.php
Normal file
323
autoload/admin/Controllers/ShopOrderController.php
Normal file
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\Order\OrderAdminService;
|
||||
use admin\ViewModels\Common\PaginatedTableViewModel;
|
||||
|
||||
class ShopOrderController
|
||||
{
|
||||
private OrderAdminService $service;
|
||||
|
||||
public function __construct(OrderAdminService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function list(): string
|
||||
{
|
||||
return $this->view_list();
|
||||
}
|
||||
|
||||
public function view_list(): string
|
||||
{
|
||||
$sortableColumns = [
|
||||
'number',
|
||||
'date_order',
|
||||
'status',
|
||||
'summary',
|
||||
'client',
|
||||
'order_email',
|
||||
'client_phone',
|
||||
'transport',
|
||||
'payment_method',
|
||||
'total_orders',
|
||||
'paid',
|
||||
];
|
||||
|
||||
$statusOptions = ['' => '- status -'];
|
||||
foreach ($this->service->statuses() as $statusId => $statusName) {
|
||||
$statusOptions[(string)$statusId] = (string)$statusName;
|
||||
}
|
||||
|
||||
$filterDefinitions = [
|
||||
['key' => 'number', 'label' => 'Nr zamówienia', 'type' => 'text'],
|
||||
['key' => 'date_from', 'label' => 'Data od', 'type' => 'date'],
|
||||
['key' => 'date_to', 'label' => 'Data do', 'type' => 'date'],
|
||||
['key' => 'status', 'label' => 'Status', 'type' => 'select', 'options' => $statusOptions],
|
||||
['key' => 'client', 'label' => 'Klient', 'type' => 'text'],
|
||||
['key' => 'address', 'label' => 'Adres', 'type' => 'text'],
|
||||
['key' => 'order_email', 'label' => 'Email', 'type' => 'text'],
|
||||
['key' => 'client_phone', 'label' => 'Telefon', 'type' => 'text'],
|
||||
['key' => 'transport', 'label' => 'Dostawa', 'type' => 'text'],
|
||||
['key' => 'payment_method', 'label' => 'Płatność', 'type' => 'text'],
|
||||
];
|
||||
|
||||
$listRequest = \admin\Support\TableListRequestFactory::fromRequest(
|
||||
$filterDefinitions,
|
||||
$sortableColumns,
|
||||
'date_order'
|
||||
);
|
||||
|
||||
$result = $this->service->listForAdmin(
|
||||
$listRequest['filters'],
|
||||
$listRequest['sortColumn'],
|
||||
$listRequest['sortDir'],
|
||||
$listRequest['page'],
|
||||
$listRequest['perPage']
|
||||
);
|
||||
|
||||
$statusesMap = $this->service->statuses();
|
||||
$rows = [];
|
||||
$lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
|
||||
|
||||
foreach ($result['items'] as $item) {
|
||||
$orderId = (int)($item['id'] ?? 0);
|
||||
$orderNumber = (string)($item['number'] ?? '');
|
||||
$statusId = (int)($item['status'] ?? 0);
|
||||
$statusLabel = (string)($statusesMap[$statusId] ?? ('Status #' . $statusId));
|
||||
|
||||
$rows[] = [
|
||||
'lp' => $lp++ . '.',
|
||||
'date_order' => $this->formatDateTime((string)($item['date_order'] ?? '')),
|
||||
'number' => '<a href="/admin/shop_order/order_details/order_id=' . $orderId . '">' . htmlspecialchars($orderNumber, ENT_QUOTES, 'UTF-8') . '</a>',
|
||||
'paid' => ((int)($item['paid'] ?? 0) === 1)
|
||||
? '<i class="fa fa-check text-success"></i>'
|
||||
: '<i class="fa fa-times text-dark"></i>',
|
||||
'status' => htmlspecialchars($statusLabel, ENT_QUOTES, 'UTF-8'),
|
||||
'summary' => number_format((float)($item['summary'] ?? 0), 2, '.', ' ') . ' zł',
|
||||
'client' => htmlspecialchars((string)($item['client'] ?? ''), ENT_QUOTES, 'UTF-8') . ' | zamówienia: <strong>' . (int)($item['total_orders'] ?? 0) . '</strong>',
|
||||
'address' => (string)($item['address'] ?? ''),
|
||||
'order_email' => (string)($item['order_email'] ?? ''),
|
||||
'client_phone' => (string)($item['client_phone'] ?? ''),
|
||||
'transport' => (string)($item['transport'] ?? ''),
|
||||
'payment_method' => (string)($item['payment_method'] ?? ''),
|
||||
'_actions' => [
|
||||
[
|
||||
'label' => 'Szczegóły',
|
||||
'url' => '/admin/shop_order/order_details/order_id=' . $orderId,
|
||||
'class' => 'btn btn-xs btn-primary',
|
||||
],
|
||||
[
|
||||
'label' => 'Usuń',
|
||||
'url' => '/admin/shop_order/order_delete/id=' . $orderId,
|
||||
'class' => 'btn btn-xs btn-danger',
|
||||
'confirm' => 'Na pewno chcesz usunąć wybrane zamówienie?',
|
||||
'confirm_ok' => 'Usuń',
|
||||
'confirm_cancel' => 'Anuluj',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$total = (int)$result['total'];
|
||||
$totalPages = max(1, (int)ceil($total / $listRequest['perPage']));
|
||||
|
||||
$viewModel = new PaginatedTableViewModel(
|
||||
[
|
||||
['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false],
|
||||
['key' => 'date_order', 'sort_key' => 'date_order', 'label' => 'Data dodania', 'class' => 'text-center', 'sortable' => true],
|
||||
['key' => 'number', 'sort_key' => 'number', 'label' => 'Nr zamówienia', 'class' => 'text-center', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'paid', 'sort_key' => 'paid', 'label' => '', 'class' => 'text-center', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'status', 'sort_key' => 'status', 'label' => 'Status', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'summary', 'sort_key' => 'summary', 'label' => 'Wartość', 'class' => 'text-right align-middle', 'sortable' => true],
|
||||
['key' => 'client', 'sort_key' => 'client', 'label' => 'Klient', 'sortable' => true, 'raw' => true],
|
||||
['key' => 'address', 'label' => 'Adres', 'sortable' => false],
|
||||
['key' => 'order_email', 'sort_key' => 'order_email', 'label' => 'Email', 'sortable' => true],
|
||||
['key' => 'client_phone', 'sort_key' => 'client_phone', 'label' => 'Telefon', 'sortable' => true],
|
||||
['key' => 'transport', 'sort_key' => 'transport', 'label' => 'Dostawa', 'sortable' => true],
|
||||
['key' => 'payment_method', 'sort_key' => 'payment_method', 'label' => 'Płatność', 'sortable' => true],
|
||||
],
|
||||
$rows,
|
||||
$listRequest['viewFilters'],
|
||||
[
|
||||
'column' => $listRequest['sortColumn'],
|
||||
'dir' => $listRequest['sortDir'],
|
||||
],
|
||||
[
|
||||
'page' => $listRequest['page'],
|
||||
'per_page' => $listRequest['perPage'],
|
||||
'total' => $total,
|
||||
'total_pages' => $totalPages,
|
||||
],
|
||||
array_merge($listRequest['queryFilters'], [
|
||||
'sort' => $listRequest['sortColumn'],
|
||||
'dir' => $listRequest['sortDir'],
|
||||
'per_page' => $listRequest['perPage'],
|
||||
]),
|
||||
$listRequest['perPageOptions'],
|
||||
$sortableColumns,
|
||||
'/admin/shop_order/list/',
|
||||
'Brak danych w tabeli.'
|
||||
);
|
||||
|
||||
return \Tpl::view('shop-order/orders-list', [
|
||||
'viewModel' => $viewModel,
|
||||
]);
|
||||
}
|
||||
|
||||
public function details(): string
|
||||
{
|
||||
return $this->order_details();
|
||||
}
|
||||
|
||||
public function order_details(): string
|
||||
{
|
||||
$orderId = (int)\S::get('order_id');
|
||||
$order = $this->service->details($orderId);
|
||||
|
||||
$coupon = null;
|
||||
if (!empty($order) && !empty($order['coupon_id'])) {
|
||||
$coupon = new \shop\Coupon((int)$order['coupon_id']);
|
||||
}
|
||||
|
||||
return \Tpl::view('shop-order/order-details', [
|
||||
'order' => $order,
|
||||
'coupon' => $coupon,
|
||||
'order_statuses' => $this->service->statuses(),
|
||||
'next_order_id' => $this->service->nextOrderId($orderId),
|
||||
'prev_order_id' => $this->service->prevOrderId($orderId),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(): string
|
||||
{
|
||||
return $this->order_edit();
|
||||
}
|
||||
|
||||
public function order_edit(): string
|
||||
{
|
||||
$orderId = (int)\S::get('order_id');
|
||||
|
||||
return \Tpl::view('shop-order/order-edit', [
|
||||
'order' => $this->service->details($orderId),
|
||||
'order_statuses' => $this->service->statuses(),
|
||||
'transport' => \shop\Transport::transport_list(),
|
||||
'payment_methods' => \shop\PaymentMethod::method_list(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->order_save();
|
||||
}
|
||||
|
||||
public function order_save(): void
|
||||
{
|
||||
$saved = $this->service->saveOrderByAdmin([
|
||||
'order_id' => (int)\S::get('order_id'),
|
||||
'client_name' => (string)\S::get('client_name'),
|
||||
'client_surname' => (string)\S::get('client_surname'),
|
||||
'client_street' => (string)\S::get('client_street'),
|
||||
'client_postal_code' => (string)\S::get('client_postal_code'),
|
||||
'client_city' => (string)\S::get('client_city'),
|
||||
'client_email' => (string)\S::get('client_email'),
|
||||
'firm_name' => (string)\S::get('firm_name'),
|
||||
'firm_street' => (string)\S::get('firm_street'),
|
||||
'firm_postal_code' => (string)\S::get('firm_postal_code'),
|
||||
'firm_city' => (string)\S::get('firm_city'),
|
||||
'firm_nip' => (string)\S::get('firm_nip'),
|
||||
'transport_id' => (int)\S::get('transport_id'),
|
||||
'inpost_paczkomat' => (string)\S::get('inpost_paczkomat'),
|
||||
'payment_method_id' => (int)\S::get('payment_method_id'),
|
||||
]);
|
||||
|
||||
if ($saved) {
|
||||
\S::alert('Zamówienie zostało zapisane.');
|
||||
}
|
||||
|
||||
header('Location: /admin/shop_order/order_details/order_id=' . (int)\S::get('order_id'));
|
||||
exit;
|
||||
}
|
||||
|
||||
public function notes_save(): void
|
||||
{
|
||||
$this->service->saveNotes((int)\S::get('order_id'), (string)\S::get('notes'));
|
||||
}
|
||||
|
||||
public function order_status_change(): void
|
||||
{
|
||||
$response = $this->service->changeStatus(
|
||||
(int)\S::get('order_id'),
|
||||
(int)\S::get('status'),
|
||||
(string)\S::get('email') === 'true'
|
||||
);
|
||||
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function order_resend_confirmation_email(): void
|
||||
{
|
||||
$response = $this->service->resendConfirmationEmail((int)\S::get('order_id'));
|
||||
|
||||
echo json_encode(['result' => $response]);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function set_order_as_unpaid(): void
|
||||
{
|
||||
$orderId = (int)\S::get('order_id');
|
||||
$this->service->setOrderAsUnpaid($orderId);
|
||||
|
||||
header('Location: /admin/shop_order/order_details/order_id=' . $orderId);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function set_order_as_paid(): void
|
||||
{
|
||||
$orderId = (int)\S::get('order_id');
|
||||
$this->service->setOrderAsPaid($orderId, (int)\S::get('send_mail') === 1);
|
||||
|
||||
header('Location: /admin/shop_order/order_details/order_id=' . $orderId);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function send_order_to_apilo(): void
|
||||
{
|
||||
$orderId = (int)\S::get('order_id');
|
||||
|
||||
if ($this->service->sendOrderToApilo($orderId)) {
|
||||
\S::alert('Zamówienie zostanie wysłane ponownie do apilo.com');
|
||||
} else {
|
||||
\S::alert('Wystąpił błąd podczas wysyłania zamówienia do apilo.com');
|
||||
}
|
||||
|
||||
header('Location: /admin/shop_order/order_details/order_id=' . $orderId);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function toggle_trustmate_send(): void
|
||||
{
|
||||
echo json_encode($this->service->toggleTrustmateSend((int)\S::get('order_id')));
|
||||
exit;
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
$this->order_delete();
|
||||
}
|
||||
|
||||
public function order_delete(): void
|
||||
{
|
||||
if ($this->service->deleteOrder((int)\S::get('id'))) {
|
||||
\S::alert('Zamówienie zostało usunięte');
|
||||
}
|
||||
|
||||
header('Location: /admin/shop_order/list/');
|
||||
exit;
|
||||
}
|
||||
|
||||
private function formatDateTime(string $value): string
|
||||
{
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ts = strtotime($value);
|
||||
if ($ts === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return date('Y-m-d H:i', $ts);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\PaymentMethod\PaymentMethodRepository;
|
||||
use Domain\Integrations\IntegrationsRepository;
|
||||
use admin\ViewModels\Common\PaginatedTableViewModel;
|
||||
use admin\ViewModels\Forms\FormAction;
|
||||
use admin\ViewModels\Forms\FormEditViewModel;
|
||||
@@ -240,7 +241,10 @@ class ShopPaymentMethodController
|
||||
|
||||
private function getApiloPaymentTypes(): array
|
||||
{
|
||||
$rawSetting = \admin\factory\Integrations::apilo_settings('payment-types-list');
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new IntegrationsRepository( $mdb );
|
||||
$rawSetting = $integrationsRepository -> getSetting( 'apilo', 'payment-types-list' );
|
||||
$raw = null;
|
||||
|
||||
if (is_array($rawSetting)) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\ShopStatus\ShopStatusRepository;
|
||||
use Domain\Integrations\IntegrationsRepository;
|
||||
use admin\ViewModels\Common\PaginatedTableViewModel;
|
||||
use admin\ViewModels\Forms\FormAction;
|
||||
use admin\ViewModels\Forms\FormEditViewModel;
|
||||
@@ -246,8 +247,12 @@ class ShopStatusesController
|
||||
|
||||
private function getApiloStatusList(): array
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new IntegrationsRepository( $mdb );
|
||||
|
||||
$list = [];
|
||||
$raw = @unserialize(\admin\factory\Integrations::apilo_settings('status-types-list'));
|
||||
$raw = @unserialize( $integrationsRepository -> getSetting( 'apilo', 'status-types-list' ) );
|
||||
if (is_array($raw)) {
|
||||
foreach ($raw as $apiloStatus) {
|
||||
if (isset($apiloStatus['id'], $apiloStatus['name'])) {
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace admin\Controllers;
|
||||
|
||||
use Domain\Transport\TransportRepository;
|
||||
use Domain\PaymentMethod\PaymentMethodRepository;
|
||||
use Domain\Integrations\IntegrationsRepository;
|
||||
use admin\ViewModels\Common\PaginatedTableViewModel;
|
||||
use admin\ViewModels\Forms\FormAction;
|
||||
use admin\ViewModels\Forms\FormEditViewModel;
|
||||
@@ -314,7 +315,10 @@ class ShopTransportController
|
||||
|
||||
private function getApiloCarrierAccounts(): array
|
||||
{
|
||||
$rawSetting = \admin\factory\Integrations::apilo_settings('carrier-account-list');
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new IntegrationsRepository( $mdb );
|
||||
$rawSetting = $integrationsRepository -> getSetting( 'apilo', 'carrier-account-list' );
|
||||
$raw = null;
|
||||
|
||||
if (is_array($rawSetting)) {
|
||||
|
||||
@@ -399,6 +399,15 @@ class Site
|
||||
new \Domain\Client\ClientRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopOrder' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopOrderController(
|
||||
new \Domain\Order\OrderAdminService(
|
||||
new \Domain\Order\OrderRepository( $mdb )
|
||||
)
|
||||
);
|
||||
},
|
||||
];
|
||||
|
||||
return self::$newControllers;
|
||||
|
||||
@@ -4,9 +4,13 @@ class Dashboard
|
||||
{
|
||||
static public function main_view()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$statusesRepository = new \Domain\ShopStatus\ShopStatusRepository( $mdb );
|
||||
|
||||
return \Tpl::view( 'dashboard/main-view', [
|
||||
'last_orders' => \shop\Dashboard::last_orders(),
|
||||
'order_statuses' => \shop\Order::order_statuses(),
|
||||
'order_statuses' => $statusesRepository -> allStatuses(),
|
||||
'sales' => \shop\Dashboard::last_24_months_sales(),
|
||||
'best_sales_products' => \shop\Dashboard::best_sales_products(),
|
||||
'most_view_products' => \shop\Dashboard::most_view_products(),
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
<?php
|
||||
namespace admin\controls;
|
||||
class ShopOrder
|
||||
{
|
||||
static public function send_order_to_apilo()
|
||||
{
|
||||
$order_id = \S::get( 'order_id' );
|
||||
if ( \admin\factory\ShopOrder::send_order_to_apilo( $order_id ) ) {
|
||||
\S::alert( 'Zamówienie zostanie wysłane ponownie do apilo.com' );
|
||||
} else {
|
||||
\S::alert( 'Wystąpił błąd podczas wysyłania zamówienia do apilo.com' );
|
||||
}
|
||||
header( 'Location: /admin/shop_order/order_details/order_id=' . $order_id );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function order_resend_confirmation_email()
|
||||
{
|
||||
$order = new \shop\Order( (int)\S::get( 'order_id' ) );
|
||||
$response = $order -> order_resend_confirmation_email();
|
||||
|
||||
echo json_encode( [ 'result' => $response ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function notes_save()
|
||||
{
|
||||
\shop\Order::notes_save( \S::get( 'order_id' ), \S::get( 'notes' ) );
|
||||
}
|
||||
|
||||
static public function order_save()
|
||||
{
|
||||
if ( \shop\Order::order_save_by_admin(
|
||||
\S::get( 'order_id' ), \S::get( 'client_name' ), \S::get( 'client_surname' ), \S::get( 'client_street' ), \S::get( 'client_postal_code' ), \S::get( 'client_city' ), \S::get( 'client_email' ), \S::get( 'firm_name' ), \S::get( 'firm_street' ), \S::get( 'firm_postal_code' ), \S::get( 'firm_city' ), \S::get( 'firm_nip' ), \S::get( 'transport_id' ), \S::get( 'inpost_paczkomat' ), \S::get( 'payment_method_id' )
|
||||
) )
|
||||
\S::alert( 'Zamówienie zostało zapisane.' );
|
||||
|
||||
header( 'Location: /admin/shop_order/order_details/order_id=' . \S::get( 'order_id' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function order_edit()
|
||||
{
|
||||
return \Tpl::view( 'shop-order/order-edit', [
|
||||
'order' => new \shop\Order( (int)\S::get( 'order_id' ) ),
|
||||
'order_statuses' => \shop\Order::order_statuses(),
|
||||
'transport' => \shop\Transport::transport_list(),
|
||||
'payment_methods' => \shop\PaymentMethod::method_list()
|
||||
] );
|
||||
}
|
||||
|
||||
public static function order_details()
|
||||
{
|
||||
$order = new \shop\Order( (int)\S::get( 'order_id' ) );
|
||||
$coupon = $order -> coupon_id ? new \shop\Coupon( $order -> coupon_id ) : null;
|
||||
|
||||
return \Tpl::view( 'shop-order/order-details', [
|
||||
'order' => $order,
|
||||
'coupon' => $coupon,
|
||||
'order_statuses' => \shop\Order::order_statuses(),
|
||||
'next_order_id' => \admin\factory\ShopOrder::next_order_id( (int)\S::get( 'order_id' ) ),
|
||||
'prev_order_id' => \admin\factory\ShopOrder::prev_order_id( (int)\S::get( 'order_id' ) ),
|
||||
] );
|
||||
}
|
||||
|
||||
public static function view_list()
|
||||
{
|
||||
return \Tpl::view(
|
||||
'shop-order/view-list'
|
||||
);
|
||||
}
|
||||
|
||||
public static function order_status_change()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$order = new \shop\Order( (int)\S::get( 'order_id' ) );
|
||||
$response = $order -> update_status( (int)\S::get( 'status' ), \S::get( 'email' ) == 'true' ? 1 : 0 );
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function order_delete()
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ( \shop\Order::order_delete( (int)\S::get( 'id' ) ) )
|
||||
{
|
||||
\S::alert( 'Zamówienie zostało usunięte' );
|
||||
\Log::save_log( 'Usunięcie zamówienia | ID: ' . (int)\S::get( 'id' ), $user['id'] );
|
||||
}
|
||||
|
||||
header( 'Location: /admin/shop_order/view_list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
// set_order_as_unpaid
|
||||
public static function set_order_as_unpaid()
|
||||
{
|
||||
$order = new \shop\Order( (int)\S::get( 'order_id' ) );
|
||||
$order -> set_as_unpaid();
|
||||
header( 'Location: /admin/shop_order/order_details/order_id=' . (int)\S::get( 'order_id' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// set_order_as_paid
|
||||
public static function set_order_as_paid()
|
||||
{
|
||||
$order = new \shop\Order( (int)\S::get( 'order_id' ) );
|
||||
if ( $order -> set_as_paid() )
|
||||
{
|
||||
$order -> update_status( 4, (int)\S::get( 'send_mail' ) );
|
||||
}
|
||||
header( 'Location: /admin/shop_order/order_details/order_id=' . (int)\S::get( 'order_id' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// toggle_trustmate_send
|
||||
public static function toggle_trustmate_send()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$order_id = (int)\S::get( 'order_id' );
|
||||
$order = $mdb -> get( 'pp_shop_orders', [ 'trustmate_send' ], [ 'id' => $order_id ] );
|
||||
|
||||
$new_value = $order['trustmate_send'] ? 0 : 1;
|
||||
$mdb -> update( 'pp_shop_orders', [ 'trustmate_send' => $new_value ], [ 'id' => $order_id ] );
|
||||
|
||||
echo json_encode( [ 'result' => true, 'trustmate_send' => $new_value ] );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -229,6 +229,10 @@ class ShopProduct
|
||||
// ajax_load_products
|
||||
static public function ajax_load_products() {
|
||||
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas ładowania produktów wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
\S::set_session( 'products_list_current_page', \S::get( 'current_page' ) );
|
||||
@@ -241,7 +245,7 @@ class ShopProduct
|
||||
'html' => \Tpl::view( 'shop-product/products-list-table', [
|
||||
'products' => $products['products'],
|
||||
'current_page' => \S::get( 'current_page' ),
|
||||
'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ),
|
||||
'apilo_enabled' => $integrationsRepository -> getSetting( 'apilo', 'enabled' ),
|
||||
'show_xml_data' => \S::get_session( 'show_xml_data' )
|
||||
] )
|
||||
];
|
||||
@@ -253,6 +257,10 @@ class ShopProduct
|
||||
|
||||
static public function view_list()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
$current_page = \S::get_session( 'products_list_current_page' );
|
||||
|
||||
if ( !$current_page ) {
|
||||
@@ -276,9 +284,9 @@ class ShopProduct
|
||||
'current_page' => $current_page,
|
||||
'query_array' => $query_array,
|
||||
'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 ),
|
||||
'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ),
|
||||
'apilo_enabled' => $integrationsRepository -> getSetting( 'apilo', 'enabled' ),
|
||||
'show_xml_data' => \S::get_session( 'show_xml_data' ),
|
||||
'shoppro_enabled' => \admin\factory\Integrations::shoppro_settings( 'enabled' )
|
||||
'shoppro_enabled' => $integrationsRepository -> getSetting( 'shoppro', 'enabled' )
|
||||
] );
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
/**
|
||||
* Fasada kompatybilnosci wstecznej.
|
||||
* Deleguje do Domain\Integrations\IntegrationsRepository.
|
||||
* Uzywane przez: cron.php, shop\Order, admin\Controllers\ShopStatusesController, admin\controls\ShopTransport, admin\controls\ShopProduct, admin\Controllers\ShopPaymentMethodController.
|
||||
*/
|
||||
class Integrations {
|
||||
|
||||
private static function repo(): \Domain\Integrations\IntegrationsRepository
|
||||
{
|
||||
global $mdb;
|
||||
return new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
}
|
||||
|
||||
// ── Apilo settings ──────────────────────────────────────────
|
||||
|
||||
static public function apilo_settings( $name = '' )
|
||||
{
|
||||
$repo = self::repo();
|
||||
return $name ? $repo->getSetting( 'apilo', $name ) : $repo->getSettings( 'apilo' );
|
||||
}
|
||||
|
||||
static public function apilo_settings_save( $field_id, $value )
|
||||
{
|
||||
return self::repo()->saveSetting( 'apilo', $field_id, $value );
|
||||
}
|
||||
|
||||
static public function apilo_get_access_token()
|
||||
{
|
||||
return self::repo()->apiloGetAccessToken();
|
||||
}
|
||||
|
||||
static public function apilo_keepalive( int $refresh_lead_seconds = 300 )
|
||||
{
|
||||
return self::repo()->apiloKeepalive( $refresh_lead_seconds );
|
||||
}
|
||||
|
||||
static public function apilo_authorization( $client_id, $client_secret, $authorization_code )
|
||||
{
|
||||
return self::repo()->apiloAuthorize( $client_id, $client_secret, $authorization_code );
|
||||
}
|
||||
|
||||
// ── Apilo product linking ─────────────────────────────────────
|
||||
|
||||
static public function apilo_product_select_save( int $product_id, $apilo_product_id, $apilo_product_name )
|
||||
{
|
||||
return self::repo()->linkProduct( $product_id, $apilo_product_id, $apilo_product_name );
|
||||
}
|
||||
|
||||
static public function apilo_product_select_delete( int $product_id )
|
||||
{
|
||||
return self::repo()->unlinkProduct( $product_id );
|
||||
}
|
||||
|
||||
// ── ShopPRO settings ──────────────────────────────────────────
|
||||
|
||||
static public function shoppro_settings( $name = '' )
|
||||
{
|
||||
$repo = self::repo();
|
||||
return $name ? $repo->getSetting( 'shoppro', $name ) : $repo->getSettings( 'shoppro' );
|
||||
}
|
||||
|
||||
static public function shoppro_settings_save( $field_id, $value )
|
||||
{
|
||||
return self::repo()->saveSetting( 'shoppro', $field_id, $value );
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
class ShopOrder
|
||||
{
|
||||
static public function send_order_to_apilo( int $order_id ) {
|
||||
|
||||
global $mdb;
|
||||
|
||||
// początek - anulowanie zamówienia w apilo
|
||||
$apilo_settings = \admin\factory\Integrations::apilo_settings();
|
||||
|
||||
$new_status = 8; // zamówienie anulowwane
|
||||
|
||||
$order = \admin\factory\ShopOrder::order_details( $order_id );
|
||||
|
||||
if ( $order['apilo_order_id'] ) {
|
||||
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $order['apilo_order_id'] . '/status/' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [
|
||||
'id' => $order['apilo_order_id'],
|
||||
'status' => (int)\front\factory\ShopStatuses::get_apilo_status_id( $new_status )
|
||||
] ) );
|
||||
curl_setopt( $ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer " . $access_token,
|
||||
"Accept: application/json",
|
||||
"Content-Type: application/json"
|
||||
] );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$apilo_result = curl_exec( $ch );
|
||||
|
||||
$apilo_result = json_decode( $apilo_result, true );
|
||||
|
||||
if ( $apilo_result['updates'] == 1 ) {
|
||||
|
||||
// zmień ID zamówienia na największe ID zamówienia + 1, oraz usuń ID zamówienia z apilo
|
||||
$new_order_id = $mdb -> max( 'pp_shop_orders', 'id' ) + 1;
|
||||
|
||||
// pobierz listę kolumn zamówienia
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_orders' AND COLUMN_NAME != 'id'";
|
||||
$columns = $mdb -> query( $query ) -> fetchAll( \PDO::FETCH_COLUMN );
|
||||
$columns_list = implode( ', ', $columns );
|
||||
|
||||
// kopiuj stare zamówienie do nowego ID
|
||||
$mdb -> query( 'INSERT INTO pp_shop_orders (' . $columns_list . ') SELECT ' . $columns_list . ' FROM pp_shop_orders pso WHERE pso.id = ' . $order_id );
|
||||
$new_order_id = $mdb -> id();
|
||||
|
||||
// pobierz listę kolumn produktów zamówienia
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_order_products' AND COLUMN_NAME != 'id' AND COLUMN_NAME != 'order_id'";
|
||||
$columns = $mdb -> query( $query ) -> fetchAll( \PDO::FETCH_COLUMN );
|
||||
$columns_list = implode( ', ', $columns );
|
||||
|
||||
// kopiuj produkty zamówienia do nowego zamówienia
|
||||
$mdb -> query( 'INSERT INTO pp_shop_order_products (order_id, ' . $columns_list . ') SELECT ' . $new_order_id . ',' . $columns_list . ' FROM pp_shop_order_products psop WHERE psop.order_id = ' . $order_id );
|
||||
|
||||
// pobierz listę kolumn z tabeli pp_shop_order_statuses
|
||||
$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'pp_shop_order_statuses' AND COLUMN_NAME != 'id' AND COLUMN_NAME != 'order_id'";
|
||||
$columns = $mdb -> query( $query ) -> fetchAll( \PDO::FETCH_COLUMN );
|
||||
$columns_list = implode( ', ', $columns );
|
||||
|
||||
// kopiuj statusy zamówienia do nowego zamówienia
|
||||
$mdb -> query( 'INSERT INTO pp_shop_order_statuses (order_id, ' . $columns_list . ') SELECT ' . $new_order_id . ',' . $columns_list . ' FROM pp_shop_order_statuses psos WHERE psos.order_id = ' . $order_id );
|
||||
|
||||
// usuń stare zamówienie
|
||||
$mdb -> delete( 'pp_shop_orders', [ 'id' => $order_id ] );
|
||||
$mdb -> delete( 'pp_shop_order_products', [ 'order_id' => $order_id ] );
|
||||
$mdb -> delete( 'pp_shop_order_statuses', [ 'order_id' => $order_id ] );
|
||||
|
||||
// zmień wartość kolumny apilo_order_id na NULL
|
||||
$mdb -> update( 'pp_shop_orders', [ 'apilo_order_id' => NULL ], [ 'id' => $new_order_id ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
curl_close( $ch );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function next_order_id( int $order_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$order_id )
|
||||
return false;
|
||||
|
||||
return $mdb -> get( 'pp_shop_orders', 'id', [ 'id[>]' => $order_id, 'ORDER' => [ 'id' => 'ASC' ], 'LIMIT' => 1 ] );
|
||||
}
|
||||
|
||||
static public function prev_order_id( int $order_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( !$order_id )
|
||||
return false;
|
||||
|
||||
return $mdb -> get( 'pp_shop_orders', 'id', [ 'id[<]' => $order_id, 'ORDER' => [ 'id' => 'DESC' ], 'LIMIT' => 1 ] );
|
||||
}
|
||||
|
||||
static public function order_details( int $order_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$order = $mdb -> get( 'pp_shop_orders', '*', [ 'id' => $order_id ] );
|
||||
$order['products'] = $mdb -> select( 'pp_shop_order_products', '*', [ 'order_id' => $order_id ] );
|
||||
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
@@ -55,10 +55,9 @@ class Order implements \ArrayAccess
|
||||
public static function order_statuses()
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> select( 'pp_shop_statuses', [ 'id', 'status' ], [ 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
|
||||
$statuses[ (int)$row['id'] ] = $row['status'];
|
||||
return $statuses;
|
||||
|
||||
$repository = new \Domain\ShopStatus\ShopStatusRepository( $mdb );
|
||||
return $repository -> allStatuses();
|
||||
}
|
||||
|
||||
public function update_aplio_order_status_date( $date )
|
||||
@@ -80,8 +79,10 @@ class Order implements \ArrayAccess
|
||||
{
|
||||
global $mdb, $config;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
// apilo
|
||||
$apilo_settings = \admin\factory\Integrations::apilo_settings();
|
||||
$apilo_settings = $integrationsRepository -> getSettings( 'apilo' );
|
||||
if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and $apilo_settings['sync_orders'] )
|
||||
{
|
||||
// put data to file
|
||||
@@ -117,6 +118,8 @@ class Order implements \ArrayAccess
|
||||
{
|
||||
global $mdb, $config;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
if ( $this -> status == $status )
|
||||
return false;
|
||||
|
||||
@@ -137,7 +140,7 @@ class Order implements \ArrayAccess
|
||||
$response['result'] = true;
|
||||
|
||||
// apilo
|
||||
$apilo_settings = \admin\factory\Integrations::apilo_settings();
|
||||
$apilo_settings = $integrationsRepository -> getSettings( 'apilo' );
|
||||
if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and $apilo_settings['sync_orders'] )
|
||||
{
|
||||
// put data to file
|
||||
@@ -322,7 +325,9 @@ class Order implements \ArrayAccess
|
||||
|
||||
private function sync_apilo_payment(): bool
|
||||
{
|
||||
global $config;
|
||||
global $config, $mdb;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
if ( !(int)$this -> apilo_order_id )
|
||||
return true;
|
||||
@@ -332,7 +337,7 @@ class Order implements \ArrayAccess
|
||||
$payment_type = 1;
|
||||
|
||||
$payment_date = new \DateTime( $this -> date_order );
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' );
|
||||
@@ -371,12 +376,14 @@ class Order implements \ArrayAccess
|
||||
|
||||
private function sync_apilo_status( int $status ): bool
|
||||
{
|
||||
global $config;
|
||||
global $config, $mdb;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
if ( !(int)$this -> apilo_order_id )
|
||||
return true;
|
||||
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' );
|
||||
|
||||
17
cron.php
17
cron.php
@@ -53,12 +53,13 @@ $mdb = new medoo( [
|
||||
] );
|
||||
|
||||
$settings = \front\factory\Settings::settings_details();
|
||||
$apilo_settings = \admin\factory\Integrations::apilo_settings();
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
$apilo_settings = $integrationsRepository -> getSettings( 'apilo' );
|
||||
|
||||
// Keepalive tokenu Apilo: odswiezaj token przed wygasnieciem, zeby integracja byla stale aktywna.
|
||||
if ( (int)($apilo_settings['enabled'] ?? 0) === 1 ) {
|
||||
\admin\factory\Integrations::apilo_keepalive( 300 );
|
||||
$apilo_settings = \admin\factory\Integrations::apilo_settings();
|
||||
$integrationsRepository -> apiloKeepalive( 300 );
|
||||
$apilo_settings = $integrationsRepository -> getSettings( 'apilo' );
|
||||
Order::process_apilo_sync_queue( 10 );
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_products'] and $apilo_
|
||||
{
|
||||
if ( $result = $mdb -> query( 'SELECT id, apilo_product_id, apilo_get_data_date, apilo_product_name FROM pp_shop_products WHERE apilo_product_id IS NOT NULL AND apilo_product_id != 0 AND ( apilo_get_data_date IS NULL OR apilo_get_data_date <= \'' . date( 'Y-m-d H:i:s', strtotime( '-10 minutes', time() ) ) . '\' ) ORDER BY apilo_get_data_date ASC LIMIT 1' ) -> fetch( \PDO::FETCH_ASSOC ) )
|
||||
{
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
$url = 'https://projectpro.apilo.com/rest/api/warehouse/product/' . $result['apilo_product_id'] . '/';
|
||||
$curl = curl_init( $url );
|
||||
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
|
||||
@@ -151,7 +152,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_products'] and $apilo_
|
||||
// synchronizacja cen apilo.com
|
||||
if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and ( !$apilo_settings['pricelist_update_date'] or $apilo_settings['pricelist_update_date'] <= date( 'Y-m-d H:i:s', strtotime( '-1 hour', time() ) ) ) )
|
||||
{
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
|
||||
$url = 'https://projectpro.apilo.com/rest/api/warehouse/price-calculated/?price=' . $apilo_settings['pricelist_id'];
|
||||
|
||||
@@ -188,7 +189,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and ( !$apil
|
||||
}
|
||||
}
|
||||
}
|
||||
\admin\factory\Integrations::apilo_settings_save( 'pricelist_update_date', date( 'Y-m-d H:i:s' ) );
|
||||
$integrationsRepository -> saveSetting( 'apilo', 'pricelist_update_date', date( 'Y-m-d H:i:s' ) );
|
||||
echo '<p>Zaktualizowałem ceny produktów (APILO)</p>';
|
||||
}
|
||||
|
||||
@@ -253,7 +254,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
'media' => null
|
||||
];
|
||||
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
|
||||
$order_date = new DateTime( $order['date_order'] );
|
||||
|
||||
@@ -502,7 +503,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se
|
||||
{
|
||||
if ( $order['apilo_order_id'] )
|
||||
{
|
||||
$access_token = \admin\factory\Integrations::apilo_get_access_token();
|
||||
$access_token = $integrationsRepository -> apiloGetAccessToken();
|
||||
$url = 'https://projectpro.apilo.com/rest/api/orders/' . $order['apilo_order_id'] . '/';
|
||||
|
||||
$ch = curl_init( $url );
|
||||
|
||||
@@ -4,6 +4,44 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.277 (2026-02-15) - Stabilizacja ShopOrder + Integrations + Global Search
|
||||
|
||||
- **ShopOrder (stabilizacja po migracji)**
|
||||
- FIX: `Domain\Order\OrderRepository::listForAdmin()` - poprawa zapytan SQL (count/list), bezpieczne fallbacki i poprawne zwracanie listy zamowien w `/admin/shop_order/list/`
|
||||
- FIX: wyrównanie wysokości komórek w `components/table-list` (`vertical-align` + lokalny override dla `.text-right` w tabeli)
|
||||
- **Integrations (cleanup)**
|
||||
- CLEANUP: usunieta fasada `autoload/admin/factory/class.Integrations.php`
|
||||
- UPDATE: przepięcie wywołań na `Domain\Integrations\IntegrationsRepository` w: `cron.php`, `shop\Order`, `admin\Controllers\ShopPaymentMethodController`, `admin\Controllers\ShopStatusesController`, `admin\Controllers\ShopTransportController`, `admin\controls\ShopProduct`
|
||||
- **Admin UX**
|
||||
- NOWE: globalna wyszukiwarka w top-barze (obok "Wyczysc cache") dla produktow i zamowien
|
||||
- NOWE: endpoint `/admin/settings/globalSearchAjax/` (`SettingsController::globalSearchAjax`)
|
||||
- FIX: wsparcie wyszukiwania po pełnym imieniu i nazwisku (np. "Jan Kowalski") + poprawka escapingu SQL w `CONCAT_WS`
|
||||
- TEST:
|
||||
- Pelny suite: **OK (385 tests, 1246 assertions)**
|
||||
- Test punktowy: `SettingsControllerTest` **OK (7 tests, 10 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.276 (2026-02-15) - ShopOrder
|
||||
|
||||
- **ShopOrder** - migracja `/admin/shop_order/*` na Domain + DI + nowe widoki
|
||||
- NOWE: `Domain\Order\OrderRepository` (lista admin z filtrowaniem/sortowaniem, szczegóły, historia statusów, notes, save admin, summary, trustmate, delete)
|
||||
- NOWE: `Domain\Order\OrderAdminService` (operacje aplikacyjne admin: status/paid/unpaid/resend email/send to apilo/delete)
|
||||
- NOWE: `admin\Controllers\ShopOrderController` (DI) z akcjami `list/view_list`, `details/order_details`, `edit/order_edit`, `save/order_save`, `notes_save`, `order_status_change`, `order_resend_confirmation_email`, `set_order_as_paid`, `set_order_as_unpaid`, `send_order_to_apilo`, `toggle_trustmate_send`, `delete/order_delete`
|
||||
- UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopOrder`
|
||||
- UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_order/list/`
|
||||
- UPDATE: lista zamówień przepięta z legacy grid na `components/table-list` (`shop-order/orders-list`)
|
||||
- UPDATE: `shop-order/order-details` i `shop-order/order-edit` przebudowane bez `gridEdit` + wydzielenie JS do `*-custom-script.php`
|
||||
- UPDATE: `shop\Order::order_statuses()` przepiete na `Domain\ShopStatus\ShopStatusRepository`
|
||||
- UPDATE: `admin\controls\Dashboard` pobiera statusy przez `Domain\ShopStatus\ShopStatusRepository`
|
||||
- CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopOrder.php`, `autoload/admin/factory/class.ShopOrder.php`, `admin/templates/shop-order/view-list.php`
|
||||
- TEST:
|
||||
- NOWE: `tests/Unit/Domain/Order/OrderRepositoryTest.php`
|
||||
- NOWE: `tests/Unit/admin/Controllers/ShopOrderControllerTest.php`
|
||||
- Testy punktowe: **OK (8 tests, 49 assertions)**
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.275 (2026-02-15) - ShopCategory
|
||||
|
||||
- **ShopCategory** - migracja `/admin/shop_category/*` na Domain + DI + nowe endpointy AJAX
|
||||
|
||||
@@ -115,6 +115,8 @@ Zamówienia sklepu (źródło danych dla list i szczegółów klientów w panelu
|
||||
|
||||
**Aktualizacja 2026-02-15 (ver. 0.274):** moduł `/admin/shop_clients/*` korzysta z `Domain\Client\ClientRepository` przez `admin\Controllers\ShopClientsController`.
|
||||
|
||||
**Aktualizacja 2026-02-15 (ver. 0.276):** moduł `/admin/shop_order/*` korzysta z `Domain\Order\OrderRepository` przez `admin\Controllers\ShopOrderController`; usunięto legacy `admin\controls\ShopOrder` i `admin\factory\ShopOrder`.
|
||||
|
||||
## pp_banners
|
||||
Banery.
|
||||
|
||||
|
||||
@@ -253,6 +253,19 @@ autoload/
|
||||
- `cron.php` automatycznie ponawia zalegle syncy (`Order::process_apilo_sync_queue()`).
|
||||
- `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`.
|
||||
|
||||
**Aktualizacja 2026-02-15 (ver. 0.276):**
|
||||
- Dodano modul domenowy `Domain/Order/OrderRepository.php`.
|
||||
- Dodano serwis aplikacyjny `Domain/Order/OrderAdminService.php`.
|
||||
- Dodano kontroler DI `admin/Controllers/ShopOrderController.php`.
|
||||
- Modul `/admin/shop_order/*` dziala na nowych widokach (`orders-list`, `order-details`, `order-edit`).
|
||||
- Usunieto legacy: `autoload/admin/controls/class.ShopOrder.php`, `autoload/admin/factory/class.ShopOrder.php`, `admin/templates/shop-order/view-list.php`.
|
||||
|
||||
**Aktualizacja 2026-02-15 (ver. 0.277):**
|
||||
- Dodano globalna wyszukiwarke admin w `admin/templates/site/main-layout.php` (produkty + zamowienia).
|
||||
- Dodano endpoint AJAX `SettingsController::globalSearchAjax()` w `autoload/admin/Controllers/SettingsController.php`.
|
||||
- Usunieto fasade `autoload/admin/factory/class.Integrations.php`.
|
||||
- Wywołania integracji przepiete bezposrednio na `Domain/Integrations/IntegrationsRepository.php`.
|
||||
|
||||
### Routing admin (admin\Site::route())
|
||||
1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj
|
||||
2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\`
|
||||
|
||||
@@ -156,6 +156,7 @@ grep -r "Product::getQuantity" .
|
||||
| 25 | ShopProduct (mass_edit) | 0.274 | DI kontroler + routing dla `mass_edit`, `mass_edit_save`, `get_products_by_category`, cleanup legacy akcji |
|
||||
| 26 | ShopClients | 0.274 | DI kontroler + routing dla `list/details`, nowe listy na `components/table-list`, cleanup legacy controls/factory |
|
||||
| 27 | ShopCategory | 0.275 | CategoryRepository + DI kontroler + routing, endpointy AJAX (`save_categories_order`, `save_products_order`, `cookie_categories`), cleanup legacy controls/factory/view |
|
||||
| 28 | ShopOrder | 0.276 | OrderRepository + OrderAdminService + DI kontroler + routing + nowe widoki (`orders-list`, `order-details`, `order-edit`) + cleanup legacy controls/factory/view-list |
|
||||
|
||||
### Product - szczegolowy status
|
||||
- ✅ getQuantity (ver. 0.238)
|
||||
@@ -170,15 +171,14 @@ grep -r "Product::getQuantity" .
|
||||
- [ ] getProductImg
|
||||
|
||||
### 📋 Do zrobienia
|
||||
- Order
|
||||
- ShopProduct (factory)
|
||||
|
||||
## Kolejność refaktoryzacji (priorytet)
|
||||
|
||||
1-27: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory
|
||||
1-28: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory, ShopOrder
|
||||
|
||||
Nastepne:
|
||||
28. **Order**
|
||||
29. **ShopProduct (factory)**
|
||||
|
||||
## Form Edit System
|
||||
|
||||
|
||||
@@ -36,7 +36,13 @@ Alternatywnie (Git Bash):
|
||||
Ostatnio zweryfikowano: 2026-02-15
|
||||
|
||||
```text
|
||||
OK (377 tests, 1197 assertions)
|
||||
OK (385 tests, 1246 assertions)
|
||||
```
|
||||
|
||||
Aktualizacja po stabilizacji ShopOrder / Integrations / Global Search (2026-02-15, ver. 0.277):
|
||||
```text
|
||||
Pelny suite: OK (385 tests, 1246 assertions)
|
||||
SettingsControllerTest: OK (7 tests, 10 assertions)
|
||||
```
|
||||
|
||||
Aktualizacja po migracji ShopClients (2026-02-15, ver. 0.274) - testy punktowe:
|
||||
@@ -54,11 +60,18 @@ Pelny suite po migracji ShopCategory (2026-02-15, ver. 0.275):
|
||||
OK (377 tests, 1197 assertions)
|
||||
```
|
||||
|
||||
Aktualizacja po migracji ShopOrder (2026-02-15, ver. 0.276) - testy punktowe:
|
||||
```text
|
||||
OK (8 tests, 49 assertions)
|
||||
```
|
||||
|
||||
Nowe testy dodane 2026-02-15:
|
||||
- `tests/Unit/Domain/Client/ClientRepositoryTest.php`
|
||||
- `tests/Unit/admin/Controllers/ShopClientsControllerTest.php`
|
||||
- `tests/Unit/Domain/Category/CategoryRepositoryTest.php`
|
||||
- `tests/Unit/admin/Controllers/ShopCategoryControllerTest.php`
|
||||
- `tests/Unit/Domain/Order/OrderRepositoryTest.php`
|
||||
- `tests/Unit/admin/Controllers/ShopOrderControllerTest.php`
|
||||
|
||||
## Struktura testow
|
||||
|
||||
|
||||
94
tests/Unit/Domain/Order/OrderRepositoryTest.php
Normal file
94
tests/Unit/Domain/Order/OrderRepositoryTest.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace Tests\Unit\Domain\Order;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\Order\OrderRepository;
|
||||
|
||||
class OrderRepositoryTest extends TestCase
|
||||
{
|
||||
public function testOrderStatusesReturnsMappedArray(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->method('select')
|
||||
->willReturnCallback(function ($table, $columns, $where) {
|
||||
if ($table === 'pp_shop_statuses') {
|
||||
return [
|
||||
['id' => 0, 'status' => 'Nowe'],
|
||||
['id' => 4, 'status' => 'W realizacji'],
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
$repository = new OrderRepository($mockDb);
|
||||
$statuses = $repository->orderStatuses();
|
||||
|
||||
$this->assertIsArray($statuses);
|
||||
$this->assertSame('Nowe', $statuses[0]);
|
||||
$this->assertSame('W realizacji', $statuses[4]);
|
||||
}
|
||||
|
||||
public function testNextAndPrevOrderIdReturnNullForInvalidInput(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
$mockDb->expects($this->never())->method('get');
|
||||
|
||||
$repository = new OrderRepository($mockDb);
|
||||
|
||||
$this->assertNull($repository->nextOrderId(0));
|
||||
$this->assertNull($repository->prevOrderId(0));
|
||||
}
|
||||
|
||||
public function testListForAdminReturnsItemsAndTotal(): void
|
||||
{
|
||||
$mockDb = $this->createMock(\medoo::class);
|
||||
|
||||
$resultSetCount = new class {
|
||||
public function fetchAll(): array
|
||||
{
|
||||
return [[3]];
|
||||
}
|
||||
};
|
||||
|
||||
$resultSetData = new class {
|
||||
public function fetchAll(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => 11,
|
||||
'number' => '2026/02/001',
|
||||
'date_order' => '2026-02-15 10:00:00',
|
||||
'client' => 'Jan Kowalski',
|
||||
'order_email' => 'jan@example.com',
|
||||
'address' => 'Testowa 1, 00-000 Warszawa',
|
||||
'status' => 0,
|
||||
'client_phone' => '111222333',
|
||||
'transport' => 'Kurier',
|
||||
'payment_method' => 'Przelew',
|
||||
'summary' => '123.45',
|
||||
'paid' => 1,
|
||||
'total_orders' => 2,
|
||||
],
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
$callIndex = 0;
|
||||
$mockDb->method('query')
|
||||
->willReturnCallback(function () use (&$callIndex, $resultSetCount, $resultSetData) {
|
||||
$callIndex++;
|
||||
return $callIndex === 1 ? $resultSetCount : $resultSetData;
|
||||
});
|
||||
|
||||
$repository = new OrderRepository($mockDb);
|
||||
$result = $repository->listForAdmin([], 'date_order', 'DESC', 1, 15);
|
||||
|
||||
$this->assertSame(3, $result['total']);
|
||||
$this->assertCount(1, $result['items']);
|
||||
$this->assertSame(11, $result['items'][0]['id']);
|
||||
$this->assertSame('2026/02/001', $result['items'][0]['number']);
|
||||
$this->assertSame(2, $result['items'][0]['total_orders']);
|
||||
$this->assertSame(1, $result['items'][0]['paid']);
|
||||
}
|
||||
}
|
||||
84
tests/Unit/admin/Controllers/ShopOrderControllerTest.php
Normal file
84
tests/Unit/admin/Controllers/ShopOrderControllerTest.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace Tests\Unit\admin\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\ShopOrderController;
|
||||
use Domain\Order\OrderAdminService;
|
||||
|
||||
class ShopOrderControllerTest extends TestCase
|
||||
{
|
||||
private $service;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->service = $this->createMock(OrderAdminService::class);
|
||||
$this->controller = new ShopOrderController($this->service);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsService(): void
|
||||
{
|
||||
$controller = new ShopOrderController($this->service);
|
||||
$this->assertInstanceOf(ShopOrderController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasExpectedActionMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'list'));
|
||||
$this->assertTrue(method_exists($this->controller, 'view_list'));
|
||||
$this->assertTrue(method_exists($this->controller, 'details'));
|
||||
$this->assertTrue(method_exists($this->controller, 'order_details'));
|
||||
$this->assertTrue(method_exists($this->controller, 'edit'));
|
||||
$this->assertTrue(method_exists($this->controller, 'order_edit'));
|
||||
$this->assertTrue(method_exists($this->controller, 'save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'order_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'notes_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'order_status_change'));
|
||||
$this->assertTrue(method_exists($this->controller, 'order_resend_confirmation_email'));
|
||||
$this->assertTrue(method_exists($this->controller, 'set_order_as_unpaid'));
|
||||
$this->assertTrue(method_exists($this->controller, 'set_order_as_paid'));
|
||||
$this->assertTrue(method_exists($this->controller, 'send_order_to_apilo'));
|
||||
$this->assertTrue(method_exists($this->controller, 'toggle_trustmate_send'));
|
||||
$this->assertTrue(method_exists($this->controller, 'delete'));
|
||||
$this->assertTrue(method_exists($this->controller, 'order_delete'));
|
||||
}
|
||||
|
||||
public function testViewActionsReturnString(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('details')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('order_details')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType());
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('order_edit')->getReturnType());
|
||||
}
|
||||
|
||||
public function testMutationActionsReturnVoid(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('order_save')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('notes_save')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('order_status_change')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('order_resend_confirmation_email')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('set_order_as_unpaid')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('set_order_as_paid')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('send_order_to_apilo')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('toggle_trustmate_send')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType());
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('order_delete')->getReturnType());
|
||||
}
|
||||
|
||||
public function testConstructorRequiresOrderAdminService(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ShopOrderController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(1, $params);
|
||||
$this->assertEquals('Domain\\Order\\OrderAdminService', $params[0]->getType()->getName());
|
||||
}
|
||||
}
|
||||
BIN
updates/0.20/ver_0.276.zip
Normal file
BIN
updates/0.20/ver_0.276.zip
Normal file
Binary file not shown.
4
updates/0.20/ver_0.276_files.txt
Normal file
4
updates/0.20/ver_0.276_files.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
F: ../admin/templates/shop-order/view-list.php
|
||||
F: ../autoload/admin/controls/class.ShopOrder.php
|
||||
F: ../autoload/admin/factory/class.Integrations.php
|
||||
F: ../autoload/admin/factory/class.ShopOrder.php
|
||||
@@ -1,3 +1,14 @@
|
||||
<b>ver. 0.276 - 15.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopOrder` do architektury Domain + DI (`Domain\Order\OrderRepository`, `Domain\Order\OrderAdminService`, `admin\Controllers\ShopOrderController`)
|
||||
- UPDATE - modul `/admin/shop_order/*` przepiety na nowy routing (kanoniczny URL `/admin/shop_order/list/`) i nowe widoki (`orders-list`, `order-details`, `order-edit`)
|
||||
- FIX - stabilizacja listy zamowien (`OrderRepository::listForAdmin`) oraz poprawa wygladu tabeli (`components/table-list`, wyrownanie komorek i `text-right`)
|
||||
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopOrder.php`, `autoload/admin/factory/class.ShopOrder.php`, `admin/templates/shop-order/view-list.php`
|
||||
- UPDATE - usunieta fasada `autoload/admin/factory/class.Integrations.php`; wywolania przepiete na `Domain\Integrations\IntegrationsRepository`
|
||||
- NEW - globalna wyszukiwarka admin (produkty + zamowienia) przy "Wyczysc cache" + endpoint `/admin/settings/globalSearchAjax/`
|
||||
- FIX - wyszukiwanie po pelnym imieniu i nazwisku w global search
|
||||
- UPDATE - testy: `OK (385 tests, 1246 assertions)`
|
||||
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.276.zip`, `ver_0.276_files.txt`
|
||||
<hr>
|
||||
<b>ver. 0.275 - 15.02.2026</b><br />
|
||||
- NEW - migracja modulu `ShopCategory` do architektury Domain + DI (`Domain\Category\CategoryRepository`, `admin\Controllers\ShopCategoryController`)
|
||||
- UPDATE - modul `/admin/shop_category/*` przepiety na nowy routing (kanoniczny URL `/admin/shop_category/list/`) i endpointy AJAX kontrolera (`save_categories_order`, `save_products_order`, `cookie_categories`)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 275;
|
||||
$current_ver = 276;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user