Skip to content

Commit

Permalink
feat(webhooks): link shipment to order (#327)
Browse files Browse the repository at this point in the history
Allows showing shipment details including barcode in order mode, when
label is printed in backoffice.

INT-627
  • Loading branch information
joerivanveen authored Dec 17, 2024
1 parent bd685ff commit e61fc9d
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 6 deletions.
8 changes: 7 additions & 1 deletion src/App/Action/Backend/Shipment/UpdateShipmentsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ public function handle(Request $request): Response
$orders = $this->pdkOrderRepository->getMany($this->getOrderIds($request));
$shipments = $this->shipmentRepository->getShipments($this->getShipmentIds($request, $orders));

if ($request->get('linkFirstShipmentToFirstOrder')
&& $orders->isNotEmpty()
&& $shipments->isNotEmpty()
) {
$shipments->first()->orderId = $orders->first()->getExternalIdentifier();
}

if ($orders->isNotEmpty()) {
$orders->updateShipments($shipments);
$this->pdkOrderRepository->updateMany($orders);
Expand Down Expand Up @@ -103,4 +110,3 @@ private function addBarcodeNotes(ShipmentCollection $shipments): void
});
}
}

2 changes: 1 addition & 1 deletion src/App/Order/Collection/PdkOrderCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ private function mergeShipmentsById(ShipmentCollection $shipments, PdkOrder $ord
return $orderShipments->values();
}


/**
* @param \MyParcelNL\Pdk\Shipment\Collection\ShipmentCollection $shipments
* @param \MyParcelNL\Pdk\App\Order\Model\PdkOrder $order
Expand All @@ -170,4 +171,3 @@ private function mergeShipmentsByOrder(ShipmentCollection $shipments, PdkOrder $
return $merged;
}
}

21 changes: 21 additions & 0 deletions src/App/Order/Repository/AbstractPdkOrderRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use MyParcelNL\Pdk\App\Order\Model\PdkOrder;
use MyParcelNL\Pdk\Base\Repository\Repository;
use MyParcelNL\Pdk\Base\Support\Utils;
use MyParcelNL\Pdk\Facade\Logger;

abstract class AbstractPdkOrderRepository extends Repository implements PdkOrderRepositoryInterface
{
Expand All @@ -19,6 +20,25 @@ abstract class AbstractPdkOrderRepository extends Repository implements PdkOrder
*/
abstract public function get($input): PdkOrder;

/**
* TODO: v3.0.0 make method abstract to force implementation
*
* @param string $uuid
*
* @return null|\MyParcelNL\Pdk\App\Order\Model\PdkOrder
*/
public function getByApiIdentifier(string $uuid): ?PdkOrder
{
Logger::notice(
'Implement getByApiIdentifier, in PDK v3 it will be required.',
[
'class' => self::class,
]
);

return $this->get(['order_id' => $uuid]);
}

/**
* @param string|string[] $orderIds
*
Expand All @@ -39,6 +59,7 @@ public function update(PdkOrder $order): PdkOrder
return $this->save($order->externalIdentifier, $order);
}


/**
* @param \MyParcelNL\Pdk\App\Order\Collection\PdkOrderCollection $collection
*
Expand Down
17 changes: 13 additions & 4 deletions src/App/Webhook/Hook/ShipmentStatusChangeWebhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
namespace MyParcelNL\Pdk\App\Webhook\Hook;

use MyParcelNL\Pdk\App\Api\Backend\PdkBackendActions;
use MyParcelNL\Pdk\App\Order\Contract\PdkOrderRepositoryInterface;
use MyParcelNL\Pdk\Facade\Actions;
use MyParcelNL\Pdk\Facade\Pdk;
use MyParcelNL\Pdk\Webhook\Model\WebhookSubscription;
use Symfony\Component\HttpFoundation\Request;

Expand All @@ -20,12 +22,19 @@ public function handle(Request $request): void
{
$content = $this->getHookBody($request);

Actions::execute(PdkBackendActions::UPDATE_SHIPMENTS, [
'orderIds' => [$content['shipment_reference_identifier']],
'shipmentIds' => [$content['shipment_id']],
]);
// translate order_id (which is api identifier / uuid) to local order id for db
$order = Pdk::get(PdkOrderRepositoryInterface::class)->getByApiIdentifier($content['order_id']);

if ($order) {
Actions::execute(PdkBackendActions::UPDATE_SHIPMENTS, [
'orderIds' => [$order->getExternalIdentifier()],
'shipmentIds' => [$content['shipment_id']],
'linkFirstShipmentToFirstOrder' => true,
]);
}
}


/**
* @return string
*/
Expand Down
5 changes: 5 additions & 0 deletions tests/Bootstrap/MockPdkOrderRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ public function get($input): PdkOrder
});
}

public function getByApiIdentifier(string $uuid): ?PdkOrder
{
return new PdkOrder(['externalIdentifier' => 197]);
}

