feat: Complete Shiplogic integration with mobile-optimized ops workflow
**Shiplogic API Integration:**
- Fixed API base URL configuration (removed /api suffix)
- Implemented comprehensive request/response logging for rates and shipments endpoints
- Fixed PDF fetching: API returns S3 URLs, now downloads actual PDFs from S3
- Added tests and mock API responses for local development (routes/shiplogic-mock.php)
**Courier Service Enhancements:**
- Added redownloadShipmentPdfs() public method for re-downloading corrupted PDFs
- Enhanced error logging with full request/response bodies for debugging
- Proper binary PDF storage using Laravel Storage facade
- URL and S3 download handling for Shiplogic API responses
**Workflow & Operations:**
- Changed to manual "Ready for Collection" button instead of automatic move
- Operators now: scan QR → apply labels → click "Ready for Collection" → moves to Awaiting Collection
- Removed duplicate PDF attachments to Trello (was adding twice from two listeners)
- Fixed NotifySlackOnShipmentCreated to only handle Slack notifications
**Mobile-Optimized Ops Page:**
- Removed QR code display from order detail page
- Implemented responsive single-column layout for mobile phones
- Large touch-friendly buttons (full width, increased padding)
- Bold typography for better readability on small screens
- Larger input fields and tracking number displays
- Clear step-by-step instructions for warehouse operators
- Re-download PDF button for damaged/corrupted labels
**New Features:**
- POST /ops/orders/{uuid}/ready-for-collection endpoint
- Re-download PDFs functionality accessible from awaiting_collection and in_transit states
- Full audit logging for all operations via ops interface
- Proper error handling and user feedback
**Testing:**
- Added ShipmentCreationTest with mock HTTP client
- Created comprehensive testing guide (SHIPLOGIC_TESTING.md)
- Mock API routes for local development without hitting live API
This commit is contained in:
@@ -79,6 +79,7 @@ class OpsController extends Controller
|
||||
$order->update([
|
||||
'packing_width' => $validated['width'],
|
||||
'packing_length' => $validated['length'],
|
||||
'packing_height' => $validated['height'],
|
||||
'packing_weight' => $validated['weight'],
|
||||
'packing_completed_at' => now(),
|
||||
'packed_by' => auth()->id(),
|
||||
@@ -175,7 +176,7 @@ class OpsController extends Controller
|
||||
]);
|
||||
|
||||
// Emit event for listeners to alert ops
|
||||
// TODO: Create InspectionFailed event
|
||||
\App\Events\InspectionFailed::dispatch($order, $validated['issue_description']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
@@ -238,4 +239,118 @@ class OpsController extends Controller
|
||||
"QR-{$order->order_number}.pdf"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-download shipment PDFs from Shiplogic API
|
||||
*
|
||||
* POST /ops/orders/{id}/redownload-pdfs
|
||||
*/
|
||||
public function redownloadShipmentPdfs(Order $order)
|
||||
{
|
||||
// Authorize
|
||||
if (! auth()->user()->can('access-ops')) {
|
||||
abort(403, 'Unauthorized to access ops interface');
|
||||
}
|
||||
|
||||
// Only allow for orders with shipments
|
||||
if (! $order->courier_shipment_id) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No shipment exists for this order',
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$courierService = new \App\Services\CourierService();
|
||||
$result = $courierService->redownloadShipmentPdfs($order);
|
||||
|
||||
Log::info('Shipment PDFs re-downloaded via ops', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'order_number' => $order->order_number,
|
||||
'user_id' => auth()->id(),
|
||||
'user_name' => auth()->user()->name,
|
||||
'success' => $result['success'],
|
||||
]);
|
||||
|
||||
if ($result['success']) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $result['message'],
|
||||
'sticker_path' => $result['sticker_path'],
|
||||
'waybill_path' => $result['waybill_path'],
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $result['message'],
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to re-download shipment PDFs via ops', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to re-download PDFs: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark order as ready for collection and move Trello card
|
||||
*
|
||||
* POST /ops/orders/{id}/ready-for-collection
|
||||
*/
|
||||
public function markReadyForCollection(Order $order)
|
||||
{
|
||||
// Authorize
|
||||
if (! auth()->user()->can('access-ops')) {
|
||||
abort(403, 'Unauthorized to access ops interface');
|
||||
}
|
||||
|
||||
// Only allow for orders with shipments in ready_to_ship status
|
||||
if (! $order->courier_shipment_id || $order->status !== 'ready_to_ship') {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Order must have a shipment and be in Ready to Ship status',
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
// Update order status
|
||||
$order->update([
|
||||
'status' => 'awaiting_collection',
|
||||
'courier_status' => 'awaiting_collection',
|
||||
]);
|
||||
|
||||
// Move Trello card to Awaiting Collection
|
||||
if ($order->trello_card_id) {
|
||||
$trelloService = new \App\Services\TrelloService();
|
||||
$trelloService->moveCard($order->trello_card_id, 'Awaiting Collection');
|
||||
}
|
||||
|
||||
Log::info('Order marked ready for collection via ops', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'order_number' => $order->order_number,
|
||||
'user_id' => auth()->id(),
|
||||
'user_name' => auth()->user()->name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Order moved to Awaiting Collection',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to mark order ready for collection', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to mark ready for collection: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user