From bc60232ed78e402fed5d6fcfdc905920ae5bfcd8 Mon Sep 17 00:00:00 2001 From: Roberto Guido Date: Sat, 23 Nov 2024 14:47:11 +0100 Subject: [PATCH] modificatore prodotto su valore complessivo dell'ordine. closes #295 --- code/app/Models/Concerns/ReducibleTrait.php | 56 ++++++------ code/app/Modifier.php | 31 +++++++ code/app/Singletons/ModifierEngine.php | 72 +++++++--------- code/app/View/Texts/Modifier.php | 2 + code/composer.json | 1 + ..._11_22_174600_order_price_in_modifiers.php | 28 ++++++ code/resources/views/modifier/edit.blade.php | 1 + code/tests/Services/ModifiersServiceTest.php | 85 ++++++++++++++++++- 8 files changed, 210 insertions(+), 66 deletions(-) create mode 100644 code/database/migrations/2024_11_22_174600_order_price_in_modifiers.php diff --git a/code/app/Models/Concerns/ReducibleTrait.php b/code/app/Models/Concerns/ReducibleTrait.php index 6c7f27bc..5fc8c56e 100644 --- a/code/app/Models/Concerns/ReducibleTrait.php +++ b/code/app/Models/Concerns/ReducibleTrait.php @@ -225,15 +225,34 @@ public function minimumRedux($modifiers) break; } - $priority = ['product', 'booking', 'order', 'aggregate']; + /* + Se il modificatore riguarda solo il prodotto nel contesto della + prenotazione o la prenotazione stessa, riduco solo la singola + prenotazione. + Se si applica all'ordine o ad prodotto complessivo, riduco solo + l'ordine. + In extremis, riduco l'intero aggregato + */ + $priority = [ + ['product'], + ['booking'], + ['order', 'global_product'], + ['aggregate'] + ]; + $target_priority = -1; $aggregate_data = null; $faster = true; foreach($modifiers as $mod) { - $p = array_search($mod->applies_target, $priority); - if ($p > $target_priority) { - $target_priority = $p; + $target_level = $mod->getCheckTargetLevel(); + \Log::debug('target_level = ' . $target_level); + + foreach($priority as $priority_index => $items) { + if (in_array($target_level, $items) && $priority_index > $target_priority) { + $target_priority = $priority_index; + break; + } } if ($mod->value != 'percentage' || $mod->distribution_type != 'price') { @@ -241,32 +260,19 @@ public function minimumRedux($modifiers) } } - if (($faster && $target_priority <= 1) && ($booking && $order)) { - $aggregate_data = $aggregate->reduxData([ - 'orders' => [$order], - 'bookings' => [$booking] - ]); - } - else { - $target_priority = 2; - } + $redux_filters = []; - if (is_null($aggregate_data)) { - if ($target_priority == 2 && $order) { - $aggregate_data = $aggregate->reduxData([ - 'orders' => [$order] - ]); - } - else { - $target_priority = 3; - } + \Log::debug('target_priority = ' . $target_priority); - if ($target_priority == 3) { - $aggregate_data = $aggregate->reduxData(); + if ($target_priority <= 2 && $order) { + if ($target_priority <= 1 && $faster && $booking) { + $redux_filters['bookings'] = [$booking]; } + + $redux_filters['orders'] = [$order]; } - return $aggregate_data; + return $aggregate_data = $aggregate->reduxData($redux_filters); } /* diff --git a/code/app/Modifier.php b/code/app/Modifier.php index b91d9aa9..ae182684 100644 --- a/code/app/Modifier.php +++ b/code/app/Modifier.php @@ -114,6 +114,37 @@ public function getROShowURL() return route('modifiers.show', $this->id); } + /* + Questa funzione permette di capire a che livello della gerarchia si + applica il modificatore. + "order" e "booking" si riferiscono, rispettivamente, all'ordine nel suo + complesso o alla specifica prenotazione. + "product" si riferisce al prodotto all'interno della prenotazione. + "global_product" si riferisce al prodotto complessivo nell'ordine + */ + public function getCheckTargetLevel() + { + if ($this->target_type == Product::class) { + if ($this->applies_type == 'order_price') { + return 'order'; + } + else { + switch($this->applies_target) { + case 'order': + return 'global_product'; + break; + + default: + return 'product'; + break; + } + } + } + else { + return $this->applies_target; + } + } + public function getActiveAttribute() { $data = $this->definitions; diff --git a/code/app/Singletons/ModifierEngine.php b/code/app/Singletons/ModifierEngine.php index 362321f0..44cb1f08 100644 --- a/code/app/Singletons/ModifierEngine.php +++ b/code/app/Singletons/ModifierEngine.php @@ -104,13 +104,14 @@ private function handlingAttributes($booking, $modifier, $attribute) non ancora consegnato. Questo si applica in particolare in fase di consegna */ - if ($modifier->applies_target == 'order' || $booking->order->status == 'closed') { + if ($modifier->applies_target == 'order' || $booking->order->status == 'closed' || $modifier->applies_type == 'order_price') { switch($modifier->$attribute) { case 'quantity': $attribute = 'relative_quantity'; break; case 'none': case 'price': + case 'order_price': $attribute = 'relative_price'; break; case 'weight': @@ -237,62 +238,53 @@ public function apply($modifier, $booking, $aggregate_data) return null; } + $order_id = $booking->order_id; + $aggregate_data = $this->normalizeAggregateData($aggregate_data, $booking); if (is_null($aggregate_data)) { - Log::debug('Applicazione modificatore: mancano dati ordine ' . $booking->order_id); + Log::debug('Applicazione modificatore: mancano dati ordine ' . $order_id); return null; } - $product_target_id = 0; - - if ($modifier->target_type == Product::class) { - $product_target_id = $modifier->target_id; - - switch($modifier->applies_target) { - case 'order': - $check_target = $aggregate_data->orders[$booking->order_id]->products[$product_target_id] ?? null; - break; - - default: - $check_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id]->products[$product_target_id] ?? null; - break; - } - } - else { - switch($modifier->applies_target) { - case 'order': - $check_target = $aggregate_data->orders[$booking->order_id] ?? null; - break; + /* + $check_target è l'elemento su cui valutare l'applicabilità del + modificatore + */ + switch($modifier->getCheckTargetLevel()) { + case 'order': + $check_target = $aggregate_data->orders[$order_id] ?? null; + break; - case 'booking': - $check_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id] ?? null; - break; + case 'booking': + $check_target = $aggregate_data->orders[$order_id]->bookings[$booking->id] ?? null; + break; - case 'product': - $check_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id]->products[$modifier->target->id] ?? null; - break; + case 'product': + $check_target = $aggregate_data->orders[$order_id]->bookings[$booking->id]->products[$modifier->target->id] ?? null; + break; - default: - Log::error('applies_target non riconosciuto per modificatore: ' . $modifier->applies_target); - return null; - } + case 'global_product': + $check_target = $aggregate_data->orders[$order_id]->products[$modifier->target->id] ?? null; + break; } + /* + $mod_target è l'elemento su cui si applica il modificatore + */ switch($modifier->applies_target) { case 'order': - $mod_target = $aggregate_data->orders[$booking->order_id] ?? null; + $mod_target = $aggregate_data->orders[$order_id] ?? null; $obj_mod_target = $booking; break; case 'booking': - $mod_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id] ?? null; + $mod_target = $aggregate_data->orders[$order_id]->bookings[$booking->id] ?? null; $obj_mod_target = $booking; break; case 'product': - $product_target_id = $modifier->target->id; - $mod_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id]->products[$product_target_id] ?? null; - $obj_mod_target = $booking->products()->where('product_id', $product_target_id)->first(); + $mod_target = $aggregate_data->orders[$order_id]->bookings[$booking->id]->products[$modifier->target->id] ?? null; + $obj_mod_target = $booking->products()->where('product_id', $modifier->target->id)->first(); break; default: @@ -322,11 +314,11 @@ public function apply($modifier, $booking, $aggregate_data) list($distribution_attribute, $useless) = $this->handlingAttributes($booking, $modifier, 'distribution_type'); if ($modifier->target_type == Product::class) { - $booking_mod_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id]->products[$product_target_id] ?? null; - $reference = $mod_target->products[$product_target_id]->$distribution_attribute; + $booking_mod_target = $aggregate_data->orders[$order_id]->bookings[$booking->id]->products[$modifier->target->id] ?? null; + $reference = $mod_target->products[$modifier->target->id]->$distribution_attribute; } else { - $booking_mod_target = $aggregate_data->orders[$booking->order_id]->bookings[$booking->id] ?? null; + $booking_mod_target = $aggregate_data->orders[$order_id]->bookings[$booking->id] ?? null; $reference = $mod_target->$distribution_attribute; } diff --git a/code/app/View/Texts/Modifier.php b/code/app/View/Texts/Modifier.php index 5d9baa58..9211bdec 100644 --- a/code/app/View/Texts/Modifier.php +++ b/code/app/View/Texts/Modifier.php @@ -12,6 +12,7 @@ private static function valueLabels() 'none' => '', 'quantity' => _i('la quantità'), 'price' => _i('il valore'), + 'order_price' => _i("il valore dell'ordine"), 'weight' => _i('il peso'), ]; } @@ -61,6 +62,7 @@ private static function unitLabels($target) 'none' => 'X', 'quantity' => $quantity_label, 'price' => $currency, + 'order_price' => $currency, 'weight' => _i('Chili'), ]; } diff --git a/code/composer.json b/code/composer.json index 82a133b3..7119d260 100644 --- a/code/composer.json +++ b/code/composer.json @@ -12,6 +12,7 @@ "barryvdh/laravel-debugbar": "^3.8", "barryvdh/laravel-dompdf": "^2.0.0", "debril/feed-io": "^5.0", + "doctrine/dbal": "^3.9", "dotworkers/laravel-gettext": "^7.6.2", "eluceo/ical": "^2.0", "genealabs/laravel-model-caching": "^0.13.4", diff --git a/code/database/migrations/2024_11_22_174600_order_price_in_modifiers.php b/code/database/migrations/2024_11_22_174600_order_price_in_modifiers.php new file mode 100644 index 00000000..3c94f46d --- /dev/null +++ b/code/database/migrations/2024_11_22_174600_order_price_in_modifiers.php @@ -0,0 +1,28 @@ +string('applies_type')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('modifiers', function (Blueprint $table) { + $table->enum('applies_type', ['none', 'quantity', 'price', 'weight'])->change(); + }); + } +}; diff --git a/code/resources/views/modifier/edit.blade.php b/code/resources/views/modifier/edit.blade.php index bb52972a..ab0b3047 100644 --- a/code/resources/views/modifier/edit.blade.php +++ b/code/resources/views/modifier/edit.blade.php @@ -47,6 +47,7 @@ 'none' => _i('Nessuna soglia'), 'quantity' => _i('Quantità'), 'price' => _i('Valore'), + 'order_price' => _i("Valore dell'Ordine"), 'weight' => _i('Peso'), ]; diff --git a/code/tests/Services/ModifiersServiceTest.php b/code/tests/Services/ModifiersServiceTest.php index 0f3166f6..006e790f 100644 --- a/code/tests/Services/ModifiersServiceTest.php +++ b/code/tests/Services/ModifiersServiceTest.php @@ -146,7 +146,7 @@ public function testThresholdUnitPrice() $order = app()->make('OrdersService')->show($this->order->id); $modifiers = $order->applyModifiers(); $aggregated_modifiers = \App\ModifiedValue::aggregateByType($modifiers); - $this->assertEquals(count($aggregated_modifiers), 1); + $this->assertEquals(1, count($aggregated_modifiers)); $without_discount = $product->price * $total_quantity; $total = $threshold_prices[$threshold_index] * $total_quantity; @@ -358,6 +358,7 @@ public function testThresholdQuantity() $redux = $order->aggregate->reduxData(); $this->assertNotEquals($redux->price, 0); + $exists = false; foreach($order->bookings as $booking) { $mods = $booking->applyModifiersWithFriends($redux, true); @@ -367,6 +368,8 @@ public function testThresholdQuantity() $this->assertEquals($mods->count(), 0); } else { + $exists = true; + if ($booked_product->quantity <= 5) { $this->assertEquals($mods->count(), 0); } @@ -383,6 +386,86 @@ public function testThresholdQuantity() } } } + + $this->assertTrue($exists); + } + + /* + Modificatore sul Prodotto, con soglia sull'Ordine + */ + public function testOrderPrice() + { + $this->localInitOrder(); + $this->actingAs($this->userReferrer); + + $order = app()->make('OrdersService')->show($this->order->id); + $redux = $order->aggregate->reduxData(); + $current_total = $redux->price; + + $booked_product = $order->bookings->random()->products->filter(fn($p) => $p->quantity != 0)->random(); + $product = $booked_product->product; + + $modifiers = $product->applicableModificationTypes(); + $this->assertEquals(count($modifiers), 2); + $mod = null; + + foreach ($modifiers as $mod) { + if ($mod->id == 'sconto') { + $mod = $product->modifiers()->where('modifier_type_id', $mod->id)->first(); + app()->make('ModifiersService')->update($mod->id, [ + 'arithmetic' => 'sub', + 'scale' => 'major', + 'applies_type' => 'order_price', + 'applies_target' => 'product', + 'value' => 'percentage', + 'threshold' => [$current_total + 10], + 'amount' => [5], + ]); + + break; + } + } + + $this->assertNotNull($mod); + + $this->nextRound(); + + $order = app()->make('OrdersService')->show($this->order->id); + $modifiers = $order->applyModifiers(); + $this->assertEquals(0, $modifiers->count()); + + do { + $other_booked_product = $order->bookings->random()->products->filter(fn($p) => $p->quantity != 0)->random(); + } while($other_booked_product->product_id != $booked_product->product_id); + + $other_booked_product->quantity += 20; + $other_booked_product->save(); + + $this->nextRound(); + + $order = app()->make('OrdersService')->show($this->order->id); + $redux = $order->aggregate->reduxData(); + $modifiers = $order->applyModifiers(); + $aggregated_modifiers = \App\ModifiedValue::aggregateByType($modifiers); + $this->assertEquals(1, count($aggregated_modifiers)); + $exists = false; + + foreach($order->bookings as $booking) { + $mods = $booking->applyModifiersWithFriends($redux, true); + $booked_product = $booking->products()->where('product_id', $product->id)->first(); + + if (is_null($booked_product)) { + $this->assertEquals(0, $mods->count()); + } + else { + $exists = true; + $this->assertEquals(1, $mods->count()); + $m = $mods->first(); + $this->assertEquals($m->effective_amount * -1, round(($product->price * $booked_product->quantity) * 0.05, 4)); + } + } + + $this->assertTrue($exists); } private function reviewBookingsIntoOrder($mod, $test_shipping_value)