protected function getKeyPrefix(): string
{
return static::class;
Expand Down
32 changes: 32 additions & 0 deletions tests/Unit/App/Order/Repository/AbstractPdkOrderRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use MyParcelNL\Pdk\App\Order\Contract\PdkOrderRepositoryInterface;
use MyParcelNL\Pdk\App\Order\Model\PdkOrder;
use MyParcelNL\Pdk\Facade\Pdk;
use MyParcelNL\Pdk\Storage\Contract\StorageInterface;
use MyParcelNL\Pdk\Tests\Uses\UsesMockPdkInstance;
use Psr\Log\LoggerInterface;
use function MyParcelNL\Pdk\Tests\usesShared;

usesShared(new UsesMockPdkInstance());
Expand Down Expand Up @@ -44,3 +46,33 @@

expect($newOrder)->toBeInstanceOf(PdkOrder::class);
});

it('gets order by api identifier', function () {
/** @var \MyParcelNL\Pdk\Tests\Bootstrap\MockLogger $logger */
$logger = Pdk::get(LoggerInterface::class);
class MockPdkOrderRepository extends AbstractPdkOrderRepository
{
public function get($input): PdkOrder
{
return new PdkOrder();
}
}
$repository = new MockPdkOrderRepository(Pdk::get(StorageInterface::class));
$order = $repository->getByApiIdentifier('123');

expect($order)
->toBeInstanceOf(PdkOrder::class)
->and($logger->getLogs())
->toEqual([
[
'level' => 'notice',
'message' => '[PDK]: Implement getByApiIdentifier, in PDK v3 it will be required.',
'context' =>
[
'class' => 'MyParcelNL\\Pdk\\App\\Order\\Repository\\AbstractPdkOrderRepository',
],
],
]
);
});

96 changes: 96 additions & 0 deletions tests/Unit/App/Webhook/Hook/ShipmentStatusChangeWebhookTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php
/** @noinspection StaticClosureCanBeUsedInspection */

declare(strict_types=1);

namespace MyParcelNL\Pdk\App\Webhook\Hook;

use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhookManagerInterface;
use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhooksRepositoryInterface;
use MyParcelNL\Pdk\Base\Contract\CronServiceInterface;
use MyParcelNL\Pdk\Base\Support\Collection;
use MyParcelNL\Pdk\Facade\Pdk;
use MyParcelNL\Pdk\Tests\Api\Response\ExampleGetShipmentsResponse;
use MyParcelNL\Pdk\Tests\Bootstrap\MockApi;
use MyParcelNL\Pdk\Tests\Uses\UsesMockEachCron;
use MyParcelNL\Pdk\Tests\Uses\UsesMockEachLogger;
use MyParcelNL\Pdk\Tests\Uses\UsesMockPdkInstance;
use MyParcelNL\Pdk\Webhook\Collection\WebhookSubscriptionCollection;
use MyParcelNL\Pdk\Webhook\Model\WebhookSubscription;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use function MyParcelNL\Pdk\Tests\usesShared;

uses()->group('webhook');

usesShared(new UsesMockPdkInstance(), new UsesMockEachCron(), new UsesMockEachLogger());

it('handles an api request', function (string $hook, string $expectedClass, array $hookBody) {
/** @var PdkWebhooksRepositoryInterface $repository */
$repository = Pdk::get(PdkWebhooksRepositoryInterface::class);
/** @var PdkWebhookManagerInterface $webhookManager */
$webhookManager = Pdk::get(PdkWebhookManagerInterface::class);
/** @var \MyParcelNL\Pdk\Tests\Bootstrap\MockCronService $cronService */
$cronService = Pdk::get(CronServiceInterface::class);
/** @var \MyParcelNL\Pdk\Tests\Bootstrap\MockLogger $logger */
$logger = Pdk::get(LoggerInterface::class);

$repository->storeHashedUrl('https://example.com/hook/1234567890abcdef');
$repository->store(new WebhookSubscriptionCollection([['hook' => $hook, 'url' => $repository->getHashedUrl()]]));
MockApi::enqueue(new ExampleGetShipmentsResponse());

$request = Request::create(
$repository->getHashedUrl(),
Request::METHOD_POST,
[],
[],
[],
['HTTP_X_MYPARCEL_HOOK' => $hook],
json_encode([
'data' => [
'hooks' => [
array_merge(['event' => $hook], $hookBody),
],
],
])
);

$webhookManager->call($request);
$cronService->executeScheduledTask();

$logs = (new Collection($logger->getLogs()))->map(function (array $log) {
// Omit the request from the logs.
unset($log['context']['request']);
return $log;
});
// Omit the shipment response from the logs.
unset($logs[1]);

expect(array_values($logs->toArray()))->toBe([
[
'level' => 'debug',
'message' => '[PDK]: Webhook received',
'context' => [],
],
[
'level' => 'debug',
'message' => '[PDK]: Webhook processed',
'context' => ['hook' => $expectedClass],
],
]);
})->with([
'shipment updated' => [
'hook' => WebhookSubscription::SHIPMENT_STATUS_CHANGE,
'class' => ShipmentStatusChangeWebhook::class,
'body' => [
'shipment_id' => 192031595,
'account_id' => 162450,
'order_id' => 'api-uuid-string',
'shop_id' => 83287,
'status' => 2,
'barcode' => '3SHOHR763563926',
'shipment_reference_identifier' => '',
],
],
]);

0 comments on commit e61fc9d

Please sign in to comment.