diff --git a/app/Console/Commands/CostCommand.php b/app/Console/Commands/CostCommand.php index 99a9db5..1e05de4 100644 --- a/app/Console/Commands/CostCommand.php +++ b/app/Console/Commands/CostCommand.php @@ -2,135 +2,160 @@ namespace App\Console\Commands; -use DB; +use Throwable; +use Carbon\Carbon; +use App\Models\Cost; use App\Models\Business; use Illuminate\Console\Command; +use Morilog\Jalali\CalendarUtils; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Storage; class CostCommand extends Command { - public const USER_FEE = 400; - - public const FILE_FEE = [ - 200 => 0, - 400 => 1000, - 800 => 2000, - ]; - protected $signature = 'cost:work'; protected $description = 'Run the cost worker'; - public function __construct() - { - parent::__construct(); - } - public function handle() { - // infinte loop while (true) { - // to baghali ha - $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); - - $calculated_at = Cache::get('calculated_at'); - if ($calculated_at === null) { - $business = Business::orderBy('calculated_at')->first()->load('users', 'cost'); - $calculated_at = $business->calculated_at; - $until_now = jdate()->getMonth() > jdate($business->calculated_at)->getMonth() - ? jdate($business->calculated_at)->toCarbon()->setTime("00", "00", "00") - : \Carbon\Carbon::now(); - - Cache::put('calculated_at', $until_now, 60); + $lock = Cache::get('lock', false); + if ($lock) { + $this->info('There is no business for auditing.'); + continue; + } + + $business = Business::orderBy('calculated_at')->first(); + if ($business === null) { + continue; + } + + if ($business->calculated_at->isFuture()) { + continue; + } + + $next_month = jdate($business->calculated_at)->addMonths(); + [$year, $month, $day] = CalendarUtils::toGregorian( + $next_month->getYear(), $next_month->getMonth(), 1 + ); + $now = Carbon::createFromDate($year, $month, $day); + $now->setTime(0, 0, 0); + + if ($now->isFuture()) { + $now = Carbon::now(); } // if calculated_at less than an hour stop - if (\Carbon\Carbon::now()->diffInMinutes($until_now) <= 60) { - $this->info('nothing to cost'); + if ($now->diffInMinutes($business->calculated_at) <= 59) { + $this->info('Must be one hour after the last audit.'); + Cache::put('lock', true, $now->diffInSeconds($business->calculated_at)); continue; } try { DB::beginTransaction(); + // Fixed amounts of expenses + $business->load('users', 'files'); - // business order by last_calculated_at take first - if (!isset($business)) { - $business = Business::orderBy('calculated_at')->first()->load('users', 'cost'); - } - - $user_fee = enum('business.fee.user'); - - // get business employee - $users_cost = $business->cost - ->where('type', '=', 'users') - ->where('fee', '=', $user_fee) - ->where('month', '=', $recorded_month) - ->where('amount', '=', $business->users->count()) - ->first(); - - if ($users_cost === null) { - $business->cost()->create([ - 'type' => 'users', - 'month' => $recorded_month, - 'amount' => $business->users->count(), - 'fee' => $user_fee, - 'duration' => $duration = $until_now->diffInSeconds($calculated_at), // from the created_at time of the newset fifth user - 'additional' => $business->users->pluck('id')->toArray(), - ]); - } else { - $users_cost->update([ - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // last calc - (current month - now else last calc - end of the past month), - 'additional' => $business->users->pluck('id')->toArray(), - ]); - } - - $costs = $user_fee * $duration; - - // do the math in php - if (intdiv($business->files_volume, 200) === 0) { - $pads = 0; - } else { - $pads = intdiv($business->files_volume, 200) - 1; - } - - $file_fee = enum('business.fee.file'); - - $files = $business->cost - ->where('type', '=', 'files') - ->where('fee', '=', $file_fee) - ->where('month', '=', $recorded_month) - ->where('amount', '=', $business->files_volume) - ->first(); - - if ($files === null) { - $business->cost()->create([ - 'type' => 'files', - 'month' => $recorded_month, - 'amount' => $pads, - 'fee' => $file_fee, - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // how to determine the file?, - ]); - } else { - $files->update([ - 'duration' => $duration = $until_now->diffInMinutes($calculated_at), // last calc - (current month - now else last calc - end of the past month),, - ]); - } - - $costs += $file_fee * $duration; + $costs = 0; + $costs += $this->calculateCostOfBusinessUsers($business, $now); + $costs += $this->calculateCostOfBusinessFiles($business, $now); // increment and decrement of wallet in php // deduct costs from your business wallet // make sure save the calculated_at $business->update([ 'wallet' => $business->wallet - $costs, - 'calculated_at' => \Carbon\Carbon::now(), + 'calculated_at' => $now, ]); + DB::commit(); - } catch (Throwable $thr) { + $this->info("The business #{$business->id} was audited."); + } catch (Throwable $throwable) { + throw $throwable; DB::rollback(); - throw $thr; + report($throwable); continue; } } } + + public function calculateCostOfBusinessUsers($business, $now) + { + $user_fee = enum('business.fee.user'); + $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); + + if ($business->users->isEmpty()) { + return 0; + } + + // get business employee + $users_cost = $business->cost + ->where('type', '=', Cost::USER_TYPE) + ->where('fee', '=', $user_fee) + ->where('month', '=', $recorded_month) + ->where('amount', '=', $business->users->count()) + ->first(); + + if ($users_cost === null) { + $business->cost()->create([ + 'type' => Cost::USER_TYPE, + 'month' => $recorded_month, + 'amount' => $business->users->count(), + 'fee' => $user_fee, + 'duration' => $duration = $now->diffInMinutes($business->calculated_at), // from the created_at time of the newset fifth user + 'additional' => $business->users->pluck('id')->toArray(), + ]); + } else { + $users_cost->update([ + 'duration' => $duration = $now->diffInMinutes($business->calculated_at) + $users_cost->duration, // last calc - (current month - now else last calc - end of the past month), + 'additional' => $business->users->pluck('id')->toArray(), + ]); + } + + return $user_fee * $duration; + } + + public function calculateCostOfBusinessFiles($business, $now) + { + $file_fee = enum('business.fee.file'); + $calculated_at = $business->calculated_at; + $recorded_month = jdate($business->calculated_at)->format("Y-m-01"); + + + // do the math in php + $packs = intdiv($business->files_volume, 200); + if ($packs === 0) { + return 0; + } else { + $packs--; + } + + $files = $business->cost + ->where('type', '=', Cost::FILE_TYPE) + ->where('fee', '=', $file_fee) + ->where('month', '=', $recorded_month) + ->where('amount', '=', $packs) + ->first(); + + if ($files === null) { + $business->cost()->create([ + 'type' => Cost::FILE_TYPE, + 'month' => $recorded_month, + 'amount' => $packs, + 'fee' => $file_fee, + 'duration' => $duration = $now->diffInMinutes($calculated_at), // how to determine the file?, + 'additional' => ['volume' => $business->files_volume], + ]); + } else { + $files->update([ + 'duration' => $duration = $now->diffInMinutes($calculated_at) + $files->duration, // last calc - (current month - now else last calc - end of the past month),, + 'additional' => ['volume' => $business->files_volume], + ]); + } + + return $file_fee * $duration; + } } diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 7e9a193..55fe6f7 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -13,7 +13,6 @@ use App\Http\Resources\UserResource; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Cache; -use Laravel\Lumen\Routing\Controller; use Laravel\Socialite\Facades\Socialite; use Illuminate\Session\TokenMismatchException; use Symfony\Component\HttpFoundation\Response; diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index 63816f6..23b30ca 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -4,13 +4,13 @@ namespace App\Http\Controllers; use App\Models\File; use App\Models\Project; -use App\Rules\maxBound; +use App\Rules\MaxBound; use App\Models\Business; use Illuminate\Support\Str; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; +use App\Http\Resources\FileResource; use Illuminate\Support\Facades\Auth; -use App\HiLib\Resources\FileResource; use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\AllowedFilter; use Illuminate\Support\Facades\Storage; @@ -102,7 +102,7 @@ class FileController extends Controller $business = Business::findOrFail($business); $project = Project::findOrFail($project); - // $this->validate($request, ['file' => 'required|file',]); + $this->validate($request, ['file' => 'required|file',]); $file = $request->file('file'); $file_extension = $file->getClientOriginalExtension(); @@ -127,6 +127,10 @@ class FileController extends Controller 'description' => $request->description ]); + $business->update([ + 'files_volume' => $business->files_volume + $file_record->size + ]); + return new FileResource($file_record); } diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 4a396ef..00a20cb 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -22,7 +22,7 @@ class ProjectController extends Controller return Business::info($request->route('business'), true); } - public function update(Request $request,string $project) + public function update(Request $request, int $business, string $project) { permit('projectEdit', ['project_id' => $project]); $project = Project::findOrFail($project); @@ -31,7 +31,7 @@ class ProjectController extends Controller } - public function delete(Request $request, string $project) + public function delete(Request $request, int $business, string $project) { permit('businessProjects'); $project = Project::findOrFail($project); @@ -39,7 +39,7 @@ class ProjectController extends Controller return Business::info($request->route('business')); } - public function restore(Request $request, string $project) + public function restore(Request $request, int $business, string $project) { $project = Project::onlyTrashed()->findOrFail($project); $project->restore(); @@ -139,7 +139,7 @@ class ProjectController extends Controller } } - public function setAvatar(Request $request, string $project) + public function setAvatar(Request $request, int $business, string $project) { $project = Project::findOrFail($project); if ($request->hasFile('avatar')) { @@ -149,7 +149,7 @@ class ProjectController extends Controller return $project; } - public function unSetAvatar(Request $request, string $project) + public function unSetAvatar(Request $request,int $business ,string $project) { $project = Project::findOrFail($project); $project->deleteAvatar(); diff --git a/app/Http/Controllers/TaskFileController.php b/app/Http/Controllers/TaskFileController.php index 6750715..b8bd77c 100644 --- a/app/Http/Controllers/TaskFileController.php +++ b/app/Http/Controllers/TaskFileController.php @@ -11,7 +11,6 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Http\Resources\FileResource; use Symfony\Component\HttpFoundation\Response; -use Illuminate\Http\Exceptions\HttpResponseException; class TaskFileController extends Controller { @@ -41,7 +40,7 @@ class TaskFileController extends Controller // guest or de active // return files as file resource [$business, $project, $task] = $this->checkBelonging($business, $project, $task); - return FileResource::collection($task->files); + return FileResource::collection($task->files ?? []); } public function sync(Request $request,int $business, int $project, int $task) @@ -78,7 +77,7 @@ class TaskFileController extends Controller // return the file resource or stream it [$business, $project, $task] = $this->checkBelonging($business, $project, $task); - $file = File::find($file); + $file = File::findOrFail($file); if ($file->user_id !== Auth::id()) { abort(Response::HTTP_UNAUTHORIZED); } diff --git a/app/Http/Resources/FileResource.php b/app/Http/Resources/FileResource.php index 3b74fb0..bc99db1 100644 --- a/app/Http/Resources/FileResource.php +++ b/app/Http/Resources/FileResource.php @@ -1,9 +1,7 @@ 'boolean', + 'calculated_at' => 'datetime', ]; public function getValueOf(?string $key) diff --git a/app/Models/Cost.php b/app/Models/Cost.php index fa06239..98b2e63 100644 --- a/app/Models/Cost.php +++ b/app/Models/Cost.php @@ -6,6 +6,10 @@ use App\Models\Model; class Cost extends Model { + public const USER_TYPE = 'users'; + + public const FILE_TYPE = 'files'; + public $perPage = 12; protected $fillable = [ diff --git a/app/Models/Model.php b/app/Models/Model.php index c68422f..b2f1373 100644 --- a/app/Models/Model.php +++ b/app/Models/Model.php @@ -2,11 +2,7 @@ namespace App\Models; -use Anik\Amqp\Exchange; -use Anik\Amqp\Facades\Amqp; use App\Events\ModelSaved; -use PhpAmqpLib\Wire\AMQPTable; -use Anik\Amqp\PublishableMessage; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index c02d1b6..69ff803 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -40,7 +40,7 @@ class AuthServiceProvider extends ServiceProvider 'token' => $request->bearerToken(), 'agent' => $request->getAgent(), 'os' => $request->getOS(), - ])->first(); + ])->firstOrFail(); return $fingerprint->user->setAttribute('token', $fingerprint->token); }); diff --git a/composer.json b/composer.json index ca1dcad..6c87a76 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,8 @@ "jenssegers/agent": "^2.6", "laravel/socialite": "^5.1", "laravel/framework": "^8.12", - "guzzlehttp/guzzle": "^7.0.1", "laravel/legacy-factories": "^1", "fruitcake/laravel-cors": "^2.0", - "laravel/lumen-framework": "^8.0", - "illuminate/notifications": "^8.0", "league/flysystem-aws-s3-v3": "~1.0", "spatie/laravel-medialibrary": "^9.0", "spatie/laravel-query-builder": "^3.3", diff --git a/config/app.php b/config/app.php index cc438b2..d517cdc 100644 --- a/config/app.php +++ b/config/app.php @@ -12,7 +12,7 @@ return [ 'asset_url' => env('ASSET_URL', null), - 'timezone' => 'UTC', + 'timezone' => 'Asia/Tehran', 'locale' => 'fa', @@ -64,6 +64,7 @@ return [ App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Utilities\Zarinpal\Laravel\ZarinpalServiceProvider::class, ], 'aliases' => [ @@ -104,5 +105,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, + + 'View' => App\Utilities\Zarinpal\Laravel\Facade\Zarinpal::class, ], ]; diff --git a/database/factories/BusinessFactory.php b/database/factories/BusinessFactory.php index f8dab3d..28f0dae 100644 --- a/database/factories/BusinessFactory.php +++ b/database/factories/BusinessFactory.php @@ -12,6 +12,6 @@ $factory->define(Business::class, function (Faker $faker) { 'slug' => Str::slug($name) . $faker->numberBetween(1, 100), 'wallet' => random_int(111111, 999999), 'color' => $faker->colorName, - 'calculated_at' => \Carbon\Carbon::now()->subMinutes(random_int(1, 1000)), + 'calculated_at' => \Carbon\Carbon::now()->subDays(random_int(1, 31)), ]; }); diff --git a/database/migrations/2020_08_18_085017_fingerprints.php b/database/migrations/2020_08_18_085017_fingerprints.php index 67bd1ee..af506ec 100644 --- a/database/migrations/2020_08_18_085017_fingerprints.php +++ b/database/migrations/2020_08_18_085017_fingerprints.php @@ -19,8 +19,8 @@ class Fingerprints extends Migration $table->string('agent'); $table->ipAddress('ip'); $table->string('os'); - $table->decimal('latitude', 10, 2); - $table->decimal('longitude', 11, 2); + $table->decimal('latitude', 10, 4); + $table->decimal('longitude', 11, 4); $table->char('token', 60)->unique(); $table->timestamps(); }); diff --git a/database/migrations/2020_08_18_085018_create_businesses_table.php b/database/migrations/2020_08_18_085018_create_businesses_table.php index e37d713..b0d168f 100644 --- a/database/migrations/2020_08_18_085018_create_businesses_table.php +++ b/database/migrations/2020_08_18_085018_create_businesses_table.php @@ -23,7 +23,7 @@ class CreateBusinessesTable extends Migration $table->string('description')->nullable(); $table->json('cache')->nullable(); $table->boolean('has_avatar')->default(false); - $table->timestamp('calculated_at')->nullable(); + $table->timestamp('calculated_at')->nullable()->index(); $table->timestamp('created_at')->nullable(); $table->timestamp('updated_at')->nullable(); $table->timestamp('deleted_at')->nullable();