feat: add PostcodeService and price validation with DB constraints
- Add PostcodeService to resolve UK postcodes, outcodes, and place names to coordinates via postcodes.io API with 30-day caching - Add LocationResult value object for resolved location data - Add per-fuel-type price validation (80p-1050p range) to FuelPriceService with warning logs for out-of-range prices - Change price_pence column from unsignedSmallInteger to unsignedMediumInteger in station_prices tables - Add CHECK constraints (5000-50000 range) on price_pence columns as database-level guard - Improve error handling in PollFuelPrices command with file/line/trace output - Add tests for PostcodeService covering full postcodes, outcodes, place names, caching, and error handling - Add test for price validation range checks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('station_prices', function (Blueprint $table): void {
|
||||
$table->unsignedMediumInteger('price_pence')
|
||||
->comment('Price in pence × 100, e.g. 15990 = 159.90p')
|
||||
->change();
|
||||
});
|
||||
|
||||
Schema::table('station_prices_current', function (Blueprint $table): void {
|
||||
$table->unsignedMediumInteger('price_pence')
|
||||
->comment('Price in pence × 100, e.g. 15990 = 159.90p')
|
||||
->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('station_prices', function (Blueprint $table): void {
|
||||
$table->unsignedSmallInteger('price_pence')
|
||||
->comment('Price in pence × 100')
|
||||
->change();
|
||||
});
|
||||
|
||||
Schema::table('station_prices_current', function (Blueprint $table): void {
|
||||
$table->unsignedSmallInteger('price_pence')
|
||||
->comment('Price in pence × 100, e.g. 15990 = 159.90p')
|
||||
->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Add database-level CHECK constraint on price_pence (MySQL only).
|
||||
*
|
||||
* Range: 5 000–50 000 = 50p–500p per litre.
|
||||
* This is a broad last-resort guard; the application layer enforces
|
||||
* tighter per-fuel-type limits before any insert reaches the DB.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (DB::getDriverName() !== 'mysql') {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::statement('ALTER TABLE station_prices ADD CONSTRAINT chk_price_pence_range CHECK (price_pence BETWEEN 5000 AND 50000)');
|
||||
DB::statement('ALTER TABLE station_prices_current ADD CONSTRAINT chk_current_price_pence_range CHECK (price_pence BETWEEN 5000 AND 50000)');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (DB::getDriverName() !== 'mysql') {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::statement('ALTER TABLE station_prices DROP CHECK chk_price_pence_range');
|
||||
DB::statement('ALTER TABLE station_prices_current DROP CHECK chk_current_price_pence_range');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user