diff --git a/app/Console/Commands/ImportPostcodes.php b/app/Console/Commands/ImportPostcodes.php new file mode 100644 index 0000000..5317b62 --- /dev/null +++ b/app/Console/Commands/ImportPostcodes.php @@ -0,0 +1,104 @@ +option('file'); + + if ($file === null || ! is_readable($file)) { + $this->error('--file is required and must be a readable path to an ONSPD CSV.'); + + return self::FAILURE; + } + + $handle = fopen($file, 'r'); + + if ($handle === false) { + throw new RuntimeException("Unable to open {$file}"); + } + + $header = fgetcsv($handle); + + if ($header === false) { + $this->error('CSV is empty.'); + fclose($handle); + + return self::FAILURE; + } + + $columns = array_change_key_case(array_flip($header), CASE_LOWER); + + foreach (['pcd', 'lat', 'long'] as $required) { + if (! isset($columns[$required])) { + $this->error("Missing required column '{$required}'."); + fclose($handle); + + return self::FAILURE; + } + } + + $hasDoterm = isset($columns['doterm']); + + DB::table('postcodes')->truncate(); + DB::table('outcodes')->truncate(); + + $buffer = []; + $imported = 0; + + while (($row = fgetcsv($handle)) !== false) { + if ($hasDoterm && trim((string) ($row[$columns['doterm']] ?? '')) !== '') { + continue; + } + + $lat = trim((string) ($row[$columns['lat']] ?? '')); + $lng = trim((string) ($row[$columns['long']] ?? '')); + + if ($lat === '' || $lng === '') { + continue; + } + + $pcd = strtoupper(preg_replace('/\s+/', '', (string) $row[$columns['pcd']])); + + if ($pcd === '' || strlen($pcd) < 5) { + continue; + } + + $buffer[] = [ + 'postcode' => $pcd, + 'outcode' => substr($pcd, 0, strlen($pcd) - 3), + 'lat' => (float) $lat, + 'lng' => (float) $lng, + ]; + + if (count($buffer) >= self::CHUNK_SIZE) { + DB::table('postcodes')->insert($buffer); + $imported += count($buffer); + $buffer = []; + } + } + + if ($buffer !== []) { + DB::table('postcodes')->insert($buffer); + $imported += count($buffer); + } + + fclose($handle); + + $this->info("Imported {$imported} postcodes."); + + return self::SUCCESS; + } +} diff --git a/tests/Feature/Console/ImportPostcodesTest.php b/tests/Feature/Console/ImportPostcodesTest.php new file mode 100644 index 0000000..0e9b076 --- /dev/null +++ b/tests/Feature/Console/ImportPostcodesTest.php @@ -0,0 +1,31 @@ +artisan('postcodes:import', ['--file' => $path]) + ->assertSuccessful(); + + expect(Postcode::count())->toBe(2) + ->and(Postcode::find('SW1A1AA')->outcode)->toBe('SW1A') + ->and(Postcode::find('M11AD')->outcode)->toBe('M1'); +});