From f5437c4374561317dc9341c82707c2a6e4c3e269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Flambard?= Date: Mon, 9 Feb 2026 22:20:14 +0100 Subject: [PATCH 1/4] [FIX] - Removed Patch, Post, Delete endpoint because read only endpoint API --- api/app/Models/Department.php | 12 ------------ api/app/Models/Taxe.php | 12 ------------ 2 files changed, 24 deletions(-) diff --git a/api/app/Models/Department.php b/api/app/Models/Department.php index e2fba76..3813d89 100644 --- a/api/app/Models/Department.php +++ b/api/app/Models/Department.php @@ -3,12 +3,9 @@ namespace App\Models; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Patch; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -21,15 +18,6 @@ use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; uriTemplate: '/departments/{department_id}', uriVariables: ['department_id' => new Link(fromClass: Department::class, identifiers: ['department_id'])] ), - new Post(), - new Patch( - uriTemplate: '/departments/{department_id}', - uriVariables: ['department_id' => new Link(fromClass: Department::class, identifiers: ['department_id'])] - ), - new Delete( - uriTemplate: '/departments/{department_id}', - uriVariables: ['department_id' => new Link(fromClass: Department::class, identifiers: ['department_id'])] - ), ] )] #[QueryParameter(key: 'department_id', filter: EqualsFilter::class)] diff --git a/api/app/Models/Taxe.php b/api/app/Models/Taxe.php index ddcaadc..764c35b 100644 --- a/api/app/Models/Taxe.php +++ b/api/app/Models/Taxe.php @@ -3,12 +3,9 @@ namespace App\Models; use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; -use ApiPlatform\Metadata\Patch; -use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\QueryParameter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -21,15 +18,6 @@ use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; uriTemplate: '/taxes/{id}', uriVariables: ['id' => new Link(fromClass: Taxe::class, identifiers: ['id'])] ), - new Post(), - new Patch( - uriTemplate: '/taxes/{id}', - uriVariables: ['id' => new Link(fromClass: Taxe::class, identifiers: ['id'])] - ), - new Delete( - uriTemplate: '/taxes/{id}', - uriVariables: ['id' => new Link(fromClass: Taxe::class, identifiers: ['id'])] - ), ] )] #[QueryParameter(key: 'commune_code', filter: EqualsFilter::class)] -- GitLab From 3ce02fb2036f736d919a7584a65e54853091c821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Flambard?= Date: Mon, 9 Feb 2026 22:49:34 +0100 Subject: [PATCH 2/4] [DOC] - Update Swagger Doc --- api/app/Dto/CommuneCorrelation.php | 18 ++++++++++++---- api/app/Dto/RegionDistribution.php | 16 ++++++++++++--- api/app/Dto/TaxFieldStat.php | 25 ++++++++++++++++++---- api/app/Dto/TaxGlobalStat.php | 16 ++++++++++++--- api/app/Dto/TaxTimeSeries.php | 20 +++++++++++++----- api/app/Models/Department.php | 29 +++++++++++++++++++++----- api/app/Models/Taxe.php | 33 +++++++++++++++++++++++------- 7 files changed, 126 insertions(+), 31 deletions(-) diff --git a/api/app/Dto/CommuneCorrelation.php b/api/app/Dto/CommuneCorrelation.php index 18c21ff..3242bb7 100644 --- a/api/app/Dto/CommuneCorrelation.php +++ b/api/app/Dto/CommuneCorrelation.php @@ -5,23 +5,33 @@ namespace App\Dto; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use App\State\CommuneCorrelationProvider; #[ApiResource( shortName: 'CommuneCorrelation', + description: 'Tax rate vs collected amount correlation per commune in a department', paginationEnabled: false, operations: [ new Get( uriTemplate: '/communes/correlation', name: 'commune_correlation', - description: 'Get commune-level tax rate vs collected volume for scatter plot', + openapi: new Operation( + summary: 'Get commune-level tax rate vs collected volume for scatter plot', + description: 'Returns rate and collected amount for each commune in a given department, year and tax type.', + responses: [ + '200' => new Response(description: 'Array of communes with their tax rate and collected amount'), + '400' => new Response(description: 'Invalid or missing parameters (department_id, tax_type, year are required)'), + ], + ), provider: CommuneCorrelationProvider::class, ), ], )] -#[QueryParameter(key: 'department_id', schema: ['type' => 'string'], required: true)] -#[QueryParameter(key: 'tax_type', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] -#[QueryParameter(key: 'year', schema: ['type' => 'integer'], required: true)] +#[QueryParameter(key: 'department_id', description: 'Department code (e.g. 76, 75, 13)', schema: ['type' => 'string'], required: true)] +#[QueryParameter(key: 'tax_type', description: 'Tax type to analyze', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] +#[QueryParameter(key: 'year', description: 'Year of tax data (2019-2022)', schema: ['type' => 'integer'], required: true)] class CommuneCorrelation { public function __construct( diff --git a/api/app/Dto/RegionDistribution.php b/api/app/Dto/RegionDistribution.php index 699c83d..2cf011c 100644 --- a/api/app/Dto/RegionDistribution.php +++ b/api/app/Dto/RegionDistribution.php @@ -5,22 +5,32 @@ namespace App\Dto; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use App\State\RegionDistributionProvider; #[ApiResource( shortName: 'RegionDistribution', + description: 'Collected tax volume breakdown by region', paginationEnabled: false, operations: [ new Get( uriTemplate: '/regions/distribution', name: 'region_distribution', - description: 'Get regional breakdown of collected tax volumes for pie chart', + openapi: new Operation( + summary: 'Get regional breakdown of collected tax volumes for pie chart', + description: 'Returns total collected amount per region for a given tax type and optional year.', + responses: [ + '200' => new Response(description: 'Array of regions with their total collected tax amount'), + '400' => new Response(description: 'Invalid or missing parameters (tax_type is required)'), + ], + ), provider: RegionDistributionProvider::class, ), ], )] -#[QueryParameter(key: 'tax_type', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] -#[QueryParameter(key: 'year', schema: ['type' => 'integer'])] +#[QueryParameter(key: 'tax_type', description: 'Tax type to analyze', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] +#[QueryParameter(key: 'year', description: 'Year of tax data (2019-2022)', schema: ['type' => 'integer'])] class RegionDistribution { public function __construct( diff --git a/api/app/Dto/TaxFieldStat.php b/api/app/Dto/TaxFieldStat.php index f0ceecc..d23257c 100644 --- a/api/app/Dto/TaxFieldStat.php +++ b/api/app/Dto/TaxFieldStat.php @@ -5,28 +5,45 @@ namespace App\Dto; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use App\State\TaxFieldStatProvider; #[ApiResource( shortName: 'TaxFieldStat', + description: 'Sum or average of a specific tax field (tfpnb, tfpb, th, cfe)', paginationEnabled: false, operations: [ new Get( uriTemplate: '/somme/{field}', name: 'tax_sum', - description: 'Calculate the sum of a specific tax field (tfpnb, tfpb, th, cfe)', + openapi: new Operation( + summary: 'Calculate the sum of a tax field (tfpnb, tfpb, th, cfe)', + description: 'Returns the total sum of a tax amount field, optionally filtered by department and year.', + responses: [ + '200' => new Response(description: 'Sum of the requested tax field'), + '400' => new Response(description: 'Invalid field name (must be tfpnb, tfpb, th or cfe)'), + ], + ), provider: TaxFieldStatProvider::class, ), new Get( uriTemplate: '/average/{field}', name: 'tax_average', - description: 'Calculate the average of a specific tax field (tfpnb, tfpb, th, cfe)', + openapi: new Operation( + summary: 'Calculate the average of a tax field (tfpnb, tfpb, th, cfe)', + description: 'Returns the average of a tax amount field, optionally filtered by department and year.', + responses: [ + '200' => new Response(description: 'Average of the requested tax field'), + '400' => new Response(description: 'Invalid field name (must be tfpnb, tfpb, th or cfe)'), + ], + ), provider: TaxFieldStatProvider::class, ), ], )] -#[QueryParameter(key: 'department_id', schema: ['type' => 'string'])] -#[QueryParameter(key: 'year', schema: ['type' => 'integer'])] +#[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76, 75, 13)', schema: ['type' => 'string'])] +#[QueryParameter(key: 'year', description: 'Filter by year of tax data (2019-2022)', schema: ['type' => 'integer'])] class TaxFieldStat { public function __construct( diff --git a/api/app/Dto/TaxGlobalStat.php b/api/app/Dto/TaxGlobalStat.php index eef1cd4..0c0a584 100644 --- a/api/app/Dto/TaxGlobalStat.php +++ b/api/app/Dto/TaxGlobalStat.php @@ -5,22 +5,32 @@ namespace App\Dto; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use App\State\TaxGlobalStatProvider; #[ApiResource( shortName: 'TaxGlobalStat', + description: 'Aggregated tax statistics grouped by department or region', paginationEnabled: false, operations: [ new Get( uriTemplate: '/stats/global', name: 'tax_stats_global', - description: 'Get aggregated tax statistics grouped by department or region', + openapi: new Operation( + summary: 'Get aggregated tax statistics grouped by department or region', + description: 'Returns sums and averages for all tax types, grouped by department or region.', + responses: [ + '200' => new Response(description: 'Array of groups with sums and averages for each tax type'), + '400' => new Response(description: 'Invalid group_by value (must be department or region)'), + ], + ), provider: TaxGlobalStatProvider::class, ), ], )] -#[QueryParameter(key: 'group_by', schema: ['type' => 'string', 'enum' => ['department', 'region']])] -#[QueryParameter(key: 'year', schema: ['type' => 'integer'])] +#[QueryParameter(key: 'group_by', description: 'Grouping level for aggregation', schema: ['type' => 'string', 'enum' => ['department', 'region']])] +#[QueryParameter(key: 'year', description: 'Year of tax data (2019-2022)', schema: ['type' => 'integer'])] class TaxGlobalStat { public function __construct( diff --git a/api/app/Dto/TaxTimeSeries.php b/api/app/Dto/TaxTimeSeries.php index 542ff08..f460311 100644 --- a/api/app/Dto/TaxTimeSeries.php +++ b/api/app/Dto/TaxTimeSeries.php @@ -5,24 +5,34 @@ namespace App\Dto; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use App\State\TaxTimeSeriesProvider; #[ApiResource( shortName: 'TaxTimeSeries', + description: 'Average tax rate evolution by year for a given region', paginationEnabled: false, operations: [ new Get( uriTemplate: '/taxes/timeseries', name: 'tax_timeseries', - description: 'Get average tax rate evolution by year for a given region', + openapi: new Operation( + summary: 'Get average tax rate evolution by year for a given region', + description: 'Returns average tax rate per year for a given region and tax type, with optional year range.', + responses: [ + '200' => new Response(description: 'Array of years with their average tax rate'), + '400' => new Response(description: 'Invalid or missing parameters (region and tax_type are required)'), + ], + ), provider: TaxTimeSeriesProvider::class, ), ], )] -#[QueryParameter(key: 'region', schema: ['type' => 'string'], required: true)] -#[QueryParameter(key: 'tax_type', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] -#[QueryParameter(key: 'start_year', schema: ['type' => 'integer'])] -#[QueryParameter(key: 'end_year', schema: ['type' => 'integer'])] +#[QueryParameter(key: 'region', description: 'French region name (e.g. Normandie, Île-de-France)', schema: ['type' => 'string'], required: true)] +#[QueryParameter(key: 'tax_type', description: 'Tax type to analyze', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] +#[QueryParameter(key: 'start_year', description: 'Start year for the range (default: earliest available)', schema: ['type' => 'integer'])] +#[QueryParameter(key: 'end_year', description: 'End year for the range (default: latest available)', schema: ['type' => 'integer'])] class TaxTimeSeries { public function __construct( diff --git a/api/app/Models/Department.php b/api/app/Models/Department.php index 3813d89..57991a6 100644 --- a/api/app/Models/Department.php +++ b/api/app/Models/Department.php @@ -7,22 +7,41 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; #[ApiResource( + description: 'French departments reference data (100 departments)', operations: [ - new GetCollection(), + new GetCollection( + openapi: new Operation( + summary: 'List all French departments', + description: 'Returns all 100 French departments with their name and region.', + responses: [ + '200' => new Response(description: 'Collection of departments'), + ], + ), + ), new Get( uriTemplate: '/departments/{department_id}', - uriVariables: ['department_id' => new Link(fromClass: Department::class, identifiers: ['department_id'])] + uriVariables: ['department_id' => new Link(fromClass: Department::class, identifiers: ['department_id'])], + openapi: new Operation( + summary: 'Get a single department by its code', + description: 'Returns a department with its name and region.', + responses: [ + '200' => new Response(description: 'Department details'), + '404' => new Response(description: 'Department not found'), + ], + ), ), ] )] -#[QueryParameter(key: 'department_id', filter: EqualsFilter::class)] -#[QueryParameter(key: 'department_name', filter: EqualsFilter::class)] -#[QueryParameter(key: 'region', filter: EqualsFilter::class)] +#[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76, 75, 13)', filter: EqualsFilter::class)] +#[QueryParameter(key: 'department_name', description: 'Filter by department name', filter: EqualsFilter::class)] +#[QueryParameter(key: 'region', description: 'Filter by region name (e.g. Normandie)', filter: EqualsFilter::class)] class Department extends Model { use HasFactory; diff --git a/api/app/Models/Taxe.php b/api/app/Models/Taxe.php index 764c35b..e4df32c 100644 --- a/api/app/Models/Taxe.php +++ b/api/app/Models/Taxe.php @@ -7,24 +7,43 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Operation; +use ApiPlatform\OpenApi\Model\Response; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; #[ApiResource( + description: 'Tax records for French communes (TFPNB, TFPB, TH, CFE)', operations: [ - new GetCollection(), + new GetCollection( + openapi: new Operation( + summary: 'List tax records for French communes', + description: 'Returns paginated tax records with amounts and rates for each commune.', + responses: [ + '200' => new Response(description: 'Paginated collection of tax records'), + ], + ), + ), new Get( uriTemplate: '/taxes/{id}', - uriVariables: ['id' => new Link(fromClass: Taxe::class, identifiers: ['id'])] + uriVariables: ['id' => new Link(fromClass: Taxe::class, identifiers: ['id'])], + openapi: new Operation( + summary: 'Get a single tax record by ID', + description: 'Returns a tax record with all amounts and rates for a commune.', + responses: [ + '200' => new Response(description: 'Tax record details'), + '404' => new Response(description: 'Tax record not found'), + ], + ), ), ] )] -#[QueryParameter(key: 'commune_code', filter: EqualsFilter::class)] -#[QueryParameter(key: 'commune_name', filter: EqualsFilter::class)] -#[QueryParameter(key: 'department_id', filter: EqualsFilter::class, property: 'department_id')] -#[QueryParameter(key: 'year', filter: EqualsFilter::class)] -#[QueryParameter(key: 'sort[:property]', filter: EqualsFilter::class)] +#[QueryParameter(key: 'commune_code', description: 'Filter by commune INSEE code (e.g. 76540)', filter: EqualsFilter::class)] +#[QueryParameter(key: 'commune_name', description: 'Filter by commune name', filter: EqualsFilter::class)] +#[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76)', filter: EqualsFilter::class, property: 'department_id')] +#[QueryParameter(key: 'year', description: 'Filter by year (2019-2022)', filter: EqualsFilter::class)] +#[QueryParameter(key: 'sort[:property]', description: 'Sort by property (e.g. sort[year]=asc)', filter: EqualsFilter::class)] class Taxe extends Model { -- GitLab From 868f2f3e5134a150714548ca226fd6aef676b8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Flambard?= Date: Mon, 9 Feb 2026 23:47:38 +0100 Subject: [PATCH 3/4] [FIX] - fix get department with region filter --- api/app/Models/Department.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/Models/Department.php b/api/app/Models/Department.php index 57991a6..96683ee 100644 --- a/api/app/Models/Department.php +++ b/api/app/Models/Department.php @@ -41,7 +41,7 @@ use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; )] #[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76, 75, 13)', filter: EqualsFilter::class)] #[QueryParameter(key: 'department_name', description: 'Filter by department name', filter: EqualsFilter::class)] -#[QueryParameter(key: 'region', description: 'Filter by region name (e.g. Normandie)', filter: EqualsFilter::class)] +#[QueryParameter(key: 'region', description: 'Filter by region name (e.g. Normandie)', filter: EqualsFilter::class, property: 'region_name')] class Department extends Model { use HasFactory; -- GitLab From 0474b2d383ffe198344b89d42f43a482d123fd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Flambard?= Date: Tue, 10 Feb 2026 13:05:26 +0100 Subject: [PATCH 4/4] [FEAT] - Add departement code instead of this attribute being the id --- api/app/Dto/CommuneCorrelation.php | 6 +- api/app/Dto/TaxFieldStat.php | 2 +- api/app/Models/Department.php | 17 +- api/app/Models/Taxe.php | 4 +- api/app/Services/TaxeStatService.php | 18 +- api/app/State/CommuneCorrelationProvider.php | 12 +- api/app/State/TaxFieldStatProvider.php | 13 +- .../2026_01_29_192309_create_taxes_table.php | 22 +- api/database/seeders/DepartmentSeeder.php | 208 +++++++++--------- api/database/seeders/TaxeSeeder.php | 8 +- 10 files changed, 161 insertions(+), 149 deletions(-) diff --git a/api/app/Dto/CommuneCorrelation.php b/api/app/Dto/CommuneCorrelation.php index 3242bb7..4489e82 100644 --- a/api/app/Dto/CommuneCorrelation.php +++ b/api/app/Dto/CommuneCorrelation.php @@ -22,20 +22,20 @@ use App\State\CommuneCorrelationProvider; description: 'Returns rate and collected amount for each commune in a given department, year and tax type.', responses: [ '200' => new Response(description: 'Array of communes with their tax rate and collected amount'), - '400' => new Response(description: 'Invalid or missing parameters (department_id, tax_type, year are required)'), + '400' => new Response(description: 'Invalid or missing parameters (department_code, tax_type, year are required)'), ], ), provider: CommuneCorrelationProvider::class, ), ], )] -#[QueryParameter(key: 'department_id', description: 'Department code (e.g. 76, 75, 13)', schema: ['type' => 'string'], required: true)] +#[QueryParameter(key: 'department_code', description: 'Department code (e.g. 76, 75, 2A)', schema: ['type' => 'string'], required: true)] #[QueryParameter(key: 'tax_type', description: 'Tax type to analyze', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] #[QueryParameter(key: 'year', description: 'Year of tax data (2019-2022)', schema: ['type' => 'integer'], required: true)] class CommuneCorrelation { public function __construct( - public readonly string $department_id = '', + public readonly string $department_code = '', public readonly string $tax_type = '', public readonly ?int $year = null, public readonly array $data = [], diff --git a/api/app/Dto/TaxFieldStat.php b/api/app/Dto/TaxFieldStat.php index d23257c..e286517 100644 --- a/api/app/Dto/TaxFieldStat.php +++ b/api/app/Dto/TaxFieldStat.php @@ -42,7 +42,7 @@ use App\State\TaxFieldStatProvider; ), ], )] -#[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76, 75, 13)', schema: ['type' => 'string'])] +#[QueryParameter(key: 'department_code', description: 'Filter by department code (e.g. 76, 75, 2A)', schema: ['type' => 'string'])] #[QueryParameter(key: 'year', description: 'Filter by year of tax data (2019-2022)', schema: ['type' => 'integer'])] class TaxFieldStat { diff --git a/api/app/Models/Department.php b/api/app/Models/Department.php index 96683ee..70a99f4 100644 --- a/api/app/Models/Department.php +++ b/api/app/Models/Department.php @@ -26,11 +26,11 @@ use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; ), ), new Get( - uriTemplate: '/departments/{department_id}', - uriVariables: ['department_id' => new Link(fromClass: Department::class, identifiers: ['department_id'])], + uriTemplate: '/departments/{id}', + uriVariables: ['id' => new Link(fromClass: Department::class, identifiers: ['id'])], openapi: new Operation( - summary: 'Get a single department by its code', - description: 'Returns a department with its name and region.', + summary: 'Get a single department by its ID', + description: 'Returns a department with its code, name and region.', responses: [ '200' => new Response(description: 'Department details'), '404' => new Response(description: 'Department not found'), @@ -39,18 +39,15 @@ use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; ), ] )] -#[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76, 75, 13)', filter: EqualsFilter::class)] +#[QueryParameter(key: 'department_code', description: 'Filter by department code (e.g. 76, 75, 13)', filter: EqualsFilter::class)] #[QueryParameter(key: 'department_name', description: 'Filter by department name', filter: EqualsFilter::class)] #[QueryParameter(key: 'region', description: 'Filter by region name (e.g. Normandie)', filter: EqualsFilter::class, property: 'region_name')] class Department extends Model { use HasFactory; - protected $primaryKey = 'department_id'; - public $incrementing = false; - protected $keyType = 'string'; protected $fillable = [ - 'department_id', + 'department_code', 'department_name', 'region_name' ]; @@ -65,6 +62,6 @@ class Department extends Model */ public function taxes() { - return $this->hasMany(Taxe::class, 'department_id', 'department_id'); + return $this->hasMany(Taxe::class, 'department_id'); } } diff --git a/api/app/Models/Taxe.php b/api/app/Models/Taxe.php index e4df32c..61177d1 100644 --- a/api/app/Models/Taxe.php +++ b/api/app/Models/Taxe.php @@ -41,7 +41,7 @@ use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; )] #[QueryParameter(key: 'commune_code', description: 'Filter by commune INSEE code (e.g. 76540)', filter: EqualsFilter::class)] #[QueryParameter(key: 'commune_name', description: 'Filter by commune name', filter: EqualsFilter::class)] -#[QueryParameter(key: 'department_id', description: 'Filter by department code (e.g. 76)', filter: EqualsFilter::class, property: 'department_id')] +#[QueryParameter(key: 'department_id', description: 'Filter by department ID (integer)', filter: EqualsFilter::class, property: 'department_id')] #[QueryParameter(key: 'year', description: 'Filter by year (2019-2022)', filter: EqualsFilter::class)] #[QueryParameter(key: 'sort[:property]', description: 'Sort by property (e.g. sort[year]=asc)', filter: EqualsFilter::class)] @@ -112,6 +112,6 @@ class Taxe extends Model */ public function department() { - return $this->belongsTo(Department::class, 'department_id', 'department_id'); + return $this->belongsTo(Department::class, 'department_id'); } } diff --git a/api/app/Services/TaxeStatService.php b/api/app/Services/TaxeStatService.php index 83d61b4..765edb3 100644 --- a/api/app/Services/TaxeStatService.php +++ b/api/app/Services/TaxeStatService.php @@ -11,7 +11,7 @@ class TaxeStatService /** * Calculate sum for a specific tax field with optional filters. */ - public function calculateSum(string $field, ?string $departmentId = null, ?int $year = null): float + public function calculateSum(string $field, ?int $departmentId = null, ?int $year = null): float { $column = $this->validateAndGetColumn($field); $query = $this->buildBaseQuery($departmentId, $year); @@ -22,7 +22,7 @@ class TaxeStatService /** * Calculate average for a specific tax field with optional filters. */ - public function calculateAverage(string $field, ?string $departmentId = null, ?int $year = null): float + public function calculateAverage(string $field, ?int $departmentId = null, ?int $year = null): float { $column = $this->validateAndGetColumn($field); $query = $this->buildBaseQuery($departmentId, $year); @@ -41,15 +41,15 @@ class TaxeStatService } $query = Taxe::query(); + $query->join('departments', 'taxes.department_id', '=', 'departments.id'); // Setup grouping if ($groupBy === 'region') { - $query->join('departments', 'taxes.department_id', '=', 'departments.department_id'); $query->groupBy('departments.region_name'); $query->select('departments.region_name as location'); } else { - $query->groupBy('taxes.department_id'); - $query->select('taxes.department_id as location'); + $query->groupBy('departments.department_code'); + $query->select('departments.department_code as location'); } // Apply year filter if provided @@ -73,7 +73,7 @@ class TaxeStatService $rateColumn = $taxType . '_percentage'; $query = Taxe::query() - ->join('departments', 'taxes.department_id', '=', 'departments.department_id') + ->join('departments', 'taxes.department_id', '=', 'departments.id') ->where('departments.region_name', $region) ->groupBy('taxes.year') ->orderBy('taxes.year') @@ -92,7 +92,7 @@ class TaxeStatService /** * Get correlation data: rate vs amount per commune for scatter plot. */ - public function getCorrelation(string $departmentId, string $taxType, int $year): array + public function getCorrelation(int $departmentId, string $taxType, int $year): array { $this->validateAndGetColumn($taxType); $rateColumn = $taxType . '_percentage'; @@ -115,7 +115,7 @@ class TaxeStatService $amountColumn = $taxType . '_amount'; $query = Taxe::query() - ->join('departments', 'taxes.department_id', '=', 'departments.department_id') + ->join('departments', 'taxes.department_id', '=', 'departments.id') ->groupBy('departments.region_name') ->select('departments.region_name as region', DB::raw("SUM(taxes.{$amountColumn}) as total_amount")) ->orderByDesc('total_amount'); @@ -142,7 +142,7 @@ class TaxeStatService /** * Build base query with optional filters. */ - private function buildBaseQuery(?string $departmentId, ?int $year) + private function buildBaseQuery(?int $departmentId, ?int $year) { $query = Taxe::query(); diff --git a/api/app/State/CommuneCorrelationProvider.php b/api/app/State/CommuneCorrelationProvider.php index 525a7cd..d85eff6 100644 --- a/api/app/State/CommuneCorrelationProvider.php +++ b/api/app/State/CommuneCorrelationProvider.php @@ -5,6 +5,7 @@ namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Dto\CommuneCorrelation; +use App\Models\Department; use App\Models\Taxe; use App\Services\TaxeStatService; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -19,12 +20,17 @@ class CommuneCorrelationProvider implements ProviderInterface { $request = $context['request'] ?? null; - $departmentId = $request?->query->get('department_id'); + $departmentCode = $request?->query->get('department_code'); $taxType = $request?->query->get('tax_type'); $year = $request?->query->get('year'); + if (!$departmentCode) { + throw new BadRequestHttpException('The "department_code" parameter is required.'); + } + + $departmentId = Department::where('department_code', $departmentCode)->value('id'); if (!$departmentId) { - throw new BadRequestHttpException('The "department_id" parameter is required.'); + throw new BadRequestHttpException(sprintf('Department "%s" not found.', $departmentCode)); } if (!$taxType || !in_array($taxType, Taxe::ALLOWED_STAT_FIELDS, true)) { @@ -42,7 +48,7 @@ class CommuneCorrelationProvider implements ProviderInterface $data = $this->taxeStatService->getCorrelation($departmentId, $taxType, $yearInt); return new CommuneCorrelation( - department_id: $departmentId, + department_code: $departmentCode, tax_type: $taxType, year: $yearInt, data: $data, diff --git a/api/app/State/TaxFieldStatProvider.php b/api/app/State/TaxFieldStatProvider.php index 8f53966..667193a 100644 --- a/api/app/State/TaxFieldStatProvider.php +++ b/api/app/State/TaxFieldStatProvider.php @@ -5,6 +5,7 @@ namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Dto\TaxFieldStat; +use App\Models\Department; use App\Models\Taxe; use App\Services\TaxeStatService; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -26,12 +27,20 @@ class TaxFieldStatProvider implements ProviderInterface } $request = $context['request'] ?? null; - $departmentId = $request?->query->get('department_id'); + $departmentCode = $request?->query->get('department_code'); $year = $request?->query->get('year'); $yearInt = $year !== null ? (int) $year : null; + $departmentId = null; + if ($departmentCode !== null) { + $departmentId = Department::where('department_code', $departmentCode)->value('id'); + if (!$departmentId) { + throw new BadRequestHttpException(sprintf('Department "%s" not found.', $departmentCode)); + } + } + $filters = array_filter([ - 'department_id' => $departmentId, + 'department_code' => $departmentCode, 'year' => $yearInt, ], fn ($v) => $v !== null); diff --git a/api/database/migrations/2026_01_29_192309_create_taxes_table.php b/api/database/migrations/2026_01_29_192309_create_taxes_table.php index 698e33c..d25a06c 100644 --- a/api/database/migrations/2026_01_29_192309_create_taxes_table.php +++ b/api/database/migrations/2026_01_29_192309_create_taxes_table.php @@ -11,11 +11,18 @@ return new class extends Migration */ public function up(): void { + Schema::create('departments', function (Blueprint $table) { + $table->id(); + $table->string('department_code', 3)->unique(); + $table->string('department_name'); + $table->string('region_name'); + }); + Schema::create('taxes', function (Blueprint $table) { $table->id(); - $table->string('commune_code', 5); + $table->string('commune_code', 5); $table->string('commune_name'); - $table->string('department_id', 3); + $table->foreignId('department_id')->constrained('departments'); $table->decimal('tfpnb_amount', 15, 2); $table->decimal('tfpnb_percentage', 15, 5); $table->decimal('tfpb_amount', 15, 2); @@ -26,17 +33,6 @@ return new class extends Migration $table->decimal('cfe_percentage', 15, 5); $table->integer('year'); }); - - Schema::create('departments', function (Blueprint $table) { - $table->string('department_id', 3)->primary(); - $table->string('department_name'); - $table->string('region_name'); - }); - - - Schema::table('taxes', function (Blueprint $table) { - $table->foreign('department_id')->references('department_id')->on('departments'); - }); } /** * Reverse the migrations. diff --git a/api/database/seeders/DepartmentSeeder.php b/api/database/seeders/DepartmentSeeder.php index 4615f52..82185c1 100644 --- a/api/database/seeders/DepartmentSeeder.php +++ b/api/database/seeders/DepartmentSeeder.php @@ -10,114 +10,114 @@ class DepartmentSeeder extends Seeder public function run(): void { $departments = [ - ['department_id' => '01', 'department_name' => 'Ain', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '02', 'department_name' => 'Aisne', 'region_name' => 'Hauts-de-France'], - ['department_id' => '03', 'department_name' => 'Allier', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '04', 'department_name' => 'Alpes-de-Haute-Provence', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], - ['department_id' => '05', 'department_name' => 'Hautes-Alpes', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], - ['department_id' => '06', 'department_name' => 'Alpes-Maritimes', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], - ['department_id' => '07', 'department_name' => 'Ardèche', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '08', 'department_name' => 'Ardennes', 'region_name' => 'Grand Est'], - ['department_id' => '09', 'department_name' => 'Ariège', 'region_name' => 'Occitanie'], - ['department_id' => '10', 'department_name' => 'Aube', 'region_name' => 'Grand Est'], - ['department_id' => '11', 'department_name' => 'Aude', 'region_name' => 'Occitanie'], - ['department_id' => '12', 'department_name' => 'Aveyron', 'region_name' => 'Occitanie'], - ['department_id' => '13', 'department_name' => 'Bouches-du-Rhône', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], - ['department_id' => '14', 'department_name' => 'Calvados', 'region_name' => 'Normandie'], - ['department_id' => '15', 'department_name' => 'Cantal', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '16', 'department_name' => 'Charente', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '17', 'department_name' => 'Charente-Maritime', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '18', 'department_name' => 'Cher', 'region_name' => 'Centre-Val de Loire'], - ['department_id' => '19', 'department_name' => 'Corrèze', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '2A', 'department_name' => 'Corse-du-Sud', 'region_name' => 'Corse'], - ['department_id' => '2B', 'department_name' => 'Haute-Corse', 'region_name' => 'Corse'], - ['department_id' => '21', 'department_name' => 'Côte-d\'Or', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '22', 'department_name' => 'Côtes-d\'Armor', 'region_name' => 'Bretagne'], - ['department_id' => '23', 'department_name' => 'Creuse', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '24', 'department_name' => 'Dordogne', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '25', 'department_name' => 'Doubs', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '26', 'department_name' => 'Drôme', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '27', 'department_name' => 'Eure', 'region_name' => 'Normandie'], - ['department_id' => '28', 'department_name' => 'Eure-et-Loir', 'region_name' => 'Centre-Val de Loire'], - ['department_id' => '29', 'department_name' => 'Finistère', 'region_name' => 'Bretagne'], - ['department_id' => '30', 'department_name' => 'Gard', 'region_name' => 'Occitanie'], - ['department_id' => '31', 'department_name' => 'Haute-Garonne', 'region_name' => 'Occitanie'], - ['department_id' => '32', 'department_name' => 'Gers', 'region_name' => 'Occitanie'], - ['department_id' => '33', 'department_name' => 'Gironde', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '34', 'department_name' => 'Hérault', 'region_name' => 'Occitanie'], - ['department_id' => '35', 'department_name' => 'Ille-et-Vilaine', 'region_name' => 'Bretagne'], - ['department_id' => '36', 'department_name' => 'Indre', 'region_name' => 'Centre-Val de Loire'], - ['department_id' => '37', 'department_name' => 'Indre-et-Loire', 'region_name' => 'Centre-Val de Loire'], - ['department_id' => '38', 'department_name' => 'Isère', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '39', 'department_name' => 'Jura', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '40', 'department_name' => 'Landes', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '41', 'department_name' => 'Loir-et-Cher', 'region_name' => 'Centre-Val de Loire'], - ['department_id' => '42', 'department_name' => 'Loire', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '43', 'department_name' => 'Haute-Loire', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '44', 'department_name' => 'Loire-Atlantique', 'region_name' => 'Pays de la Loire'], - ['department_id' => '45', 'department_name' => 'Loiret', 'region_name' => 'Centre-Val de Loire'], - ['department_id' => '46', 'department_name' => 'Lot', 'region_name' => 'Occitanie'], - ['department_id' => '47', 'department_name' => 'Lot-et-Garonne', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '48', 'department_name' => 'Lozère', 'region_name' => 'Occitanie'], - ['department_id' => '49', 'department_name' => 'Maine-et-Loire', 'region_name' => 'Pays de la Loire'], - ['department_id' => '50', 'department_name' => 'Manche', 'region_name' => 'Normandie'], - ['department_id' => '51', 'department_name' => 'Marne', 'region_name' => 'Grand Est'], - ['department_id' => '52', 'department_name' => 'Haute-Marne', 'region_name' => 'Grand Est'], - ['department_id' => '53', 'department_name' => 'Mayenne', 'region_name' => 'Pays de la Loire'], - ['department_id' => '54', 'department_name' => 'Meurthe-et-Moselle', 'region_name' => 'Grand Est'], - ['department_id' => '55', 'department_name' => 'Meuse', 'region_name' => 'Grand Est'], - ['department_id' => '56', 'department_name' => 'Morbihan', 'region_name' => 'Bretagne'], - ['department_id' => '57', 'department_name' => 'Moselle', 'region_name' => 'Grand Est'], - ['department_id' => '58', 'department_name' => 'Nièvre', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '59', 'department_name' => 'Nord', 'region_name' => 'Hauts-de-France'], - ['department_id' => '60', 'department_name' => 'Oise', 'region_name' => 'Hauts-de-France'], - ['department_id' => '61', 'department_name' => 'Orne', 'region_name' => 'Normandie'], - ['department_id' => '62', 'department_name' => 'Pas-de-Calais', 'region_name' => 'Hauts-de-France'], - ['department_id' => '63', 'department_name' => 'Puy-de-Dôme', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '64', 'department_name' => 'Pyrénées-Atlantiques', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '65', 'department_name' => 'Hautes-Pyrénées', 'region_name' => 'Occitanie'], - ['department_id' => '66', 'department_name' => 'Pyrénées-Orientales', 'region_name' => 'Occitanie'], - ['department_id' => '67', 'department_name' => 'Bas-Rhin', 'region_name' => 'Grand Est'], - ['department_id' => '68', 'department_name' => 'Haut-Rhin', 'region_name' => 'Grand Est'], - ['department_id' => '69', 'department_name' => 'Rhône', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '70', 'department_name' => 'Haute-Saône', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '71', 'department_name' => 'Saône-et-Loire', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '72', 'department_name' => 'Sarthe', 'region_name' => 'Pays de la Loire'], - ['department_id' => '73', 'department_name' => 'Savoie', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '74', 'department_name' => 'Haute-Savoie', 'region_name' => 'Auvergne-Rhône-Alpes'], - ['department_id' => '75', 'department_name' => 'Paris', 'region_name' => 'Île-de-France'], - ['department_id' => '76', 'department_name' => 'Seine-Maritime', 'region_name' => 'Normandie'], - ['department_id' => '77', 'department_name' => 'Seine-et-Marne', 'region_name' => 'Île-de-France'], - ['department_id' => '78', 'department_name' => 'Yvelines', 'region_name' => 'Île-de-France'], - ['department_id' => '79', 'department_name' => 'Deux-Sèvres', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '80', 'department_name' => 'Somme', 'region_name' => 'Hauts-de-France'], - ['department_id' => '81', 'department_name' => 'Tarn', 'region_name' => 'Occitanie'], - ['department_id' => '82', 'department_name' => 'Tarn-et-Garonne', 'region_name' => 'Occitanie'], - ['department_id' => '83', 'department_name' => 'Var', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], - ['department_id' => '84', 'department_name' => 'Vaucluse', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], - ['department_id' => '85', 'department_name' => 'Vendée', 'region_name' => 'Pays de la Loire'], - ['department_id' => '86', 'department_name' => 'Vienne', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '87', 'department_name' => 'Haute-Vienne', 'region_name' => 'Nouvelle-Aquitaine'], - ['department_id' => '88', 'department_name' => 'Vosges', 'region_name' => 'Grand Est'], - ['department_id' => '89', 'department_name' => 'Yonne', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '90', 'department_name' => 'Territoire de Belfort', 'region_name' => 'Bourgogne-Franche-Comté'], - ['department_id' => '91', 'department_name' => 'Essonne', 'region_name' => 'Île-de-France'], - ['department_id' => '92', 'department_name' => 'Hauts-de-Seine', 'region_name' => 'Île-de-France'], - ['department_id' => '93', 'department_name' => 'Seine-Saint-Denis', 'region_name' => 'Île-de-France'], - ['department_id' => '94', 'department_name' => 'Val-de-Marne', 'region_name' => 'Île-de-France'], - ['department_id' => '95', 'department_name' => 'Val-d\'Oise', 'region_name' => 'Île-de-France'], - ['department_id' => '971', 'department_name' => 'Guadeloupe', 'region_name' => 'Guadeloupe'], - ['department_id' => '972', 'department_name' => 'Martinique', 'region_name' => 'Martinique'], - ['department_id' => '973', 'department_name' => 'Guyane', 'region_name' => 'Guyane'], - ['department_id' => '974', 'department_name' => 'La Réunion', 'region_name' => 'La Réunion'], - ['department_id' => '976', 'department_name' => 'Mayotte', 'region_name' => 'Mayotte'], - ['department_id' => '977', 'department_name' => 'Saint-Barthélemy', 'region_name' => 'Saint-Barthélemy'], - ['department_id' => '978', 'department_name' => 'Saint-Martin', 'region_name' => 'Saint-Martin'], + ['department_code' => '01', 'department_name' => 'Ain', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '02', 'department_name' => 'Aisne', 'region_name' => 'Hauts-de-France'], + ['department_code' => '03', 'department_name' => 'Allier', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '04', 'department_name' => 'Alpes-de-Haute-Provence', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], + ['department_code' => '05', 'department_name' => 'Hautes-Alpes', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], + ['department_code' => '06', 'department_name' => 'Alpes-Maritimes', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], + ['department_code' => '07', 'department_name' => 'Ardèche', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '08', 'department_name' => 'Ardennes', 'region_name' => 'Grand Est'], + ['department_code' => '09', 'department_name' => 'Ariège', 'region_name' => 'Occitanie'], + ['department_code' => '10', 'department_name' => 'Aube', 'region_name' => 'Grand Est'], + ['department_code' => '11', 'department_name' => 'Aude', 'region_name' => 'Occitanie'], + ['department_code' => '12', 'department_name' => 'Aveyron', 'region_name' => 'Occitanie'], + ['department_code' => '13', 'department_name' => 'Bouches-du-Rhône', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], + ['department_code' => '14', 'department_name' => 'Calvados', 'region_name' => 'Normandie'], + ['department_code' => '15', 'department_name' => 'Cantal', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '16', 'department_name' => 'Charente', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '17', 'department_name' => 'Charente-Maritime', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '18', 'department_name' => 'Cher', 'region_name' => 'Centre-Val de Loire'], + ['department_code' => '19', 'department_name' => 'Corrèze', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '2A', 'department_name' => 'Corse-du-Sud', 'region_name' => 'Corse'], + ['department_code' => '2B', 'department_name' => 'Haute-Corse', 'region_name' => 'Corse'], + ['department_code' => '21', 'department_name' => 'Côte-d\'Or', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '22', 'department_name' => 'Côtes-d\'Armor', 'region_name' => 'Bretagne'], + ['department_code' => '23', 'department_name' => 'Creuse', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '24', 'department_name' => 'Dordogne', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '25', 'department_name' => 'Doubs', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '26', 'department_name' => 'Drôme', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '27', 'department_name' => 'Eure', 'region_name' => 'Normandie'], + ['department_code' => '28', 'department_name' => 'Eure-et-Loir', 'region_name' => 'Centre-Val de Loire'], + ['department_code' => '29', 'department_name' => 'Finistère', 'region_name' => 'Bretagne'], + ['department_code' => '30', 'department_name' => 'Gard', 'region_name' => 'Occitanie'], + ['department_code' => '31', 'department_name' => 'Haute-Garonne', 'region_name' => 'Occitanie'], + ['department_code' => '32', 'department_name' => 'Gers', 'region_name' => 'Occitanie'], + ['department_code' => '33', 'department_name' => 'Gironde', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '34', 'department_name' => 'Hérault', 'region_name' => 'Occitanie'], + ['department_code' => '35', 'department_name' => 'Ille-et-Vilaine', 'region_name' => 'Bretagne'], + ['department_code' => '36', 'department_name' => 'Indre', 'region_name' => 'Centre-Val de Loire'], + ['department_code' => '37', 'department_name' => 'Indre-et-Loire', 'region_name' => 'Centre-Val de Loire'], + ['department_code' => '38', 'department_name' => 'Isère', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '39', 'department_name' => 'Jura', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '40', 'department_name' => 'Landes', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '41', 'department_name' => 'Loir-et-Cher', 'region_name' => 'Centre-Val de Loire'], + ['department_code' => '42', 'department_name' => 'Loire', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '43', 'department_name' => 'Haute-Loire', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '44', 'department_name' => 'Loire-Atlantique', 'region_name' => 'Pays de la Loire'], + ['department_code' => '45', 'department_name' => 'Loiret', 'region_name' => 'Centre-Val de Loire'], + ['department_code' => '46', 'department_name' => 'Lot', 'region_name' => 'Occitanie'], + ['department_code' => '47', 'department_name' => 'Lot-et-Garonne', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '48', 'department_name' => 'Lozère', 'region_name' => 'Occitanie'], + ['department_code' => '49', 'department_name' => 'Maine-et-Loire', 'region_name' => 'Pays de la Loire'], + ['department_code' => '50', 'department_name' => 'Manche', 'region_name' => 'Normandie'], + ['department_code' => '51', 'department_name' => 'Marne', 'region_name' => 'Grand Est'], + ['department_code' => '52', 'department_name' => 'Haute-Marne', 'region_name' => 'Grand Est'], + ['department_code' => '53', 'department_name' => 'Mayenne', 'region_name' => 'Pays de la Loire'], + ['department_code' => '54', 'department_name' => 'Meurthe-et-Moselle', 'region_name' => 'Grand Est'], + ['department_code' => '55', 'department_name' => 'Meuse', 'region_name' => 'Grand Est'], + ['department_code' => '56', 'department_name' => 'Morbihan', 'region_name' => 'Bretagne'], + ['department_code' => '57', 'department_name' => 'Moselle', 'region_name' => 'Grand Est'], + ['department_code' => '58', 'department_name' => 'Nièvre', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '59', 'department_name' => 'Nord', 'region_name' => 'Hauts-de-France'], + ['department_code' => '60', 'department_name' => 'Oise', 'region_name' => 'Hauts-de-France'], + ['department_code' => '61', 'department_name' => 'Orne', 'region_name' => 'Normandie'], + ['department_code' => '62', 'department_name' => 'Pas-de-Calais', 'region_name' => 'Hauts-de-France'], + ['department_code' => '63', 'department_name' => 'Puy-de-Dôme', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '64', 'department_name' => 'Pyrénées-Atlantiques', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '65', 'department_name' => 'Hautes-Pyrénées', 'region_name' => 'Occitanie'], + ['department_code' => '66', 'department_name' => 'Pyrénées-Orientales', 'region_name' => 'Occitanie'], + ['department_code' => '67', 'department_name' => 'Bas-Rhin', 'region_name' => 'Grand Est'], + ['department_code' => '68', 'department_name' => 'Haut-Rhin', 'region_name' => 'Grand Est'], + ['department_code' => '69', 'department_name' => 'Rhône', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '70', 'department_name' => 'Haute-Saône', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '71', 'department_name' => 'Saône-et-Loire', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '72', 'department_name' => 'Sarthe', 'region_name' => 'Pays de la Loire'], + ['department_code' => '73', 'department_name' => 'Savoie', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '74', 'department_name' => 'Haute-Savoie', 'region_name' => 'Auvergne-Rhône-Alpes'], + ['department_code' => '75', 'department_name' => 'Paris', 'region_name' => 'Île-de-France'], + ['department_code' => '76', 'department_name' => 'Seine-Maritime', 'region_name' => 'Normandie'], + ['department_code' => '77', 'department_name' => 'Seine-et-Marne', 'region_name' => 'Île-de-France'], + ['department_code' => '78', 'department_name' => 'Yvelines', 'region_name' => 'Île-de-France'], + ['department_code' => '79', 'department_name' => 'Deux-Sèvres', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '80', 'department_name' => 'Somme', 'region_name' => 'Hauts-de-France'], + ['department_code' => '81', 'department_name' => 'Tarn', 'region_name' => 'Occitanie'], + ['department_code' => '82', 'department_name' => 'Tarn-et-Garonne', 'region_name' => 'Occitanie'], + ['department_code' => '83', 'department_name' => 'Var', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], + ['department_code' => '84', 'department_name' => 'Vaucluse', 'region_name' => 'Provence-Alpes-Côte d\'Azur'], + ['department_code' => '85', 'department_name' => 'Vendée', 'region_name' => 'Pays de la Loire'], + ['department_code' => '86', 'department_name' => 'Vienne', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '87', 'department_name' => 'Haute-Vienne', 'region_name' => 'Nouvelle-Aquitaine'], + ['department_code' => '88', 'department_name' => 'Vosges', 'region_name' => 'Grand Est'], + ['department_code' => '89', 'department_name' => 'Yonne', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '90', 'department_name' => 'Territoire de Belfort', 'region_name' => 'Bourgogne-Franche-Comté'], + ['department_code' => '91', 'department_name' => 'Essonne', 'region_name' => 'Île-de-France'], + ['department_code' => '92', 'department_name' => 'Hauts-de-Seine', 'region_name' => 'Île-de-France'], + ['department_code' => '93', 'department_name' => 'Seine-Saint-Denis', 'region_name' => 'Île-de-France'], + ['department_code' => '94', 'department_name' => 'Val-de-Marne', 'region_name' => 'Île-de-France'], + ['department_code' => '95', 'department_name' => 'Val-d\'Oise', 'region_name' => 'Île-de-France'], + ['department_code' => '971', 'department_name' => 'Guadeloupe', 'region_name' => 'Guadeloupe'], + ['department_code' => '972', 'department_name' => 'Martinique', 'region_name' => 'Martinique'], + ['department_code' => '973', 'department_name' => 'Guyane', 'region_name' => 'Guyane'], + ['department_code' => '974', 'department_name' => 'La Réunion', 'region_name' => 'La Réunion'], + ['department_code' => '976', 'department_name' => 'Mayotte', 'region_name' => 'Mayotte'], + ['department_code' => '977', 'department_name' => 'Saint-Barthélemy', 'region_name' => 'Saint-Barthélemy'], + ['department_code' => '978', 'department_name' => 'Saint-Martin', 'region_name' => 'Saint-Martin'], ]; foreach ($departments as $department) { Department::updateOrCreate( - ['department_id' => $department['department_id']], + ['department_code' => $department['department_code']], $department ); } diff --git a/api/database/seeders/TaxeSeeder.php b/api/database/seeders/TaxeSeeder.php index 5ec2265..b766c51 100644 --- a/api/database/seeders/TaxeSeeder.php +++ b/api/database/seeders/TaxeSeeder.php @@ -2,12 +2,15 @@ namespace Database\Seeders; +use App\Models\Department; use App\Models\Taxe; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class TaxeSeeder extends Seeder { + private array $departmentMap = []; + /** * CSV column indices (0-based) from TRACE_REI.csv. * Variable numbers in TRACE are 1-based, so index = variable_number - 1. @@ -40,6 +43,7 @@ class TaxeSeeder extends Seeder public function run(): void { + $this->departmentMap = Department::pluck('id', 'department_code')->toArray(); $totalImported = 0; foreach (self::CSV_FILES as $year => $relativePath) { @@ -88,7 +92,7 @@ class TaxeSeeder extends Seeder $com = trim($row[self::COL_COMMUNE] ?? ''); $communeName = trim($row[self::COL_COMMUNE_NAME] ?? ''); - if ($dep === '' || $com === '') { + if ($dep === '' || $com === '' || !isset($this->departmentMap[$dep])) { continue; } @@ -100,7 +104,7 @@ class TaxeSeeder extends Seeder $batch[] = [ 'commune_code' => $communeCode, 'commune_name' => $communeName, - 'department_id' => $dep, + 'department_id' => $this->departmentMap[$dep], 'tfpnb_amount' => $this->parseDecimal($row[self::COL_TFPNB_AMOUNT] ?? ''), 'tfpnb_percentage' => $this->parseDecimal($row[self::COL_TFPNB_RATE] ?? ''), 'tfpb_amount' => $this->parseDecimal($row[self::COL_TFPB_AMOUNT] ?? ''), -- GitLab