migrate to gtea from bistbucket
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\RestaurantDelivery\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\RestaurantDelivery\Enums\DeliveryStatus;
|
||||
use Modules\RestaurantDelivery\Jobs\AssignRiderJob;
|
||||
use Modules\RestaurantDelivery\Models\Delivery;
|
||||
|
||||
class AssignPendingDeliveries extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'delivery:assign-pending
|
||||
{--limit=50 : Maximum number of deliveries to process}
|
||||
{--restaurant= : Filter by restaurant ID}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Assign riders to pending deliveries that need assignment';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$limit = (int) $this->option('limit');
|
||||
$restaurantId = $this->option('restaurant');
|
||||
|
||||
$this->info('Looking for deliveries that need rider assignment...');
|
||||
|
||||
$query = Delivery::query()
|
||||
->where('status', DeliveryStatus::READY_FOR_PICKUP)
|
||||
->whereNull('rider_id');
|
||||
|
||||
if ($restaurantId) {
|
||||
$query->where('restaurant_id', $restaurantId);
|
||||
}
|
||||
|
||||
$deliveries = $query->limit($limit)
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
|
||||
if ($deliveries->isEmpty()) {
|
||||
$this->info('No deliveries found that need rider assignment.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info("Found {$deliveries->count()} deliveries to process.");
|
||||
|
||||
$bar = $this->output->createProgressBar($deliveries->count());
|
||||
$bar->start();
|
||||
|
||||
foreach ($deliveries as $delivery) {
|
||||
dispatch(new AssignRiderJob($delivery));
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
|
||||
$this->info("Dispatched {$deliveries->count()} assignment jobs to queue.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\RestaurantDelivery\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\RestaurantDelivery\Jobs\CleanupStaleLocationsJob;
|
||||
use Modules\RestaurantDelivery\Models\LocationLog;
|
||||
use Modules\RestaurantDelivery\Models\Rider;
|
||||
|
||||
class CleanupStaleData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'delivery:cleanup
|
||||
{--days=7 : Number of days to keep location logs}
|
||||
{--queue : Dispatch as a background job}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Clean up stale location logs and mark offline riders';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$days = (int) $this->option('days');
|
||||
$queue = $this->option('queue');
|
||||
|
||||
if ($queue) {
|
||||
dispatch(new CleanupStaleLocationsJob($days));
|
||||
$this->info('Cleanup job dispatched to queue.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info("Cleaning up data older than {$days} days...");
|
||||
|
||||
// Delete old location logs
|
||||
$deletedLogs = LocationLog::where('recorded_at', '<', now()->subDays($days))
|
||||
->delete();
|
||||
|
||||
$this->info("Deleted {$deletedLogs} old location log entries.");
|
||||
|
||||
// Mark stale riders as offline
|
||||
$offlineThreshold = config('restaurant-delivery.firebase.location.offline_threshold', 120);
|
||||
|
||||
$ridersMarkedOffline = Rider::where('is_online', true)
|
||||
->where('last_location_update', '<', now()->subSeconds($offlineThreshold))
|
||||
->update(['is_online' => false]);
|
||||
|
||||
$this->info("Marked {$ridersMarkedOffline} riders as offline (no update for {$offlineThreshold}s).");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\RestaurantDelivery\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\RestaurantDelivery\Models\RiderEarning;
|
||||
|
||||
class GenerateEarningsReport extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'delivery:earnings-report
|
||||
{--period=week : Report period (today, week, month, year)}
|
||||
{--restaurant= : Filter by restaurant ID}
|
||||
{--export= : Export to file (csv, json)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Generate earnings report for riders';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$period = $this->option('period');
|
||||
$restaurantId = $this->option('restaurant');
|
||||
$export = $this->option('export');
|
||||
|
||||
$startDate = match ($period) {
|
||||
'today' => now()->startOfDay(),
|
||||
'week' => now()->startOfWeek(),
|
||||
'month' => now()->startOfMonth(),
|
||||
'year' => now()->startOfYear(),
|
||||
default => now()->startOfWeek(),
|
||||
};
|
||||
|
||||
$this->info("Generating earnings report for period: {$period}");
|
||||
$this->info("From: {$startDate->format('Y-m-d H:i:s')}");
|
||||
$this->newLine();
|
||||
|
||||
$query = RiderEarning::query()
|
||||
->select([
|
||||
'rider_id',
|
||||
DB::raw('COUNT(*) as total_deliveries'),
|
||||
DB::raw('SUM(gross_amount) as gross_earnings'),
|
||||
DB::raw('SUM(commission_amount) as total_commission'),
|
||||
DB::raw('SUM(net_amount) as net_earnings'),
|
||||
DB::raw('SUM(tip_amount) as total_tips'),
|
||||
DB::raw('SUM(bonus_amount) as total_bonuses'),
|
||||
DB::raw('SUM(penalty_amount) as total_penalties'),
|
||||
])
|
||||
->where('earned_at', '>=', $startDate)
|
||||
->groupBy('rider_id');
|
||||
|
||||
if ($restaurantId) {
|
||||
$query->where('restaurant_id', $restaurantId);
|
||||
}
|
||||
|
||||
$report = $query->with('rider:id,first_name,last_name,phone')
|
||||
->get();
|
||||
|
||||
if ($report->isEmpty()) {
|
||||
$this->warn('No earnings data found for the specified period.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$currency = config('restaurant-delivery.pricing.currency', 'BDT');
|
||||
|
||||
$data = $report->map(function ($row) use ($currency) {
|
||||
return [
|
||||
'Rider ID' => $row->rider_id,
|
||||
'Name' => $row->rider?->full_name ?? 'Unknown',
|
||||
'Deliveries' => $row->total_deliveries,
|
||||
'Gross' => "{$currency} ".number_format((float) $row->gross_earnings, 2),
|
||||
'Commission' => "{$currency} ".number_format((float) $row->total_commission, 2),
|
||||
'Tips' => "{$currency} ".number_format((float) $row->total_tips, 2),
|
||||
'Bonuses' => "{$currency} ".number_format((float) $row->total_bonuses, 2),
|
||||
'Penalties' => "{$currency} ".number_format((float) $row->total_penalties, 2),
|
||||
'Net' => "{$currency} ".number_format((float) $row->net_earnings, 2),
|
||||
];
|
||||
});
|
||||
|
||||
// Display table
|
||||
$this->table(array_keys($data->first()), $data->toArray());
|
||||
|
||||
// Show totals
|
||||
$this->newLine();
|
||||
$this->info('Totals:');
|
||||
$this->line(' Total Deliveries: '.$report->sum('total_deliveries'));
|
||||
$this->line(" Gross Earnings: {$currency} ".number_format((float) $report->sum('gross_earnings'), 2));
|
||||
$this->line(" Total Commission: {$currency} ".number_format((float) $report->sum('total_commission'), 2));
|
||||
$this->line(" Net Earnings: {$currency} ".number_format((float) $report->sum('net_earnings'), 2));
|
||||
|
||||
// Export if requested
|
||||
if ($export) {
|
||||
$filename = "earnings_report_{$period}_".now()->format('Y-m-d_His').".{$export}";
|
||||
|
||||
if ($export === 'csv') {
|
||||
$this->exportToCsv($data->toArray(), $filename);
|
||||
} elseif ($export === 'json') {
|
||||
$this->exportToJson($report->toArray(), $filename);
|
||||
}
|
||||
|
||||
$this->info("Report exported to: {$filename}");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export data to CSV.
|
||||
*/
|
||||
protected function exportToCsv(array $data, string $filename): void
|
||||
{
|
||||
$handle = fopen(storage_path("app/{$filename}"), 'w');
|
||||
|
||||
if (! empty($data)) {
|
||||
fputcsv($handle, array_keys($data[0]));
|
||||
foreach ($data as $row) {
|
||||
fputcsv($handle, $row);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export data to JSON.
|
||||
*/
|
||||
protected function exportToJson(array $data, string $filename): void
|
||||
{
|
||||
file_put_contents(
|
||||
storage_path("app/{$filename}"),
|
||||
json_encode($data, JSON_PRETTY_PRINT)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\RestaurantDelivery\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\RestaurantDelivery\Jobs\ProcessPayoutJob;
|
||||
use Modules\RestaurantDelivery\Models\Rider;
|
||||
|
||||
class ProcessRiderPayouts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'delivery:process-payouts
|
||||
{--rider= : Process payout for specific rider ID}
|
||||
{--restaurant= : Filter by restaurant ID}
|
||||
{--payment-method= : Override payment method}
|
||||
{--dry-run : Show what would be processed without actually processing}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Process pending payouts for riders';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$riderId = $this->option('rider');
|
||||
$restaurantId = $this->option('restaurant');
|
||||
$paymentMethod = $this->option('payment-method');
|
||||
$dryRun = $this->option('dry-run');
|
||||
|
||||
$this->info('Processing rider payouts...');
|
||||
|
||||
$query = Rider::query()
|
||||
->where('status', '!=', 'suspended')
|
||||
->whereHas('earnings', function ($q) {
|
||||
$q->where('status', 'pending')
|
||||
->whereNull('payout_id');
|
||||
});
|
||||
|
||||
if ($riderId) {
|
||||
$query->where('id', $riderId);
|
||||
}
|
||||
|
||||
if ($restaurantId) {
|
||||
$query->where('restaurant_id', $restaurantId);
|
||||
}
|
||||
|
||||
$riders = $query->with(['earnings' => function ($q) {
|
||||
$q->where('status', 'pending')
|
||||
->whereNull('payout_id');
|
||||
}])->get();
|
||||
|
||||
if ($riders->isEmpty()) {
|
||||
$this->info('No riders with pending earnings found.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$minimumPayout = config('restaurant-delivery.earnings.payout.minimum_amount', 500);
|
||||
$currency = config('restaurant-delivery.pricing.currency', 'BDT');
|
||||
|
||||
$this->info("Found {$riders->count()} riders with pending earnings.");
|
||||
$this->info("Minimum payout threshold: {$currency} {$minimumPayout}");
|
||||
$this->newLine();
|
||||
|
||||
$processed = 0;
|
||||
$skipped = 0;
|
||||
|
||||
$this->table(
|
||||
['Rider ID', 'Name', 'Pending Amount', 'Status'],
|
||||
$riders->map(function ($rider) use ($minimumPayout, $currency, $dryRun, $paymentMethod, &$processed, &$skipped) {
|
||||
$pendingAmount = $rider->earnings->sum('net_amount');
|
||||
|
||||
if ($pendingAmount < $minimumPayout) {
|
||||
$skipped++;
|
||||
|
||||
return [
|
||||
$rider->id,
|
||||
$rider->full_name,
|
||||
"{$currency} ".number_format($pendingAmount, 2),
|
||||
'Below minimum',
|
||||
];
|
||||
}
|
||||
|
||||
if (! $dryRun) {
|
||||
dispatch(new ProcessPayoutJob($rider, $paymentMethod));
|
||||
}
|
||||
|
||||
$processed++;
|
||||
|
||||
return [
|
||||
$rider->id,
|
||||
$rider->full_name,
|
||||
"{$currency} ".number_format($pendingAmount, 2),
|
||||
$dryRun ? 'Would process' : 'Processing',
|
||||
];
|
||||
})->toArray()
|
||||
);
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn("DRY RUN: Would process {$processed} payouts, skip {$skipped} (below minimum).");
|
||||
} else {
|
||||
$this->info("Dispatched {$processed} payout jobs, skipped {$skipped} (below minimum).");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\RestaurantDelivery\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\RestaurantDelivery\Enums\DeliveryStatus;
|
||||
use Modules\RestaurantDelivery\Models\Delivery;
|
||||
use Modules\RestaurantDelivery\Models\Rider;
|
||||
use Modules\RestaurantDelivery\Services\Firebase\FirebaseService;
|
||||
|
||||
class SyncFirebaseData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'delivery:sync-firebase
|
||||
{--type=all : Type to sync (all, riders, deliveries)}
|
||||
{--direction=to-firebase : Sync direction (to-firebase, from-firebase)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Synchronize data between database and Firebase';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(FirebaseService $firebase): int
|
||||
{
|
||||
if (! $firebase->isEnabled()) {
|
||||
$this->error('Firebase is not enabled. Check your configuration.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$type = $this->option('type');
|
||||
$direction = $this->option('direction');
|
||||
|
||||
$this->info("Syncing {$type} {$direction}...");
|
||||
|
||||
if ($type === 'all' || $type === 'riders') {
|
||||
$this->syncRiders($firebase, $direction);
|
||||
}
|
||||
|
||||
if ($type === 'all' || $type === 'deliveries') {
|
||||
$this->syncDeliveries($firebase, $direction);
|
||||
}
|
||||
|
||||
$this->info('Sync completed successfully.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync rider data.
|
||||
*/
|
||||
protected function syncRiders(FirebaseService $firebase, string $direction): void
|
||||
{
|
||||
$this->info('Syncing rider locations...');
|
||||
|
||||
$riders = Rider::where('is_online', true)
|
||||
->whereNotNull('current_latitude')
|
||||
->whereNotNull('current_longitude')
|
||||
->get();
|
||||
|
||||
$bar = $this->output->createProgressBar($riders->count());
|
||||
$bar->start();
|
||||
|
||||
foreach ($riders as $rider) {
|
||||
if ($direction === 'to-firebase') {
|
||||
$firebase->updateRiderLocation(
|
||||
$rider->id,
|
||||
(float) $rider->current_latitude,
|
||||
(float) $rider->current_longitude
|
||||
);
|
||||
$firebase->updateRiderStatus($rider->id, $rider->status);
|
||||
}
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
$this->info("Synced {$riders->count()} online riders.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync delivery data.
|
||||
*/
|
||||
protected function syncDeliveries(FirebaseService $firebase, string $direction): void
|
||||
{
|
||||
$this->info('Syncing active deliveries...');
|
||||
|
||||
$deliveries = Delivery::whereIn('status', array_map(
|
||||
fn ($s) => $s->value,
|
||||
DeliveryStatus::riderActiveStatuses()
|
||||
))
|
||||
->whereNotNull('rider_id')
|
||||
->with('rider')
|
||||
->get();
|
||||
|
||||
$bar = $this->output->createProgressBar($deliveries->count());
|
||||
$bar->start();
|
||||
|
||||
foreach ($deliveries as $delivery) {
|
||||
if ($direction === 'to-firebase') {
|
||||
$firebase->initializeDeliveryTracking($delivery->id, [
|
||||
'status' => $delivery->status->value,
|
||||
'rider_id' => $delivery->rider_id,
|
||||
'pickup_latitude' => $delivery->pickup_latitude,
|
||||
'pickup_longitude' => $delivery->pickup_longitude,
|
||||
'drop_latitude' => $delivery->drop_latitude,
|
||||
'drop_longitude' => $delivery->drop_longitude,
|
||||
]);
|
||||
}
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
$this->info("Synced {$deliveries->count()} active deliveries.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user