Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1984,7 +1984,7 @@ protected function normalizeTrackingLookup($input): ?string

$input = trim($input);

return preg_match('/^[A-Za-z0-9][A-Za-z0-9._\-]{0,99}$/', $input) === 1 ? $input : null;
return preg_match('/^[A-Za-z0-9](?:[A-Za-z0-9._\-]| (?=[A-Za-z0-9._\-])){0,99}$/', $input) === 1 ? $input : null;
}

/**
Expand Down
16 changes: 13 additions & 3 deletions server/src/Models/TrackingNumber.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,8 @@ public function getTypeAttribute()
*/
public static function generateTrackingNumber($region = 'SG', $length = 10): string
{
$company = \Fleetbase\Models\Company::where('uuid', session('company'))->withoutGlobalScopes()->first();
$companyName = $company ? strtoupper(substr($company->name, 0, 3)) : null;
$number = $companyName ?? 'FLB';
$company = \Fleetbase\Models\Company::where('uuid', session('company'))->withoutGlobalScopes()->first();
$number = static::trackingNumberPrefix($company ? $company->name : null);

for ($i = 0; $i < $length; $i++) {
$number .= mt_rand(0, 9);
Expand All @@ -195,6 +194,17 @@ public static function generateTrackingNumber($region = 'SG', $length = 10): str
return $number . strtoupper($region);
}

/**
* The tracking number prefix for a company: the letters and digits among the
* first three characters of its name, upper-cased, or FLB when none are left.
*/
public static function trackingNumberPrefix(?string $companyName): string
{
$prefix = (string) preg_replace('/[^A-Z0-9]/', '', strtoupper(substr((string) $companyName, 0, 3)));

return $prefix !== '' ? $prefix : 'FLB';
}

/**
* Generates a unique fleetbase tracking number.
*
Expand Down
2 changes: 1 addition & 1 deletion server/src/Support/TrackingPage/TrackingResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*/
class TrackingResolver
{
public const NUMBER_PATTERN = '/^[A-Za-z0-9][A-Za-z0-9._\-]{0,99}$/';
public const NUMBER_PATTERN = '/^[A-Za-z0-9](?:[A-Za-z0-9._\-]| (?=[A-Za-z0-9._\-])){0,99}$/';

/**
* The tracking number as typed, trimmed, or null when it can't be one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,14 @@ function fleetopsSuppressStrNullDeprecations(): Closure
expect($controller->lookup(new Request(['tracking' => 'TN-404']))->getData(true))->toBe($notFound)
->and($controller->trackingLookups)->toBe(['TN-404']);

// Numbers generated for companies with a space in the first three letters of
// their name ("AL 7796486490AE") reach the query; other spacing does not.
foreach (['AL 1', "AL\t1", "AL\n1", 'AL&1'] as $tracking) {
expect($controller->lookup(new Request(['tracking' => $tracking]))->getData(true))->toBe($notFound);
}
expect($controller->lookup(new Request(['tracking' => 'AL 7796486490AE']))->getData(true))->toBe($notFound)
->and($controller->trackingLookups)->toBe(['TN-404', 'AL 7796486490AE']);

$trackedOrder = fleetopsInternalOrderLifecycleOrder('tracked-order');
$trackedOrder->trackerForTest = new FleetOpsInternalOrderLifecycleTrackerFake($trackedOrder);
$controller->trackingOrder = $trackedOrder;
Expand Down
10 changes: 10 additions & 0 deletions server/tests/Unit/Models/TrackingNumberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,13 @@ public function getBarcodePNG(...$arguments)
}
}
});

test('tracking number prefixes keep only letters and digits from the company name', function () {
expect(TrackingNumber::trackingNumberPrefix('Nordic Freight'))->toBe('NOR')
->and(TrackingNumber::trackingNumberPrefix('Al Noor Transport'))->toBe('AL')
->and(TrackingNumber::trackingNumberPrefix('A&B Logistics'))->toBe('AB')
->and(TrackingNumber::trackingNumberPrefix('3PL Hub'))->toBe('3PL')
->and(TrackingNumber::trackingNumberPrefix('النور'))->toBe('FLB')
->and(TrackingNumber::trackingNumberPrefix(' '))->toBe('FLB')
->and(TrackingNumber::trackingNumberPrefix(null))->toBe('FLB');
});
20 changes: 20 additions & 0 deletions server/tests/Unit/Support/TrackingPage/TrackingResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ function trackingResolverSetting($db, string $key, mixed $value): void
->and((new TrackingResolver())->resolve('../etc'))->toBeNull();
});

test('tracking numbers may hold single inner spaces, as generated prefixes can', function () {
// generateTrackingNumber() takes the first three letters of the company name,
// so a company called "Al Noor" issues numbers like "AL 7796486490AE".
$matches = fn (string $input) => preg_match(TrackingResolver::NUMBER_PATTERN, $input) === 1;

expect(TrackingResolver::normalize('AL 7796486490AE'))->toBe('AL 7796486490AE')
->and(TrackingResolver::normalize('ABC1234567890AE'))->toBe('ABC1234567890AE')
->and(TrackingResolver::normalize(' AL 7796486490AE '))->toBe('AL 7796486490AE')
->and($matches(' AL1'))->toBeFalse()
->and($matches('AL1 '))->toBeFalse()
->and(TrackingResolver::normalize('AL 1'))->toBeNull()
->and(TrackingResolver::normalize("AL'1"))->toBeNull()
->and(TrackingResolver::normalize('AL&1'))->toBeNull()
->and(TrackingResolver::normalize("AL\t1"))->toBeNull()
->and(TrackingResolver::normalize("AL\n1"))->toBeNull()
->and(TrackingResolver::normalize('AL 1 2'))->toBe('AL 1 2')
->and(TrackingResolver::normalize(str_repeat('A B', 33) . 'A'))->toBe(str_repeat('A B', 33) . 'A')
->and(TrackingResolver::normalize(str_repeat('A B', 33) . 'AB'))->toBeNull();
});

test('each kind of tracking number opens its order for the right customer', function () {
trackingResolverFixture();
$resolver = new TrackingResolver();
Expand Down
Loading