diff --git a/.github/actions/upload-artifact b/.github/actions/upload-artifact new file mode 160000 index 0000000..6027e3d --- /dev/null +++ b/.github/actions/upload-artifact @@ -0,0 +1 @@ +Subproject commit 6027e3dd177782cd8ab9af838c04fd81a07f1d47 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bbabb30 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: Build ocktopus + +on: + push: + branches: [ main ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install PHP dependencies + run: composer install --no-dev --optimize-autoloader + + - name: Prepare deploy folder + run: | + mkdir deploy + rsync -av --progress ./ ./deploy \ + --exclude='.env' \ + --exclude='storage' \ + --exclude='node_modules' \ + --exclude='tests' \ + --exclude='vendor/bin' \ + --exclude='deploy' \ + --exclude='.git' + + cd deploy + tar -czf ../deploy.tar.gz . + + - name: Upload artifact + uses: ./.github/actions/upload-artifact + with: + name: laravel-deploy-package + path: deploy.tar.gz diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php new file mode 100644 index 0000000..b7de874 --- /dev/null +++ b/app/Http/Controllers/TransactionController.php @@ -0,0 +1,180 @@ +input('badge_id'); + $items = $request->input('items'); // [[article_id, quantity], ...] + $sessionId = $request->input('session_id'); + + $systemId = env('WEEZEVENT_SYSTEM_ID'); + $appKey = env('WEEZEVENT_APP_KEY'); + $fundId = env('WEEZEVENT_FUND_ID'); + + $mappedItems = []; + foreach ($items as [$articleId, $quantity]) { + $categoryId = $this->getCategoryFromArticle($articleId); + if ($categoryId == 11 || $categoryId == 10) { // Si c'est une bière pression ou bouteille, on remplace par le prix du marché + $firstTransaction = Http::withHeaders([ // Première transaction de l'article (qui aura été mis à 0e) pour les stats + 'accept-language' => 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', + ])->post('https://api.nemopay.net/services/POSS3/transaction?system_id='.$systemId.'&app_key='.$appKey.'&sessionid='.$sessionId, [ + 'badge_id' => $badgeId, + 'obj_ids' => [[$articleId, $quantity]], + 'fun_id' => $fundId, + ])->throw(); + + $currentPrice = $this->getCurrentMarketPrice($articleId); + $replacementArticleId = $this->mapPriceToBeerArticle($currentPrice); + for ($i = 0; $i < $quantity; $i++) { + $currentPrice = $this->getCurrentMarketPrice($articleId); + $replacementArticleId = $this->mapPriceToBeerArticle($currentPrice); + + $mappedItems[] = [$replacementArticleId, 1]; // On passe les articles 1 par 1 au cas où le prix augmente + $this->updateMarket($articleId, 1); + } + } else { + $mappedItems[] = [$articleId, $quantity]; + } + } + + $response = Http::withHeaders([ + 'accept-language' => 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7', + ])->post('https://api.nemopay.net/services/POSS3/transaction?system_id='.$systemId.'&app_key='.$appKey.'&sessionid='.$sessionId, [ + 'badge_id' => $badgeId, + 'obj_ids' => $mappedItems, + 'fun_id' => $fundId, + ])->throw(); + + return response()->json($response->json()); + } + + private function getCategoryFromArticle($articleId) + { + $article = Articles::where('article_id', $articleId)->first() ?? null; + return $article ? $article->category_id : null; + } + + private function getCurrentMarketPrice($articleId) + { + return MarketPrices::where('article_id', $articleId) + ->first() + ->price ?? 1.80; + } + + private function mapPriceToBeerArticle($price) // Calcule l'id de la bière sachant que 60 centimes c'est l'id 23658 et qu'on augmente l'id de 1 par centime + { + $rounded = number_format($price, 2, '.', ''); + $basePrice = 0.60; + $baseId = 23658; + + $diff = ($rounded - $basePrice) * 100; // nb centimes d'écart + $id = $baseId + (int)round($diff); // l'ID part de baseId + centimes d'écart + + if ($id < $baseId || $id > 23658 + 180) { // Si le prix est inférieur à 0.60 ou spérieur à 2.40 + return 23778; // Prix inconnu ? -> 1e80 + } + + return $id; + } + + private function updateMarket($articleId, $quantity) + { + $maxPrice = 2.4; + $minPrice = 0.6; + $priceStep = 0.07; // Plus ou moins de fluctuation sur le prix du marché + $balanceMarket = 1.80; // Le marché se balance autour de cette valeur + + $article = Articles::where('article_id', $articleId)->first(); + if (!$article) return; + + $categoryId = $article->category_id; + $allArticles = Articles::where('category_id', $categoryId)->get(); + $nbArticles = $allArticles->count(); + + if ($nbArticles <= 1) return; + + $weights = []; + $totalWeight = 0; + foreach ($allArticles as $otherArticle) { + if ($otherArticle->article_id == $articleId) continue; + + $w = mt_rand(50, 150); + $weights[$otherArticle->article_id] = $w; + $totalWeight += $w; + } + + foreach ($allArticles as $otherArticle) { + $currentPrice = MarketPrices::firstOrNew( + ['article_id' => $otherArticle->article_id] + ); + + if (!$currentPrice->exists) { + $currentPrice->price = 1.00; + } + + $newPrice = $currentPrice->price; + + if ($otherArticle->article_id == $articleId) { + $newPrice += $priceStep * $quantity; + } else { + $weight = $weights[$otherArticle->article_id]; + $share = ($priceStep * $quantity) * ($weight / $totalWeight); + $newPrice -= $share; + } + + $newPrice = max($minPrice, min($maxPrice, round($newPrice, 2))); + + $currentPrice->price = $newPrice; + $currentPrice->updated_at = now(); + $currentPrice->save(); + } + + $sum = 0; + foreach ($allArticles as $article) { + $marketPrice = MarketPrices::where('article_id', $article->article_id)->first(); + $sum += $marketPrice ? $marketPrice->price : 1.00; + } + + $mean = $sum / $nbArticles; + $delta = $balanceMarket - $mean; + $correction = round($delta, 4); + + foreach ($allArticles as $article) { + $marketPrice = MarketPrices::where('article_id', $article->article_id)->first(); + if (!$marketPrice) continue; + + $adjusted = max($minPrice, min($maxPrice, round($marketPrice->price + $correction, 2))); + $marketPrice->price = $adjusted; + $marketPrice->save(); + } + } + + public function getPrices() + { + $articles = MarketPrices::all(); + + foreach ($articles as $article) { + $articleModel = Articles::where('article_id', $article->article_id)->first(); + $article->article_name = $articleModel ? $articleModel->article_name : null; + $article->category_id = $articleModel ? $articleModel->category_id : null; + } + + $response = [ + 'refresh' => 5000, + 'data' => $articles, + ]; + + return response()->json($response) + ->header('Access-Control-Allow-Origin', '*') + ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + ->header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + } +} diff --git a/app/Models/Articles.php b/app/Models/Articles.php new file mode 100644 index 0000000..702a044 --- /dev/null +++ b/app/Models/Articles.php @@ -0,0 +1,13 @@ +integer('article_id')->primary(); + $table->string('article_name'); + $table->integer('category_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('articles'); + } +}; diff --git a/database/migrations/create_market_prices_table.php b/database/migrations/create_market_prices_table.php new file mode 100644 index 0000000..fab4133 --- /dev/null +++ b/database/migrations/create_market_prices_table.php @@ -0,0 +1,28 @@ +integer('article_id')->primary(); + $table->decimal('price', 3, 2)->default(1.00); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('market_prices'); + } +}; diff --git a/routes/api.php b/routes/api.php index acdebed..e1d553d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,6 +3,7 @@ use App\Http\Controllers\BachController; use App\Http\Controllers\ExonerationController; use App\Http\Controllers\TodayConsumptionController; +use App\Http\Controllers\TransactionController; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; @@ -36,3 +37,5 @@ // Exoneration d'un achat Route::post('/exoneration', [ExonerationController::class, 'storeExonerations']); + +Route::post('/transaction', [TransactionController::class, 'handle']); \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 990be13..adc44c3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -58,3 +58,5 @@ return response()->json(['message' => 'Image not found'], 404); } })->name('download'); + +Route::get('/bourse',[\App\Http\Controllers\TransactionController::class,'getPrices']);