Skip to content

Commit d8a0542

Browse files
authored
Merge pull request PrestaShop#41527 from tleon/Issue-24421-migrate-merchandise-return
Migrate merchandise return
2 parents bc46d6c + 85e7cd6 commit d8a0542

67 files changed

Lines changed: 2705 additions & 717 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

admin-dev/themes/new-theme/.webpack/common.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ module.exports = {
112112
maintenance: './js/pages/maintenance',
113113
manufacturer: './js/pages/manufacturer',
114114
manufacturer_address_form: './js/pages/manufacturer/manufacturer_address_form',
115-
merchandise_return: './js/pages/merchandise-return',
115+
order_return: './js/pages/order-return',
116+
order_return_form: './js/pages/order-return/form',
116117
meta: './js/pages/meta',
117118
module: './js/pages/module',
118119
module_card: './js/app/pages/module-card',
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* For the full copyright and license information, please view the
3+
* docs/licenses/LICENSE.txt file that was distributed with this source code.
4+
*/
5+
6+
/**
7+
* Merchandise return edit page — deferred deletion of returned products (Issue #27628).
8+
*
9+
* When the merchant clicks the per-row "Delete" button we open a confirmation modal but
10+
* do NOT call the back end. Instead the row is visually marked as staged-for-deletion and
11+
* the row's (id_order_detail, id_customization) pair is appended to a hidden JSON field.
12+
* The actual removal happens server-side when the form is submitted via the main "Save"
13+
* button, processed by OrderReturnFormDataHandler.
14+
*/
15+
16+
type StagedRow = {
17+
order_detail_id: number;
18+
customization_id: number;
19+
};
20+
21+
const SELECTORS = {
22+
deleteButton: '[data-role="order-return-delete-product"]',
23+
productRow: '[data-role="order-return-product-row"]',
24+
stagedField: '[data-role="order-return-staged-deletions"]',
25+
modal: '#orderReturnDeleteProductModal',
26+
modalConfirmButton: '[data-role="order-return-confirm-delete-product"]',
27+
} as const;
28+
29+
const STAGED_ROW_CSS_CLASS = 'order-return-row-staged-for-deletion';
30+
31+
class OrderReturnDeletionStager {
32+
private readonly $form: JQuery;
33+
34+
private readonly $modal: JQuery;
35+
36+
private $pendingRow: JQuery | null = null;
37+
38+
constructor($form: JQuery) {
39+
this.$form = $form;
40+
this.$modal = $(SELECTORS.modal);
41+
this.bind();
42+
}
43+
44+
private bind(): void {
45+
this.$form.on('click', SELECTORS.deleteButton, (event) => {
46+
event.preventDefault();
47+
this.$pendingRow = $(event.currentTarget).closest(SELECTORS.productRow);
48+
this.$modal.modal('show');
49+
});
50+
51+
this.$modal.on('click', SELECTORS.modalConfirmButton, () => {
52+
if (this.$pendingRow !== null) {
53+
this.stageRow(this.$pendingRow);
54+
this.$pendingRow = null;
55+
}
56+
this.$modal.modal('hide');
57+
});
58+
59+
this.$modal.on('hidden.bs.modal', () => {
60+
this.$pendingRow = null;
61+
});
62+
}
63+
64+
private stageRow($row: JQuery): void {
65+
const orderDetailId = parseInt($row.data('order-detail-id'), 10);
66+
const customizationId = parseInt($row.data('customization-id'), 10);
67+
68+
if (Number.isNaN(orderDetailId) || orderDetailId <= 0) {
69+
return;
70+
}
71+
72+
// Visual feedback — strike-through + dim, plus disable any further interaction in the row.
73+
$row.addClass(STAGED_ROW_CSS_CLASS);
74+
$row.find(SELECTORS.deleteButton).attr('disabled', 'disabled');
75+
76+
// Update the hidden JSON payload that OrderReturnFormDataHandler reads on submit.
77+
const $stagedField = this.$form.find(SELECTORS.stagedField);
78+
const existing: StagedRow[] = OrderReturnDeletionStager.parseField($stagedField.val());
79+
80+
if (!existing.some((row) => row.order_detail_id === orderDetailId
81+
&& row.customization_id === (Number.isNaN(customizationId) ? 0 : customizationId))) {
82+
existing.push({
83+
order_detail_id: orderDetailId,
84+
customization_id: Number.isNaN(customizationId) ? 0 : customizationId,
85+
});
86+
}
87+
$stagedField.val(JSON.stringify(existing));
88+
}
89+
90+
private static parseField(raw: unknown): StagedRow[] {
91+
if (typeof raw !== 'string' || raw === '') {
92+
return [];
93+
}
94+
try {
95+
const decoded = JSON.parse(raw);
96+
97+
return Array.isArray(decoded) ? decoded as StagedRow[] : [];
98+
} catch (_error) {
99+
return [];
100+
}
101+
}
102+
}
103+
104+
$(() => {
105+
const $form = $(SELECTORS.stagedField).closest('form');
106+
107+
if ($form.length === 0) {
108+
return;
109+
}
110+
// eslint-disable-next-line no-new
111+
new OrderReturnDeletionStager($form);
112+
});

admin-dev/themes/new-theme/js/pages/merchandise-return/index.ts renamed to admin-dev/themes/new-theme/js/pages/order-return/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ $(() => {
1212
);
1313

1414
const grid = new window.prestashop.component.Grid('merchandise_return');
15+
grid.addExtension(new window.prestashop.component.GridExtensions.BulkActionCheckboxExtension());
16+
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitBulkActionExtension());
1517
grid.addExtension(new window.prestashop.component.GridExtensions.SortingExtension());
1618
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersResetExtension());
19+
grid.addExtension(new window.prestashop.component.GridExtensions.ReloadListExtension());
20+
grid.addExtension(new window.prestashop.component.GridExtensions.SubmitRowActionExtension());
1721
grid.addExtension(new window.prestashop.component.GridExtensions.FiltersSubmitButtonEnablerExtension());
22+
grid.addExtension(new window.prestashop.component.GridExtensions.LinkRowActionExtension());
1823
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
/**
3+
* For the full copyright and license information, please view the
4+
* docs/licenses/LICENSE.txt file that was distributed with this source code.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace PrestaShop\PrestaShop\Adapter\OrderReturn\CommandHandler;
10+
11+
use PrestaShop\PrestaShop\Adapter\OrderReturn\Repository\OrderReturnRepository;
12+
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
13+
use PrestaShop\PrestaShop\Core\Domain\AbstractBulkCommandHandler;
14+
use PrestaShop\PrestaShop\Core\Domain\Exception\BulkCommandExceptionInterface;
15+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Command\BulkDeleteOrderReturnsCommand;
16+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\CommandHandler\BulkDeleteOrderReturnsHandlerInterface;
17+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Exception\BulkDeleteOrderReturnsException;
18+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Exception\DeleteOrderReturnException;
19+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\ValueObject\OrderReturnId;
20+
21+
#[AsCommandHandler]
22+
class BulkDeleteOrderReturnsHandler extends AbstractBulkCommandHandler implements BulkDeleteOrderReturnsHandlerInterface
23+
{
24+
private OrderReturnRepository $orderReturnRepository;
25+
26+
public function __construct(OrderReturnRepository $orderReturnRepository)
27+
{
28+
$this->orderReturnRepository = $orderReturnRepository;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function handle(BulkDeleteOrderReturnsCommand $command): void
35+
{
36+
$orderReturnIds = $command->getOrderReturnIds();
37+
if ($orderReturnIds === []) {
38+
return;
39+
}
40+
41+
$this->handleBulkAction($orderReturnIds, DeleteOrderReturnException::class);
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
protected function handleSingleAction(mixed $id, mixed $command): void
48+
{
49+
/* @var OrderReturnId $id */
50+
$this->orderReturnRepository->delete($id);
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
protected function supports($id): bool
57+
{
58+
return $id instanceof OrderReturnId;
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
protected function buildBulkException(array $caughtExceptions): BulkCommandExceptionInterface
65+
{
66+
return new BulkDeleteOrderReturnsException($caughtExceptions);
67+
}
68+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
/**
3+
* For the full copyright and license information, please view the
4+
* docs/licenses/LICENSE.txt file that was distributed with this source code.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace PrestaShop\PrestaShop\Adapter\OrderReturn\CommandHandler;
10+
11+
use PrestaShop\PrestaShop\Adapter\OrderReturn\Repository\OrderReturnRepository;
12+
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
13+
use PrestaShop\PrestaShop\Core\Domain\AbstractBulkCommandHandler;
14+
use PrestaShop\PrestaShop\Core\Domain\Exception\BulkCommandExceptionInterface;
15+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Command\BulkDeleteProductsFromOrderReturnCommand;
16+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\CommandHandler\BulkDeleteProductsFromOrderReturnHandlerInterface;
17+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Exception\BulkDeleteProductsFromOrderReturnException;
18+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Exception\CannotDeleteLastProductFromOrderReturnException;
19+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Exception\DeleteProductFromOrderReturnException;
20+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\ValueObject\OrderReturnProductId;
21+
22+
#[AsCommandHandler]
23+
class BulkDeleteProductsFromOrderReturnHandler extends AbstractBulkCommandHandler implements BulkDeleteProductsFromOrderReturnHandlerInterface
24+
{
25+
/**
26+
* @var OrderReturnRepository
27+
*/
28+
private $orderReturnRepository;
29+
30+
public function __construct(OrderReturnRepository $orderReturnRepository)
31+
{
32+
$this->orderReturnRepository = $orderReturnRepository;
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function handle(BulkDeleteProductsFromOrderReturnCommand $command): void
39+
{
40+
$productIds = $command->getProductIds();
41+
if ($productIds === []) {
42+
return;
43+
}
44+
45+
// Pre-check the legacy parity guard once, before any row deletion, so partial bulk
46+
// deletes can never leave the return empty.
47+
$currentCount = $this->orderReturnRepository->countProductLines($command->getOrderReturnId());
48+
if ($currentCount - count($productIds) < 1) {
49+
throw new CannotDeleteLastProductFromOrderReturnException(
50+
'A merchandise return must contain at least one product after deletion.'
51+
);
52+
}
53+
54+
$this->handleBulkAction($productIds, DeleteProductFromOrderReturnException::class, $command);
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
protected function handleSingleAction(mixed $id, mixed $command): void
61+
{
62+
/* @var OrderReturnProductId $id */
63+
/* @var BulkDeleteProductsFromOrderReturnCommand $command */
64+
$this->orderReturnRepository->deleteProductLine($command->getOrderReturnId(), $id);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
protected function supports($id): bool
71+
{
72+
return $id instanceof OrderReturnProductId;
73+
}
74+
75+
/**
76+
* {@inheritdoc}
77+
*/
78+
protected function buildBulkException(array $caughtExceptions): BulkCommandExceptionInterface
79+
{
80+
return new BulkDeleteProductsFromOrderReturnException($caughtExceptions);
81+
}
82+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/**
3+
* For the full copyright and license information, please view the
4+
* docs/licenses/LICENSE.txt file that was distributed with this source code.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace PrestaShop\PrestaShop\Adapter\OrderReturn\CommandHandler;
10+
11+
use PrestaShop\PrestaShop\Adapter\OrderReturn\Repository\OrderReturnRepository;
12+
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
13+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Command\DeleteOrderReturnCommand;
14+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\CommandHandler\DeleteOrderReturnHandlerInterface;
15+
16+
#[AsCommandHandler]
17+
class DeleteOrderReturnHandler implements DeleteOrderReturnHandlerInterface
18+
{
19+
private OrderReturnRepository $orderReturnRepository;
20+
21+
public function __construct(OrderReturnRepository $orderReturnRepository)
22+
{
23+
$this->orderReturnRepository = $orderReturnRepository;
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function handle(DeleteOrderReturnCommand $command): void
30+
{
31+
$this->orderReturnRepository->delete($command->getOrderReturnId());
32+
}
33+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
/**
3+
* For the full copyright and license information, please view the
4+
* docs/licenses/LICENSE.txt file that was distributed with this source code.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace PrestaShop\PrestaShop\Adapter\OrderReturn\CommandHandler;
10+
11+
use PrestaShop\PrestaShop\Adapter\OrderReturn\Repository\OrderReturnRepository;
12+
use PrestaShop\PrestaShop\Core\CommandBus\Attributes\AsCommandHandler;
13+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Command\DeleteProductFromOrderReturnCommand;
14+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\CommandHandler\DeleteProductFromOrderReturnHandlerInterface;
15+
use PrestaShop\PrestaShop\Core\Domain\OrderReturn\Exception\CannotDeleteLastProductFromOrderReturnException;
16+
17+
#[AsCommandHandler]
18+
class DeleteProductFromOrderReturnHandler implements DeleteProductFromOrderReturnHandlerInterface
19+
{
20+
/**
21+
* @var OrderReturnRepository
22+
*/
23+
private $orderReturnRepository;
24+
25+
public function __construct(OrderReturnRepository $orderReturnRepository)
26+
{
27+
$this->orderReturnRepository = $orderReturnRepository;
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function handle(DeleteProductFromOrderReturnCommand $command): void
34+
{
35+
// Mirrors AdminReturnController::postProcess: the operation must leave at least one row.
36+
if ($this->orderReturnRepository->countProductLines($command->getOrderReturnId()) <= 1) {
37+
throw new CannotDeleteLastProductFromOrderReturnException(
38+
'A merchandise return must contain at least one product.'
39+
);
40+
}
41+
42+
$this->orderReturnRepository->deleteProductLine($command->getOrderReturnId(), $command->getProductId());
43+
}
44+
}

0 commit comments

Comments
 (0)