diff --git a/api/app/Dto/CommuneCorrelation.php b/api/app/Dto/CommuneCorrelation.php new file mode 100644 index 0000000000000000000000000000000000000000..18c21ff1ed0eaaba81a0d980a1ca9168be268554 --- /dev/null +++ b/api/app/Dto/CommuneCorrelation.php @@ -0,0 +1,33 @@ + '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)] +class CommuneCorrelation +{ + public function __construct( + public readonly string $department_id = '', + public readonly string $tax_type = '', + public readonly ?int $year = null, + public readonly array $data = [], + ) {} +} diff --git a/api/app/Dto/RegionDistribution.php b/api/app/Dto/RegionDistribution.php new file mode 100644 index 0000000000000000000000000000000000000000..699c83d180a554a5338c00e2fcf735f162783b68 --- /dev/null +++ b/api/app/Dto/RegionDistribution.php @@ -0,0 +1,31 @@ + 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] +#[QueryParameter(key: 'year', schema: ['type' => 'integer'])] +class RegionDistribution +{ + public function __construct( + 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 new file mode 100644 index 0000000000000000000000000000000000000000..f0ceecc316fa2437c00b45e0e5ca8cd4910f05e6 --- /dev/null +++ b/api/app/Dto/TaxFieldStat.php @@ -0,0 +1,38 @@ + 'string'])] +#[QueryParameter(key: 'year', schema: ['type' => 'integer'])] +class TaxFieldStat +{ + public function __construct( + public readonly string $field = '', + public readonly ?float $sum = null, + public readonly ?float $average = null, + public readonly array $filters = [], + ) {} +} diff --git a/api/app/Dto/TaxGlobalStat.php b/api/app/Dto/TaxGlobalStat.php new file mode 100644 index 0000000000000000000000000000000000000000..eef1cd4abd8e4bc01ce95ac1f14f232e794487d2 --- /dev/null +++ b/api/app/Dto/TaxGlobalStat.php @@ -0,0 +1,31 @@ + 'string', 'enum' => ['department', 'region']])] +#[QueryParameter(key: 'year', schema: ['type' => 'integer'])] +class TaxGlobalStat +{ + public function __construct( + public readonly string $group_by = 'department', + public readonly string|int|null $year = null, + public readonly array $data = [], + ) {} +} diff --git a/api/app/Dto/TaxTimeSeries.php b/api/app/Dto/TaxTimeSeries.php new file mode 100644 index 0000000000000000000000000000000000000000..542ff08837791ec4dc059a5e628572693fe4e2cc --- /dev/null +++ b/api/app/Dto/TaxTimeSeries.php @@ -0,0 +1,35 @@ + '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'])] +class TaxTimeSeries +{ + public function __construct( + public readonly string $region = '', + public readonly string $tax_type = '', + public readonly ?int $start_year = null, + public readonly ?int $end_year = null, + public readonly array $data = [], + ) {} +} diff --git a/api/app/Http/Controllers/TaxeStatController.php b/api/app/Http/Controllers/TaxeStatController.php deleted file mode 100644 index 0491dadd42b27fed63d287d092b0b2915b6c10e1..0000000000000000000000000000000000000000 --- a/api/app/Http/Controllers/TaxeStatController.php +++ /dev/null @@ -1,119 +0,0 @@ -json(['error' => 'Champ invalide'], 400); - } - - $query = Taxe::query(); - - if ($request->has('department_id')) { - $query->where('department_id', $request->input('department_id')); - } - - if ($request->has('year')) { - $query->where('year', $request->input('year')); - } - - $total = $query->sum($column); - - return response()->json([ - 'field' => $field, - 'sum' => $total, - 'filters' => $request->all() - ]); - } - - public function average(Request $request, string $field) - { - $allowedFields = ['tfpnb_amount', 'tfpb_amount', 'th_amount', 'cfe_amount']; - $column = $field . '_amount'; - - if (!in_array($column, $allowedFields)) { - return response()->json(['error' => 'Champ invalide'], 400); - } - - $query = Taxe::query(); - - if ($request->has('department_id')) { - $query->where('department_id', $request->input('department_id')); - } - - if ($request->has('year')) { - $query->where('year', $request->input('year')); - } - - $average = $query->avg($column); - - return response()->json([ - 'field' => $field, - 'average' => round($average, 2), - 'filters' => $request->all() - ]); - } - -public function statsByLocation(Request $request) -{ - $groupBy = $request->input('group_by', 'department'); - - $query = Taxe::query(); - - 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'); - } - - if ($request->has('year')) { - $query->where('taxes.year', $request->input('year')); - } - - $sqlSelects = []; - - $amounts = [ - 'tfpnb_amount' => 'tfpnb', - 'tfpb_amount' => 'tfpb', - 'th_amount' => 'th', - 'cfe_amount' => 'cfe' - ]; - - foreach ($amounts as $col => $alias) { - $sqlSelects[] = "SUM($col) as {$alias}_total_amount"; - $sqlSelects[] = "ROUND(AVG($col), 2) as {$alias}_avg_amount"; - } - - $rates = [ - 'tfpnb_percentage' => 'tfpnb', - 'tfpb_percentage' => 'tfpb', - 'th_percentage' => 'th', - 'cfe_percentage' => 'cfe' - ]; - - foreach ($rates as $col => $alias) { - $sqlSelects[] = "ROUND(AVG($col), 2) as {$alias}_avg_rate"; - } - - $query->addSelect(DB::raw(implode(', ', $sqlSelects))); - - return response()->json([ - 'group_by' => $groupBy, - 'year' => $request->input('year', 'all'), - 'data' => $query->get() - ]); -} -} \ No newline at end of file diff --git a/api/app/Models/Department.php b/api/app/Models/Department.php index 09ea02d775047496712843e182e83b125e8baf33..e2fba76281157c1a6f6376d504cdd3b444b07ef0 100644 --- a/api/app/Models/Department.php +++ b/api/app/Models/Department.php @@ -3,16 +3,38 @@ 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; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; +#[ApiResource( + operations: [ + new GetCollection(), + new Get( + 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)] #[QueryParameter(key: 'department_name', filter: EqualsFilter::class)] #[QueryParameter(key: 'region', filter: EqualsFilter::class)] - -#[ApiResource] class Department extends Model { use HasFactory; @@ -25,7 +47,17 @@ class Department extends Model 'department_name', 'region_name' ]; - - protected $table = 'departments'; - public $timestamps = false; -} \ No newline at end of file + + protected $hidden = ['taxes']; + + protected $table = 'departments'; + public $timestamps = false; + + /** + * Get the taxes for the department. + */ + public function taxes() + { + return $this->hasMany(Taxe::class, 'department_id', 'department_id'); + } +} diff --git a/api/app/Models/Taxe.php b/api/app/Models/Taxe.php index 1ee6293378457c1efc97b2f437818987feecb568..ddcaadca59031f6074bb45b07dc12fd67e15f590 100644 --- a/api/app/Models/Taxe.php +++ b/api/app/Models/Taxe.php @@ -3,15 +3,38 @@ 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; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; -#[ApiResource] +#[ApiResource( + operations: [ + new GetCollection(), + new Get( + 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)] #[QueryParameter(key: 'commune_name', filter: EqualsFilter::class)] -#[QueryParameter(key: 'department_id', 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)] @@ -19,6 +42,33 @@ class Taxe extends Model { use HasFactory; + // Tax field constants + public const FIELD_TFPNB = 'tfpnb'; + public const FIELD_TFPB = 'tfpb'; + public const FIELD_TH = 'th'; + public const FIELD_CFE = 'cfe'; + + public const ALLOWED_STAT_FIELDS = [ + self::FIELD_TFPNB, + self::FIELD_TFPB, + self::FIELD_TH, + self::FIELD_CFE, + ]; + + public const AMOUNT_FIELDS = [ + 'tfpnb_amount' => self::FIELD_TFPNB, + 'tfpb_amount' => self::FIELD_TFPB, + 'th_amount' => self::FIELD_TH, + 'cfe_amount' => self::FIELD_CFE, + ]; + + public const PERCENTAGE_FIELDS = [ + 'tfpnb_percentage' => self::FIELD_TFPNB, + 'tfpb_percentage' => self::FIELD_TFPB, + 'th_percentage' => self::FIELD_TH, + 'cfe_percentage' => self::FIELD_CFE, + ]; + protected $fillable = [ 'commune_code', 'commune_name', @@ -46,7 +96,15 @@ class Taxe extends Model 'cfe_amount' => 'float', ]; - protected $table = 'taxes'; - - public $timestamps = false; -} \ No newline at end of file + protected $table = 'taxes'; + + public $timestamps = false; + + /** + * Get the department that owns the tax record. + */ + public function department() + { + return $this->belongsTo(Department::class, 'department_id', 'department_id'); + } +} diff --git a/api/app/Models/User.php b/api/app/Models/User.php deleted file mode 100644 index 749c7b77d9befa8c8726db91a4ba38ff777cc8ff..0000000000000000000000000000000000000000 --- a/api/app/Models/User.php +++ /dev/null @@ -1,48 +0,0 @@ - */ - use HasFactory, Notifiable; - - /** - * The attributes that are mass assignable. - * - * @var list - */ - protected $fillable = [ - 'name', - 'email', - 'password', - ]; - - /** - * The attributes that should be hidden for serialization. - * - * @var list - */ - protected $hidden = [ - 'password', - 'remember_token', - ]; - - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - ]; - } -} diff --git a/api/app/Services/TaxeStatService.php b/api/app/Services/TaxeStatService.php new file mode 100644 index 0000000000000000000000000000000000000000..83d61b4f23db4825964268dd5ac081a7f18346bf --- /dev/null +++ b/api/app/Services/TaxeStatService.php @@ -0,0 +1,180 @@ +validateAndGetColumn($field); + $query = $this->buildBaseQuery($departmentId, $year); + + return $query->sum($column); + } + + /** + * Calculate average for a specific tax field with optional filters. + */ + public function calculateAverage(string $field, ?string $departmentId = null, ?int $year = null): float + { + $column = $this->validateAndGetColumn($field); + $query = $this->buildBaseQuery($departmentId, $year); + + return round($query->avg($column), 2); + } + + /** + * Get statistics grouped by location (department or region). + */ + public function getStatsByLocation(string $groupBy = 'department', ?int $year = null): array + { + // Validate groupBy parameter (whitelist approach) + if (!in_array($groupBy, ['department', 'region'], true)) { + throw new InvalidArgumentException('Le paramètre group_by doit être "department" ou "region".'); + } + + $query = Taxe::query(); + + // 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'); + } + + // Apply year filter if provided + if ($year !== null) { + $query->where('taxes.year', $year); + } + + // Build aggregate selects + $sqlSelects = $this->buildAggregateSelects(); + $query->addSelect(DB::raw(implode(', ', $sqlSelects))); + + return $query->get()->toArray(); + } + + /** + * Get time series data: average tax rate by year for a given region. + */ + public function getTimeSeries(string $region, string $taxType, ?int $startYear = null, ?int $endYear = null): array + { + $this->validateAndGetColumn($taxType); + $rateColumn = $taxType . '_percentage'; + + $query = Taxe::query() + ->join('departments', 'taxes.department_id', '=', 'departments.department_id') + ->where('departments.region_name', $region) + ->groupBy('taxes.year') + ->orderBy('taxes.year') + ->select('taxes.year', DB::raw("ROUND(AVG(taxes.{$rateColumn}), 2) as avg_rate")); + + if ($startYear !== null) { + $query->where('taxes.year', '>=', $startYear); + } + if ($endYear !== null) { + $query->where('taxes.year', '<=', $endYear); + } + + return $query->get()->toArray(); + } + + /** + * Get correlation data: rate vs amount per commune for scatter plot. + */ + public function getCorrelation(string $departmentId, string $taxType, int $year): array + { + $this->validateAndGetColumn($taxType); + $rateColumn = $taxType . '_percentage'; + $amountColumn = $taxType . '_amount'; + + return Taxe::query() + ->where('department_id', $departmentId) + ->where('year', $year) + ->select('commune_name', "{$rateColumn} as rate", "{$amountColumn} as amount") + ->get() + ->toArray(); + } + + /** + * Get distribution data: total collected volume per region for pie chart. + */ + public function getDistribution(string $taxType, ?int $year = null): array + { + $this->validateAndGetColumn($taxType); + $amountColumn = $taxType . '_amount'; + + $query = Taxe::query() + ->join('departments', 'taxes.department_id', '=', 'departments.department_id') + ->groupBy('departments.region_name') + ->select('departments.region_name as region', DB::raw("SUM(taxes.{$amountColumn}) as total_amount")) + ->orderByDesc('total_amount'); + + if ($year !== null) { + $query->where('taxes.year', $year); + } + + return $query->get()->toArray(); + } + + /** + * Validate field and return the corresponding column name. + */ + private function validateAndGetColumn(string $field): string + { + if (!in_array($field, Taxe::ALLOWED_STAT_FIELDS)) { + throw new InvalidArgumentException('Champ invalide'); + } + + return $field . '_amount'; + } + + /** + * Build base query with optional filters. + */ + private function buildBaseQuery(?string $departmentId, ?int $year) + { + $query = Taxe::query(); + + if ($departmentId !== null) { + $query->where('department_id', $departmentId); + } + + if ($year !== null) { + $query->where('year', $year); + } + + return $query; + } + + /** + * Build aggregate SELECT statements for stats query. + */ + private function buildAggregateSelects(): array + { + $sqlSelects = []; + + // Aggregate amount fields + foreach (Taxe::AMOUNT_FIELDS as $col => $alias) { + $sqlSelects[] = "SUM({$col}) as {$alias}_total_amount"; + $sqlSelects[] = "ROUND(AVG({$col}), 2) as {$alias}_avg_amount"; + } + + // Aggregate percentage fields + foreach (Taxe::PERCENTAGE_FIELDS as $col => $alias) { + $sqlSelects[] = "ROUND(AVG({$col}), 2) as {$alias}_avg_rate"; + } + + return $sqlSelects; + } +} diff --git a/api/app/State/CommuneCorrelationProvider.php b/api/app/State/CommuneCorrelationProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..525a7cd58e75dcd6074279241cb2555d75b6bb50 --- /dev/null +++ b/api/app/State/CommuneCorrelationProvider.php @@ -0,0 +1,51 @@ +query->get('department_id'); + $taxType = $request?->query->get('tax_type'); + $year = $request?->query->get('year'); + + if (!$departmentId) { + throw new BadRequestHttpException('The "department_id" parameter is required.'); + } + + if (!$taxType || !in_array($taxType, Taxe::ALLOWED_STAT_FIELDS, true)) { + throw new BadRequestHttpException( + sprintf('Invalid tax_type "%s". Allowed: %s', $taxType, implode(', ', Taxe::ALLOWED_STAT_FIELDS)) + ); + } + + if (!$year) { + throw new BadRequestHttpException('The "year" parameter is required.'); + } + + $yearInt = (int) $year; + + $data = $this->taxeStatService->getCorrelation($departmentId, $taxType, $yearInt); + + return new CommuneCorrelation( + department_id: $departmentId, + tax_type: $taxType, + year: $yearInt, + data: $data, + ); + } +} diff --git a/api/app/State/RegionDistributionProvider.php b/api/app/State/RegionDistributionProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..7f4e1d2277ba7d5f37162834fa7c91ca21a9012f --- /dev/null +++ b/api/app/State/RegionDistributionProvider.php @@ -0,0 +1,41 @@ +query->get('tax_type'); + $year = $request?->query->get('year'); + + if (!$taxType || !in_array($taxType, Taxe::ALLOWED_STAT_FIELDS, true)) { + throw new BadRequestHttpException( + sprintf('Invalid tax_type "%s". Allowed: %s', $taxType, implode(', ', Taxe::ALLOWED_STAT_FIELDS)) + ); + } + + $yearInt = $year !== null ? (int) $year : null; + + $data = $this->taxeStatService->getDistribution($taxType, $yearInt); + + return new RegionDistribution( + tax_type: $taxType, + year: $yearInt, + data: $data, + ); + } +} diff --git a/api/app/State/TaxFieldStatProvider.php b/api/app/State/TaxFieldStatProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..8f53966505d0d78d4ac3af562c34ad60c3332941 --- /dev/null +++ b/api/app/State/TaxFieldStatProvider.php @@ -0,0 +1,50 @@ +query->get('department_id'); + $year = $request?->query->get('year'); + $yearInt = $year !== null ? (int) $year : null; + + $filters = array_filter([ + 'department_id' => $departmentId, + 'year' => $yearInt, + ], fn ($v) => $v !== null); + + $operationName = $operation->getName(); + + if (str_contains($operationName, 'sum')) { + $sum = $this->taxeStatService->calculateSum($field, $departmentId, $yearInt); + + return new TaxFieldStat(field: $field, sum: $sum, filters: $filters); + } + + $average = $this->taxeStatService->calculateAverage($field, $departmentId, $yearInt); + + return new TaxFieldStat(field: $field, average: $average, filters: $filters); + } +} diff --git a/api/app/State/TaxGlobalStatProvider.php b/api/app/State/TaxGlobalStatProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..3565bd8c2f001bb6208fbc263198d644eeada6a8 --- /dev/null +++ b/api/app/State/TaxGlobalStatProvider.php @@ -0,0 +1,32 @@ +query->get('group_by', 'department') ?? 'department'; + $year = $request?->query->get('year'); + $yearInt = $year !== null ? (int) $year : null; + + $data = $this->taxeStatService->getStatsByLocation($groupBy, $yearInt); + + return new TaxGlobalStat( + group_by: $groupBy, + year: $yearInt ?? 'all', + data: $data, + ); + } +} diff --git a/api/app/State/TaxTimeSeriesProvider.php b/api/app/State/TaxTimeSeriesProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..73d613168b7aafde4dc6af2fd380be75cf111acd --- /dev/null +++ b/api/app/State/TaxTimeSeriesProvider.php @@ -0,0 +1,50 @@ +query->get('region'); + $taxType = $request?->query->get('tax_type'); + $startYear = $request?->query->get('start_year'); + $endYear = $request?->query->get('end_year'); + + if (!$region) { + throw new BadRequestHttpException('The "region" parameter is required.'); + } + + if (!$taxType || !in_array($taxType, Taxe::ALLOWED_STAT_FIELDS, true)) { + throw new BadRequestHttpException( + sprintf('Invalid tax_type "%s". Allowed: %s', $taxType, implode(', ', Taxe::ALLOWED_STAT_FIELDS)) + ); + } + + $startYearInt = $startYear !== null ? (int) $startYear : null; + $endYearInt = $endYear !== null ? (int) $endYear : null; + + $data = $this->taxeStatService->getTimeSeries($region, $taxType, $startYearInt, $endYearInt); + + return new TaxTimeSeries( + region: $region, + tax_type: $taxType, + start_year: $startYearInt, + end_year: $endYearInt, + data: $data, + ); + } +} diff --git a/api/config/api-platform.php b/api/config/api-platform.php index ecc875fb808a95dda958ed9ddfdd098ea53f5332..cfee337ca14483d63363e81cfdc0d0645c0420b1 100644 --- a/api/config/api-platform.php +++ b/api/config/api-platform.php @@ -30,6 +30,7 @@ return [ 'resources' => [ app_path('Models'), + app_path('Dto'), ], 'formats' => [ diff --git a/api/database/factories/UserFactory.php b/api/database/factories/UserFactory.php deleted file mode 100644 index 584104c9cf7521e255fad7a01e2cec3658a4d2c4..0000000000000000000000000000000000000000 --- a/api/database/factories/UserFactory.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ -class UserFactory extends Factory -{ - /** - * The current password being used by the factory. - */ - protected static ?string $password; - - /** - * Define the model's default state. - * - * @return array - */ - public function definition(): array - { - return [ - 'name' => fake()->name(), - 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), - 'password' => static::$password ??= Hash::make('password'), - 'remember_token' => Str::random(10), - ]; - } - - /** - * Indicate that the model's email address should be unverified. - */ - public function unverified(): static - { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); - } -} diff --git a/api/database/migrations/0001_01_01_000000_create_users_table.php b/api/database/migrations/0001_01_01_000000_create_users_table.php deleted file mode 100644 index 05fb5d9ea95d1b527ba0e7074936553944b8d302..0000000000000000000000000000000000000000 --- a/api/database/migrations/0001_01_01_000000_create_users_table.php +++ /dev/null @@ -1,49 +0,0 @@ -id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); - - Schema::create('password_reset_tokens', function (Blueprint $table) { - $table->string('email')->primary(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); - }); - - Schema::create('sessions', function (Blueprint $table) { - $table->string('id')->primary(); - $table->foreignId('user_id')->nullable()->index(); - $table->string('ip_address', 45)->nullable(); - $table->text('user_agent')->nullable(); - $table->longText('payload'); - $table->integer('last_activity')->index(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('users'); - Schema::dropIfExists('password_reset_tokens'); - Schema::dropIfExists('sessions'); - } -}; diff --git a/api/database/migrations/2026_02_07_000000_add_indexes_to_taxes_table.php b/api/database/migrations/2026_02_07_000000_add_indexes_to_taxes_table.php new file mode 100644 index 0000000000000000000000000000000000000000..1f78437495fcd25baff35f2fc353a6b36d8a17c9 --- /dev/null +++ b/api/database/migrations/2026_02_07_000000_add_indexes_to_taxes_table.php @@ -0,0 +1,46 @@ +index('year', 'idx_taxes_year'); + + // Index for commune code lookups + $table->index('commune_code', 'idx_taxes_commune_code'); + + // Composite index for common query pattern (department + year) + $table->index(['department_id', 'year'], 'idx_taxes_dept_year'); + }); + + Schema::table('departments', function (Blueprint $table) { + // Index for region filtering (used in statsByLocation with group_by=region) + $table->index('region_name', 'idx_departments_region'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('taxes', function (Blueprint $table) { + $table->dropIndex('idx_taxes_year'); + $table->dropIndex('idx_taxes_commune_code'); + $table->dropIndex('idx_taxes_dept_year'); + }); + + Schema::table('departments', function (Blueprint $table) { + $table->dropIndex('idx_departments_region'); + }); + } +}; diff --git a/api/routes/api.php b/api/routes/api.php index bc856bff9a197680d82bac7cde2cd1c90653bec5..7c4d255a676e7ed72334b6791e3714f5b0ea42e2 100644 --- a/api/routes/api.php +++ b/api/routes/api.php @@ -1,7 +1,3 @@