add spatial plannings retribution calculations
This commit is contained in:
288
app/Console/Commands/CalculateRetributionProposalsCommand.php
Normal file
288
app/Console/Commands/CalculateRetributionProposalsCommand.php
Normal file
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\SpatialPlanning;
|
||||
use App\Models\RetributionProposal;
|
||||
use App\Services\RetributionProposalService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Exception;
|
||||
|
||||
class CalculateRetributionProposalsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'retribution:calculate-proposals
|
||||
{--force : Force recalculate existing proposals}
|
||||
{--limit= : Limit number of spatial plannings to process}
|
||||
{--skip-existing : Skip spatial plannings that already have proposals}
|
||||
{--dry-run : Show what would be processed without actually creating proposals}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Calculate retribution proposals for all spatial plannings';
|
||||
|
||||
protected RetributionProposalService $proposalService;
|
||||
|
||||
public function __construct(RetributionProposalService $proposalService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->proposalService = $proposalService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('🧮 Starting Retribution Proposal Calculation...');
|
||||
$this->newLine();
|
||||
|
||||
try {
|
||||
// Get processing options
|
||||
$force = $this->option('force');
|
||||
$limit = $this->option('limit') ? (int) $this->option('limit') : null;
|
||||
$skipExisting = $this->option('skip-existing');
|
||||
$dryRun = $this->option('dry-run');
|
||||
|
||||
// Build query for spatial plannings
|
||||
$query = SpatialPlanning::query();
|
||||
|
||||
if ($skipExisting && !$force) {
|
||||
$query->whereDoesntHave('retributionProposals');
|
||||
}
|
||||
|
||||
if ($limit) {
|
||||
$query->limit($limit);
|
||||
}
|
||||
|
||||
$spatialPlannings = $query->get();
|
||||
$totalCount = $spatialPlannings->count();
|
||||
|
||||
if ($totalCount === 0) {
|
||||
$this->warn('No spatial plannings found to process.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Show processing summary
|
||||
$this->info("📊 PROCESSING SUMMARY:");
|
||||
$this->info(" Total Spatial Plannings: {$totalCount}");
|
||||
$this->info(" Force Recalculate: " . ($force ? 'Yes' : 'No'));
|
||||
$this->info(" Skip Existing: " . ($skipExisting ? 'Yes' : 'No'));
|
||||
$this->info(" Dry Run: " . ($dryRun ? 'Yes' : 'No'));
|
||||
$this->newLine();
|
||||
|
||||
if ($dryRun) {
|
||||
$this->showDryRunPreview($spatialPlannings);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Confirm processing
|
||||
if (!$this->confirm("Process {$totalCount} spatial plannings?")) {
|
||||
$this->info('Operation cancelled.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Process spatial plannings
|
||||
$this->processRetributionCalculations($spatialPlannings, $force);
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->error('Error during retribution calculation: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dry run preview
|
||||
*/
|
||||
private function showDryRunPreview($spatialPlannings)
|
||||
{
|
||||
$this->info('🔍 DRY RUN PREVIEW:');
|
||||
$this->newLine();
|
||||
|
||||
$processable = 0;
|
||||
$withProposals = 0;
|
||||
$withoutFunction = 0;
|
||||
$withoutArea = 0;
|
||||
|
||||
foreach ($spatialPlannings as $spatial) {
|
||||
$hasProposals = $spatial->retributionProposals()->exists();
|
||||
$buildingFunction = $this->proposalService->detectBuildingFunction($spatial->getBuildingFunctionText());
|
||||
$area = $spatial->getCalculationArea();
|
||||
|
||||
if ($hasProposals) {
|
||||
$withProposals++;
|
||||
}
|
||||
|
||||
if (!$buildingFunction) {
|
||||
$withoutFunction++;
|
||||
}
|
||||
|
||||
if ($area <= 0) {
|
||||
$withoutArea++;
|
||||
}
|
||||
|
||||
if ($buildingFunction && $area > 0) {
|
||||
$processable++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->table(
|
||||
['Status', 'Count', 'Description'],
|
||||
[
|
||||
['✅ Processable', $processable, 'Can create retribution proposals'],
|
||||
['🔄 With Existing Proposals', $withProposals, 'Already have proposals (will skip unless --force)'],
|
||||
['❌ Missing Building Function', $withoutFunction, 'Cannot detect building function'],
|
||||
['❌ Missing Area', $withoutArea, 'Area is zero or missing'],
|
||||
]
|
||||
);
|
||||
|
||||
$this->newLine();
|
||||
$this->info("💡 To process: php artisan retribution:calculate-proposals");
|
||||
$this->info("💡 To force recalculate: php artisan retribution:calculate-proposals --force");
|
||||
}
|
||||
|
||||
/**
|
||||
* Process retribution calculations
|
||||
*/
|
||||
private function processRetributionCalculations($spatialPlannings, $force)
|
||||
{
|
||||
$progressBar = $this->output->createProgressBar($spatialPlannings->count());
|
||||
$progressBar->start();
|
||||
|
||||
$stats = [
|
||||
'processed' => 0,
|
||||
'skipped' => 0,
|
||||
'errors' => 0,
|
||||
'created_proposals' => 0
|
||||
];
|
||||
|
||||
foreach ($spatialPlannings as $spatial) {
|
||||
try {
|
||||
$result = $this->processSingleSpatialPlanning($spatial, $force);
|
||||
|
||||
if ($result['status'] === 'processed') {
|
||||
$stats['processed']++;
|
||||
$stats['created_proposals'] += $result['proposals_created'];
|
||||
} elseif ($result['status'] === 'skipped') {
|
||||
$stats['skipped']++;
|
||||
} else {
|
||||
$stats['errors']++;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$stats['errors']++;
|
||||
$this->newLine();
|
||||
$this->error("Error processing spatial planning ID {$spatial->id}: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$progressBar->advance();
|
||||
}
|
||||
|
||||
$progressBar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
// Show final statistics
|
||||
$this->showFinalStatistics($stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process single spatial planning
|
||||
*/
|
||||
private function processSingleSpatialPlanning(SpatialPlanning $spatial, $force)
|
||||
{
|
||||
// Check if already has proposals
|
||||
if (!$force && $spatial->retributionProposals()->exists()) {
|
||||
return ['status' => 'skipped', 'reason' => 'already_has_proposals'];
|
||||
}
|
||||
|
||||
// Detect building function
|
||||
$buildingFunction = $this->proposalService->detectBuildingFunction($spatial->getBuildingFunctionText());
|
||||
if (!$buildingFunction) {
|
||||
return ['status' => 'error', 'reason' => 'no_building_function'];
|
||||
}
|
||||
|
||||
// Get area
|
||||
$totalArea = $spatial->getCalculationArea();
|
||||
if ($totalArea <= 0) {
|
||||
return ['status' => 'error', 'reason' => 'no_area'];
|
||||
}
|
||||
|
||||
// Get number of floors
|
||||
$numberOfFloors = max(1, $spatial->number_of_floors ?? 1);
|
||||
|
||||
// Delete existing proposals if force mode
|
||||
if ($force) {
|
||||
$spatial->retributionProposals()->delete();
|
||||
}
|
||||
|
||||
$proposalsCreated = 0;
|
||||
|
||||
// Create proposals for each floor
|
||||
for ($floor = 1; $floor <= $numberOfFloors; $floor++) {
|
||||
// Calculate floor area (distribute total area across floors)
|
||||
$floorArea = $totalArea / $numberOfFloors;
|
||||
|
||||
// Create retribution proposal
|
||||
$proposal = $this->proposalService->createProposalForSpatialPlanning(
|
||||
$spatial,
|
||||
$buildingFunction->id,
|
||||
$floor,
|
||||
$floorArea,
|
||||
$totalArea,
|
||||
"Auto-generated from spatial planning calculation"
|
||||
);
|
||||
|
||||
if ($proposal) {
|
||||
$proposalsCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'processed',
|
||||
'proposals_created' => $proposalsCreated
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show final statistics
|
||||
*/
|
||||
private function showFinalStatistics($stats)
|
||||
{
|
||||
$this->info('✅ CALCULATION COMPLETED!');
|
||||
$this->newLine();
|
||||
|
||||
$this->table(
|
||||
['Metric', 'Count'],
|
||||
[
|
||||
['Processed Successfully', $stats['processed']],
|
||||
['Skipped (Already Exists)', $stats['skipped']],
|
||||
['Errors', $stats['errors']],
|
||||
['Total Proposals Created', $stats['created_proposals']],
|
||||
]
|
||||
);
|
||||
|
||||
if ($stats['errors'] > 0) {
|
||||
$this->newLine();
|
||||
$this->warn("⚠️ {$stats['errors']} spatial plannings had errors:");
|
||||
$this->warn(" • Missing building function detection");
|
||||
$this->warn(" • Missing or zero area");
|
||||
$this->warn(" • Other calculation errors");
|
||||
}
|
||||
|
||||
if ($stats['processed'] > 0) {
|
||||
$this->newLine();
|
||||
$this->info("🎉 Successfully created {$stats['created_proposals']} retribution proposals!");
|
||||
$this->info("💡 You can view them using: php artisan retribution:list-proposals");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user