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:
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Mock Shiplogic API Routes for Testing
|
||||
* These routes simulate the Shiplogic API responses
|
||||
* To use these routes, add to routes/web.php: include base_path('routes/shiplogic-mock.php');
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('api/v1/mock')->group(function () {
|
||||
/**
|
||||
* Mock /rates endpoint
|
||||
* Returns sample service level options
|
||||
*/
|
||||
Route::post('/rates', function () {
|
||||
return response()->json([
|
||||
'id' => '550e8400-e29b-41d4-a716-446655440001',
|
||||
'company_shipment_rates' => [
|
||||
[
|
||||
'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a00',
|
||||
'company_code' => 'FEDEX',
|
||||
'company_name' => 'FedEx',
|
||||
'service_level' => [
|
||||
'id' => '123456789',
|
||||
'code' => 'FEDEX_INTERNATIONAL_PRIORITY_EXPRESS',
|
||||
'name' => 'International Priority Express',
|
||||
'description' => 'Fastest service',
|
||||
],
|
||||
'rate' => 125.50,
|
||||
'currency' => 'GBP',
|
||||
'transit_days' => '1-2',
|
||||
'delivery_guarantee_date' => '2026-01-05',
|
||||
],
|
||||
[
|
||||
'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a01',
|
||||
'company_code' => 'FEDEX',
|
||||
'company_name' => 'FedEx',
|
||||
'service_level' => [
|
||||
'id' => '123456790',
|
||||
'code' => 'FEDEX_INTERNATIONAL_ECONOMY',
|
||||
'name' => 'International Economy',
|
||||
'description' => 'Economy service (ECO)',
|
||||
],
|
||||
'rate' => 45.75,
|
||||
'currency' => 'GBP',
|
||||
'transit_days' => '5-7',
|
||||
'delivery_guarantee_date' => '2026-01-09',
|
||||
],
|
||||
[
|
||||
'id' => '9f67ff00-7a82-4d81-9481-4c5d8c8f1a02',
|
||||
'company_code' => 'DHL',
|
||||
'company_name' => 'DHL',
|
||||
'service_level' => [
|
||||
'id' => '123456791',
|
||||
'code' => 'DHL_EXPRESS_WORLDWIDE',
|
||||
'name' => 'Express Worldwide',
|
||||
'description' => 'Standard express',
|
||||
],
|
||||
'rate' => 95.25,
|
||||
'currency' => 'GBP',
|
||||
'transit_days' => '2-3',
|
||||
'delivery_guarantee_date' => '2026-01-06',
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Mock /shipments endpoint
|
||||
* Returns created shipment details
|
||||
*/
|
||||
Route::post('/shipments', function () {
|
||||
return response()->json([
|
||||
'id' => '550e8400-e29b-41d4-a716-446655440002',
|
||||
'short_tracking_reference' => 'SHP123456789',
|
||||
'tracking_reference' => 'SHP-123456789-ABC',
|
||||
'customer_reference' => 'ORDER-12345',
|
||||
'company_code' => 'FEDEX',
|
||||
'service_level' => [
|
||||
'code' => 'FEDEX_INTERNATIONAL_ECONOMY',
|
||||
'name' => 'International Economy',
|
||||
],
|
||||
'collection_min_date' => '2026-01-04',
|
||||
'delivery_min_date' => '2026-01-09',
|
||||
'status' => 'created',
|
||||
'created_at' => now()->toIso8601String(),
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Mock /shipments/label endpoint (waybill/shipping label)
|
||||
* Returns PDF binary data
|
||||
*/
|
||||
Route::get('/shipments/label', function () {
|
||||
// Return a minimal PDF (you can replace with a real PDF file)
|
||||
$pdf = $this->generateMockPdf('Shipment Label');
|
||||
|
||||
return response($pdf, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="label.pdf"',
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Mock /shipments/label/stickers endpoint
|
||||
* Returns PDF with sticker labels
|
||||
*/
|
||||
Route::get('/shipments/label/stickers', function () {
|
||||
// Return a minimal PDF (you can replace with a real PDF file)
|
||||
$pdf = $this->generateMockPdf('Shipment Sticker');
|
||||
|
||||
return response($pdf, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="stickers.pdf"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate a minimal valid PDF for testing
|
||||
* This is a very basic PDF structure that opens in most readers
|
||||
*/
|
||||
if (! function_exists('generateMockPdf')) {
|
||||
function generateMockPdf($title = 'Document')
|
||||
{
|
||||
$pdf = "%PDF-1.4\n";
|
||||
$pdf .= "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
|
||||
$pdf .= "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n";
|
||||
$pdf .= "3 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /Font << /F1 4 0 R >> >> /MediaBox [0 0 612 792] /Contents 5 0 R >>\nendobj\n";
|
||||
$pdf .= "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n";
|
||||
$pdf .= "5 0 obj\n<< /Length 44 >>\nstream\nBT\n/F1 12 Tf\n100 700 Td\n({$title}) Tj\nET\nendstream\nendobj\n";
|
||||
$pdf .= "xref\n0 6\n0000000000 65535 f\n0000000009 00000 n\n0000000058 00000 n\n0000000115 00000 n\n0000000273 00000 n\n0000000352 00000 n\n";
|
||||
$pdf .= "trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n446\n%%EOF";
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user