From 60f5278c47aaaa7f1742b60a94db5fc10362de75 Mon Sep 17 00:00:00 2001 From: Roberto Guido Date: Sat, 9 Dec 2023 14:56:39 +0100 Subject: [PATCH 1/2] limite esplicito di credito per prenotazioni. closes #230 --- code/app/Console/Commands/FixDatabase.php | 55 ++---------- code/app/Formatters/GenericProductFormat.php | 85 ++++++++----------- code/app/Gas.php | 2 + code/app/Helpers/Components.php | 2 - .../Http/Controllers/VariantsController.php | 16 +--- code/app/Models/Concerns/BookerTrait.php | 4 +- code/app/Observers/UserObserver.php | 5 +- .../Config/RestrictedBookingToCredit.php | 25 +++++- code/app/Services/BookingsService.php | 6 +- code/config/larastrap.php | 6 +- code/resources/views/booking/edit.blade.php | 6 +- code/resources/views/gas/orders.blade.php | 6 +- code/tests/Services/BookingsServiceTest.php | 28 +++++- 13 files changed, 119 insertions(+), 127 deletions(-) diff --git a/code/app/Console/Commands/FixDatabase.php b/code/app/Console/Commands/FixDatabase.php index ef24ac67f..61b186dfb 100644 --- a/code/app/Console/Commands/FixDatabase.php +++ b/code/app/Console/Commands/FixDatabase.php @@ -34,56 +34,17 @@ public function handle() Artisan::call('db:seed', ['--force' => true, '--class' => 'ModifierTypesSeeder']); /* - Per fixare gli URL dei listini autogenerati ed erroneamente - sovrascritti - */ - foreach(Supplier::all() as $supplier) { - $attachments = $supplier->attachments; - - $pdf = $attachments->firstWhere('name', 'Listino PDF (autogenerato)'); - if ($pdf) { - $pdf->url = route('suppliers.catalogue', ['id' => $supplier->id, 'format' => 'pdf']); - $pdf->save(); - } - - $csv = $attachments->firstWhere('name', 'Listino CSV (autogenerato)'); - if ($csv) { - $csv->url = route('suppliers.catalogue', ['id' => $supplier->id, 'format' => 'csv']); - $csv->save(); - } - } - - /* - Per non attivare il tour di onboarding per gli utenti esistenti - */ - User::query()->update(['tour' => true]); - - /* - Per impostare un default sull'approvazione manuale delle - registrazioni pubbliche degli utenti + Per revisionare le configurazioni relative ai limiti di credito per + permettere le prenotazioni */ foreach(Gas::all() as $gas) { - $registrations_info = $gas->public_registrations; - if (isset($registrations_info['manual']) == false) { - $registrations_info['manual'] = false; - $gas->setConfig('public_registrations', $registrations_info); - } - } - - /* - Per azzerare le vecchie configurazioni Satispay non compatibili con - la nuova implementazione - */ - foreach(Gas::all() as $gas) { - $satispay_info = $gas->satispay; - if (isset($satispay_info['public']) == false) { - $satispay_info = (object) [ - 'public' => '', - 'secret' => '', - 'key' => '', + $restriction_info = $gas->getConfig('restrict_booking_to_credit'); + if (is_array($restriction_info) == false) { + $restriction_info = (object) [ + 'enabled' => $restriction_info, + 'limit' => 0, ]; - - $gas->setConfig('satispay', $satispay_info); + $gas->setConfig('restrict_booking_to_credit', $restriction_info); } } diff --git a/code/app/Formatters/GenericProductFormat.php b/code/app/Formatters/GenericProductFormat.php index f1d6e7941..f847c56b3 100644 --- a/code/app/Formatters/GenericProductFormat.php +++ b/code/app/Formatters/GenericProductFormat.php @@ -1,59 +1,44 @@ (object) [ - 'name' => _i('Nome'), - 'checked' => true, - ], - 'supplier_code' => (object) [ - 'name' => _i('Codice Fornitore'), - ], - 'measure' => (object) [ - 'name' => _i('Unità di Misura'), - ], - 'category' => (object) [ - 'name' => _i('Categoria'), - ], - 'price' => (object) [ - 'name' => _i('Prezzo Unitario'), - 'checked' => true, - ], - 'active' => (object) [ - 'name' => _i('Ordinabile'), - ], - 'vat_rate' => (object) [ - 'name' => _i('Aliquota IVA'), - ], - 'portion_quantity' => (object) [ - 'name' => _i('Pezzatura'), - ], - 'variable' => (object) [ - 'name' => _i('Variabile'), - ], - 'package_size' => (object) [ - 'name' => _i('Dimensione Confezione'), - ], - 'weight' => (object) [ - 'name' => _i('Peso'), - ], - 'multiple' => (object) [ - 'name' => _i('Multiplo'), - ], - 'min_quantity' => (object) [ - 'name' => _i('Minimo'), - ], - 'max_quantity' => (object) [ - 'name' => _i('Massimo Consigliato'), - ], - 'max_available' => (object) [ - 'name' => _i('Disponibile'), - ], + protected static function genericColumns() + { + $attributes = [ + 'name' => _i('Nome'), + 'supplier_code' => _i('Codice Fornitore'), + 'measure' => _i('Unità di Misura'), + 'category' => _i('Categoria'), + 'price' => _i('Prezzo Unitario'), + 'active' => _i('Ordinabile'), + 'vat_rate' => _i('Aliquota IVA'), + 'portion_quantity' => _i('Pezzatura'), + 'variable' => _i('Variabile'), + 'package_size' => _i('Dimensione Confezione'), + 'weight' => _i('Peso'), + 'multiple' => _i('Multiplo'), + 'min_quantity' => _i('Minimo'), + 'max_quantity' => _i('Massimo Consigliato'), + 'max_available' => _i('Disponibile'), + ]; + + $ret = []; + $checked_by_default = ['name', 'price']; + foreach($attributes as $attr => $label) { + $ret[$attr] = (object) [ + 'name' => $label, + 'checked' => in_array($attr, $checked_by_default), ]; } + + return $ret; + } } diff --git a/code/app/Gas.php b/code/app/Gas.php index 07d85032a..ca64eb9c8 100644 --- a/code/app/Gas.php +++ b/code/app/Gas.php @@ -103,6 +103,8 @@ public function hasFeature($name) return (!empty($obj->extra_invoicing['taxcode']) || !empty($obj->extra_invoicing['vat'])); case 'public_registrations': return $obj->public_registrations['enabled']; + case 'restrict_booking_to_credit': + return $obj->restrict_booking_to_credit['enabled']; case 'auto_aggregates': return Aggregate::has('orders', '>=', aggregatesConvenienceLimit())->count() > 3; case 'send_order_reminder': diff --git a/code/app/Helpers/Components.php b/code/app/Helpers/Components.php index 0bd5d3a4c..80e7e6cb4 100644 --- a/code/app/Helpers/Components.php +++ b/code/app/Helpers/Components.php @@ -115,8 +115,6 @@ function formatObjectsToComponent($component, $params) function formatPriceToComponent($component, $params) { - $value = printablePrice($params['value']); - if (isset($params['currency']) == false) { $currency = defaultCurrency()->symbol; } diff --git a/code/app/Http/Controllers/VariantsController.php b/code/app/Http/Controllers/VariantsController.php index 7f6b9012e..5ef265d48 100644 --- a/code/app/Http/Controllers/VariantsController.php +++ b/code/app/Http/Controllers/VariantsController.php @@ -68,15 +68,7 @@ public function matrix(Request $request, $id) private function transformFromSimplified($request, $product) { $original_combinations = $request->input('combination', []); - - $ids = array_map(function($item) { - if (Str::startsWith($item, 'new_')) { - return ''; - } - else { - return $item; - } - }, $original_combinations); + $ids = array_map(fn($item) => Str::startsWith($item, 'new_') ? '' : $item, $original_combinations); $values = $request->input('value', []); @@ -87,11 +79,7 @@ private function transformFromSimplified($request, $product) 'value' => $values, ]); - $combinations = []; - - foreach($values as $value) { - $combinations[] = $variant->values()->where('value', $value)->first()->id; - } + $combinations = array_map(fn($v) => $variant->values()->where('value', $v)->first()->id, $values); /* Ai nuovi valori dinamicamente immessi nella tabella aggiungo un diff --git a/code/app/Models/Concerns/BookerTrait.php b/code/app/Models/Concerns/BookerTrait.php index db3b024c3..1fbd6089e 100644 --- a/code/app/Models/Concerns/BookerTrait.php +++ b/code/app/Models/Concerns/BookerTrait.php @@ -36,12 +36,12 @@ public function getLastBookingAttribute() public function canBook() { - if ($this->gas->restrict_booking_to_credit) { + if ($this->gas->hasFeature('restrict_booking_to_credit')) { if ($this->isFriend()) { return $this->parent->canBook(); } else { - return $this->activeBalance() > 0; + return $this->activeBalance() > $this->gas->restrict_booking_to_credit['limit']; } } else { diff --git a/code/app/Observers/UserObserver.php b/code/app/Observers/UserObserver.php index 1e00a0428..c30949cab 100644 --- a/code/app/Observers/UserObserver.php +++ b/code/app/Observers/UserObserver.php @@ -12,7 +12,10 @@ class UserObserver { private function checkUsername($user) { - $test = User::withTrashed()->where('id', '!=', $user->id)->where('username', $user->username)->first(); + /* + Lo username deve essere univoco per tutti gli utenti di tutti i GAS + */ + $test = User::withTrashed()->withoutGlobalScopes()->where('id', '!=', $user->id)->where('username', $user->username)->first(); if ($test != null) { throw new IllegalArgumentException(_i('Username già assegnato'), 'username'); } diff --git a/code/app/Parameters/Config/RestrictedBookingToCredit.php b/code/app/Parameters/Config/RestrictedBookingToCredit.php index 10c60eaee..daec3508f 100644 --- a/code/app/Parameters/Config/RestrictedBookingToCredit.php +++ b/code/app/Parameters/Config/RestrictedBookingToCredit.php @@ -11,11 +11,32 @@ public function identifier() public function type() { - return 'boolean'; + return 'object'; } public function default() { - return 0; + return (object) [ + 'enabled' => false, + 'limit' => 0, + ]; + } + + public function handleSave($gas, $request) + { + if ($request->has('enable_restrict_booking_to_credit')) { + $restriction_info = (object) [ + 'enabled' => true, + 'limit' => $request->input('restrict_booking_to_credit->limit', 0), + ]; + } + else { + $restriction_info = (object) [ + 'enabled' => false, + 'limit' => 0, + ]; + } + + $gas->setConfig('restrict_booking_to_credit', $restriction_info); } } diff --git a/code/app/Services/BookingsService.php b/code/app/Services/BookingsService.php index 237931b81..ff2db37ae 100644 --- a/code/app/Services/BookingsService.php +++ b/code/app/Services/BookingsService.php @@ -401,16 +401,16 @@ public function handleBookingUpdate($request, $user, $order, $target_user, $deli */ private function checkAvailableCredit($user) { - if ($user->gas->restrict_booking_to_credit) { + if ($user->gas->hasFeature('restrict_booking_to_credit')) { /* Questa funzione viene invocata dopo aver salvato la prenotazione, nel contesto di una transazione, dunque il bilancio attivo dell'utente già prevede la prenotazione stessa e - pertanto, per essere valido, deve essere superiore a 0 + pertanto, per essere valido, deve essere superiore al limite */ $current_active_balance = $user->activeBalance(); - if ($current_active_balance < 0) { + if ($current_active_balance < $user->gas->restrict_booking_to_credit['limit']) { DB::rollback(); throw new IllegalArgumentException(_i('Credito non sufficiente'), 1); } diff --git a/code/config/larastrap.php b/code/config/larastrap.php index 2aa028336..7064dde1d 100644 --- a/code/config/larastrap.php +++ b/code/config/larastrap.php @@ -66,7 +66,11 @@ 'tabpane' => [ 'reviewCallback' => 'formatTabLabel', - ] + ], + + 'collapse' => [ + 'classes' => ['mb-2'], + ], ], 'customs' => [ diff --git a/code/resources/views/booking/edit.blade.php b/code/resources/views/booking/edit.blade.php index 5aa4ff264..ae715fdd5 100644 --- a/code/resources/views/booking/edit.blade.php +++ b/code/resources/views/booking/edit.blade.php @@ -40,8 +40,8 @@ - @if($user->gas->restrict_booking_to_credit) - + @if($user->gas->hasFeature('restrict_booking_to_credit')) + @endif
@@ -214,7 +214,7 @@ 'skip_cells' => 3 ]) - @if($user->gas->restrict_booking_to_credit) + @if($user->gas->hasFeature('restrict_booking_to_credit'))   diff --git a/code/resources/views/gas/orders.blade.php b/code/resources/views/gas/orders.blade.php index 8c04fcc72..b560b27e5 100644 --- a/code/resources/views/gas/orders.blade.php +++ b/code/resources/views/gas/orders.blade.php @@ -4,7 +4,11 @@
- + + + + + gas->setConfig('restrict_booking_to_credit', true); + $this->gas->setConfig('restrict_booking_to_credit', (object) [ + 'enabled' => true, + 'limit' => 0, + ]); $this->actingAs($this->userWithBasePerms); @@ -467,6 +473,26 @@ public function testInsufficientCredit() } } + /* + Prenotazione con limiti di credito diversi da 0 abilitati + */ + public function testInsufficientCreditWithLimit() + { + $this->gas->setConfig('restrict_booking_to_credit', (object) [ + 'enabled' => true, + 'limit' => -20, + ]); + + $this->actingAs($this->userWithBasePerms); + + list($data, $booked_count, $total) = $this->randomQuantities($this->sample_order->products); + $this->userWithBasePerms = $this->enforceBalance($this->userWithBasePerms, $total - 10); + + $data['action'] = 'booked'; + $booking = $this->updateAndFetch($data, $this->sample_order, $this->userWithBasePerms, false); + $this->assertNotNull($booking); + } + /* I test per prenotazioni fatte da un amico sono fatti in ModifiersServiceTest From f98146a3c6959e49bbc52c334980b78c730f2f55 Mon Sep 17 00:00:00 2001 From: Roberto Guido Date: Wed, 13 Dec 2023 15:10:41 +0100 Subject: [PATCH 2/2] fix modificatori per ordini. closes #245 --- code/app/Http/Controllers/MailController.php | 7 +- code/app/Models/Concerns/ReducibleTrait.php | 2 +- code/app/Singletons/ModifierEngine.php | 2 +- .../Services/DynamicBookingsServiceTest.php | 108 +++++++++++++++++- code/tests/Services/ModifiersServiceTest.php | 20 +++- 5 files changed, 128 insertions(+), 11 deletions(-) diff --git a/code/app/Http/Controllers/MailController.php b/code/app/Http/Controllers/MailController.php index f2874ae27..5d886199a 100644 --- a/code/app/Http/Controllers/MailController.php +++ b/code/app/Http/Controllers/MailController.php @@ -3,10 +3,8 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; -use App\Http\Controllers\Controller; - -use DB; -use Log; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Aws\Sns\Message; use Aws\Sns\MessageValidator; @@ -33,6 +31,7 @@ private function saveInstances($email, $message) $db->delete("DELETE FROM contacts WHERE type = 'email' and value = '$email'"); $message = _i('Rimosso indirizzo email ' . $email); $db->insert("INSERT INTO inner_logs (level, type, message, created_at, updated_at) VALUES ('error', 'mailsuppression', '$message', '$now', '$now')"); + Log::info($message); } } } diff --git a/code/app/Models/Concerns/ReducibleTrait.php b/code/app/Models/Concerns/ReducibleTrait.php index c4e80e64b..c023103fe 100644 --- a/code/app/Models/Concerns/ReducibleTrait.php +++ b/code/app/Models/Concerns/ReducibleTrait.php @@ -242,7 +242,7 @@ public function minimumRedux($modifiers) } } - if (($faster || $target_priority <= 1) && ($booking && $order)) { + if (($faster && $target_priority <= 1) && ($booking && $order)) { $aggregate_data = $aggregate->reduxData([ 'orders' => [$order], 'bookings' => [$booking] diff --git a/code/app/Singletons/ModifierEngine.php b/code/app/Singletons/ModifierEngine.php index 27d1ce5bf..11eb2e4b9 100644 --- a/code/app/Singletons/ModifierEngine.php +++ b/code/app/Singletons/ModifierEngine.php @@ -108,7 +108,7 @@ private function handlingAttributes($booking, $modifier) OrdersController::postFixModifiers(), previa conferma dell'utente, quando l'ordine è davvero in stato "consegnato" */ - if ($booking->status != 'pending') { + if (in_array($booking->status, ['saved', 'shipped'])) { switch($modifier->applies_type) { case 'none': case 'quantity': diff --git a/code/tests/Services/DynamicBookingsServiceTest.php b/code/tests/Services/DynamicBookingsServiceTest.php index aa1e05e29..f4afc2530 100644 --- a/code/tests/Services/DynamicBookingsServiceTest.php +++ b/code/tests/Services/DynamicBookingsServiceTest.php @@ -7,6 +7,7 @@ use Illuminate\Support\Collection; use App\Exceptions\AuthException; +use App\Booking; class DynamicBookingsServiceTest extends TestCase { @@ -65,7 +66,7 @@ public function testSimple() $this->nextRound(); - $booking = \App\Booking::where('order_id', $this->order->id)->where('user_id', $this->userWithBasePerms->id)->first(); + $booking = Booking::where('order_id', $this->order->id)->where('user_id', $this->userWithBasePerms->id)->first(); $this->assertEquals($booking->getValue('effective', true), $total); $this->assertEquals($booking->products()->count(), $booked_count); } @@ -96,7 +97,7 @@ public function testFriend() $this->nextRound(); - $booking = \App\Booking::where('order_id', $this->order->id)->where('user_id', $this->userWithBasePerms->id)->first(); + $booking = Booking::where('order_id', $this->order->id)->where('user_id', $this->userWithBasePerms->id)->first(); $this->assertEquals($booking->getValue('effective', true), $total_master + $total_friend); $this->nextRound(); @@ -318,7 +319,7 @@ private function attachDiscount($product) /* Lettura dinamica delle prenotazioni, prodotto con modificatori */ - public function testModifiers() + public function testModifiersOnProduct() { $product = $this->order->products->random(); $this->attachDiscount($product); @@ -349,7 +350,7 @@ public function testModifiers() /* Lettura dinamica delle prenotazioni, prodotto con varianti e modificatori */ - public function testModifiersAndVariants() + public function testModifiersOnProductAndVariants() { $this->actingAs($this->userReferrer); @@ -391,4 +392,103 @@ public function testModifiersAndVariants() } } } + + /* + Lettura dinamica delle prenotazioni, modificatore su ordine + */ + public function testModifiersOnOrder() + { + $this->actingAs($this->userReferrer); + + /* + Creo modificatore su ordine + */ + $mod = null; + $modifiers = $this->order->applicableModificationTypes(); + foreach ($modifiers as $mod) { + if ($mod->id == 'sconto') { + $mod = $this->order->modifiers()->where('modifier_type_id', $mod->id)->first(); + app()->make('ModifiersService')->update($mod->id, [ + 'value' => 'percentage', + 'arithmetic' => 'sub', + 'scale' => 'major', + 'applies_type' => 'price', + 'applies_target' => 'order', + 'distribution_type' => 'price', + 'threshold' => [1000], + 'amount' => [10], + ]); + + break; + } + } + + $this->assertNotNull($mod); + $product = $this->order->products->random(); + + /* + Effettuo prenotazione senza attivare modificatori + */ + + $this->nextRound(); + $this->actingAs($this->userWithBasePerms); + + $data = [ + 'action' => 'booked', + $product->id => 2, + ]; + + $ret = app()->make('DynamicBookingsService')->dynamicModifiers($data, $this->order->aggregate, $this->userWithBasePerms); + + $this->assertEquals(count($ret->bookings), 1); + + foreach($ret->bookings as $b) { + $this->assertTrue($b->total < 1000); + $this->assertEquals(count($b->products), 1); + $this->assertEquals($b->total, $product->price * 2); + $this->assertEquals(count($b->modifiers), 0); + } + + /* + Creo una prenotazione che faccia attivare i modificatori + */ + + $this->nextRound(); + $this->actingAs($this->userReferrer); + + $data = ['action' => 'booked']; + $total = 0; + + foreach($this->order->products as $p) { + $data[$p->id] = 100; + $total += $p->price * 100; + } + + $this->assertTrue($total > 100); + $this->updateAndFetch($data, $this->order, $this->userReferrer, false); + $booking = Booking::where('order_id', $this->order->id)->where('user_id', $this->userReferrer->id)->first(); + $this->assertNotNull($booking); + + /* + Aggiorno prenotazione con modificatori attivi + */ + + $this->nextRound(); + $this->actingAs($this->userWithBasePerms); + + $data = [ + 'action' => 'booked', + $product->id => 2, + ]; + + $ret = app()->make('DynamicBookingsService')->dynamicModifiers($data, $this->order->aggregate, $this->userWithBasePerms); + + $this->assertEquals(count($ret->bookings), 1); + + foreach($ret->bookings as $b) { + $this->assertEquals(count($b->products), 1); + $this->assertEquals($b->total, $product->price * 2 - ($product->price * 0.10 * 2)); + $this->assertEquals(count($b->modifiers), 1); + } + } } diff --git a/code/tests/Services/ModifiersServiceTest.php b/code/tests/Services/ModifiersServiceTest.php index d6fdf940c..4c3a35940 100644 --- a/code/tests/Services/ModifiersServiceTest.php +++ b/code/tests/Services/ModifiersServiceTest.php @@ -175,7 +175,7 @@ public function testThresholdUnitPricePortions() $mod = null; $thresholds = [10, 5, 0]; - $threshold_prices = [0.9, 0.92, 0.94]; + $threshold_prices = [1, 2, 3]; foreach ($modifiers as $mod) { if ($mod->id == 'sconto') { @@ -253,6 +253,10 @@ public function testThresholdUnitPricePortions() $this->nextRound(); + /* + Da qui agisco in consegna, dunque con le quantità a peso + */ + $this->actingAs($this->userWithShippingPerms); $data = [ @@ -280,7 +284,21 @@ public function testThresholdUnitPricePortions() $product->id => 4, ]; app()->make('BookingsService')->bookingUpdate($data, $order->aggregate, $booking->user, true); + + $this->nextRound(); + $booking = $booking->fresh(); + $found = false; + + foreach($booking->products as $prod) { + if ($prod->product_id == $product->id) { + $found = true; + $this->assertEquals(4, $prod->delivered); + } + } + + $this->assertTrue($found); + $mods = $booking->applyModifiers(null, false); $this->assertEquals(\App\ModifiedValue::count(), 1); $this->assertEquals($mods->count(), 1);