From 3aba1610f74e4fa15153b4a7f0af30c3e2a13ca2 Mon Sep 17 00:00:00 2001 From: OniiCode Date: Tue, 19 Nov 2024 09:34:43 +0000 Subject: [PATCH 1/6] Update quantity validation logic to also take into account the requirements occurrence in the bundle line items --- src/Models/CartRequirement.php | 73 ++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/Models/CartRequirement.php b/src/Models/CartRequirement.php index 3918f42..a1e177f 100644 --- a/src/Models/CartRequirement.php +++ b/src/Models/CartRequirement.php @@ -146,37 +146,78 @@ public function quantityIsEligible(CartItem $cartItem): array public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProductVariantsInCart, $set_of_required_shopify_product_ids): array { - $totalRequirementsMetQuantity = 0; - $hasMisMatch = false; - - $requirementsMet = $nonWhiteGloveProductVariantsInCart - ->map(fn ($item) => $item->getProduct()?->id) + // Find required products at the top level + $topLevelProductIds = $nonWhiteGloveProductVariantsInCart + ->map(fn($item) => $item->getProduct()?->id) ->values() ->intersect($set_of_required_shopify_product_ids); - foreach ($requirementsMet as $id) { - /** @var CartItem $retrievedCartItem */ - $retrievedCartItem = $nonWhiteGloveProductVariantsInCart->first(fn ($item) => $item->getProduct()?->id === $id); - $totalRequirementsMetQuantity += $retrievedCartItem->getQuantity(); + // Check bundles if no required products are found at the top level + if ($topLevelProductIds->isEmpty()) { + $bundleProductVariantIds = resolve(GoLoadUp::class)->getProductVariantIdsOfBundleLineItemsInCart(); + $bundleRequirementsMet = $bundleProductVariantIds->intersect($set_of_required_shopify_product_ids); + + foreach ($bundleRequirementsMet as $productId) { + $occurrenceCount = $this->getRequiredProductOccurrenceCountInBundles($productId); - if ($wgCartItem->getQuantity() > $retrievedCartItem->getQuantity()) { - $hasMisMatch = true; + if ($this->isQuantityMismatch($wgCartItem->getQuantity(), $occurrenceCount, $wgCartItem)) { + return $this->generateErrorResponse($wgCartItem); + } } - } - if ($totalRequirementsMetQuantity >= $wgCartItem->getQuantity()) { return [true, null]; } - if ($hasMisMatch) { - $errorMessage = 'The number of '. $wgCartItem?->getVariant()?->title . ' services you selected does not match the number of eligible products in your cart. Please double-check the items in your cart to ensure the quantity of ' . $wgCartItem?->getVariant()?->title . ' services matches the number of eligible products.'; - return [false, $errorMessage]; + // Validate quantities for top-level products + foreach ($topLevelProductIds as $productId) { + $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn($item) => $item->getProduct()?->id === $productId); + $occurrenceCount = $this->getRequiredProductOccurrenceCountInBundles($productId); + + $totalEligibleQuantity = ($cartItem->getQuantity() ?? 0) + $occurrenceCount; + + if ($this->isQuantityMismatch($wgCartItem->getQuantity(), $totalEligibleQuantity, $wgCartItem)) { + return $this->generateErrorResponse($wgCartItem); + } } return [true, null]; } + private function isQuantityMismatch(int $selectedQuantity, int $availableQuantity, CartItem $wgCartItem): bool + { + return $selectedQuantity > $availableQuantity; + } + + private function generateErrorResponse(CartItem $wgCartItem): array + { + $serviceTitle = $wgCartItem?->getVariant()?->title ?? 'service'; + $errorMessage = sprintf( + 'The number of %s services you selected does not match the number of eligible products in your cart. ' . + 'Please double-check the items in your cart to ensure the quantity of %s services matches the number of eligible products.', + $serviceTitle, + $serviceTitle + ); + + return [false, $errorMessage]; + } + + public function getRequiredProductOccurrenceCountInBundles($productId): int + { + $bundleLineItems = resolve(GoLoadUp::class)->getBundleLineItemsInCart(); + $count = 0; + + foreach ($bundleLineItems as $bundleItem) { + foreach ($bundleItem->getComponents() as $component) { + if ($component->findModel()?->product_id === $productId) { + $count += $component->getQuantity(); + } + } + } + + return $count; + } + public function errorMessage(): string { return $this->error_message; From 8ba97e9c6b2e40ed4ffa6dd6a8dd179a6b1804a7 Mon Sep 17 00:00:00 2001 From: OniiCoder Date: Tue, 19 Nov 2024 09:35:05 +0000 Subject: [PATCH 2/6] Fix styling --- src/Models/CartRequirement.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Models/CartRequirement.php b/src/Models/CartRequirement.php index a1e177f..882f42a 100644 --- a/src/Models/CartRequirement.php +++ b/src/Models/CartRequirement.php @@ -148,7 +148,7 @@ public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProduc { // Find required products at the top level $topLevelProductIds = $nonWhiteGloveProductVariantsInCart - ->map(fn($item) => $item->getProduct()?->id) + ->map(fn ($item) => $item->getProduct()?->id) ->values() ->intersect($set_of_required_shopify_product_ids); @@ -171,7 +171,7 @@ public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProduc // Validate quantities for top-level products foreach ($topLevelProductIds as $productId) { - $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn($item) => $item->getProduct()?->id === $productId); + $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn ($item) => $item->getProduct()?->id === $productId); $occurrenceCount = $this->getRequiredProductOccurrenceCountInBundles($productId); $totalEligibleQuantity = ($cartItem->getQuantity() ?? 0) + $occurrenceCount; From a5c4a3005bb95fdb629a54e8ba0d4a58e5fdb9c6 Mon Sep 17 00:00:00 2001 From: OniiCode Date: Tue, 19 Nov 2024 23:31:50 +0000 Subject: [PATCH 3/6] Improve code and fix some bugs --- src/Models/CartRequirement.php | 43 ++++++++++++++-------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/Models/CartRequirement.php b/src/Models/CartRequirement.php index a1e177f..d3d1fda 100644 --- a/src/Models/CartRequirement.php +++ b/src/Models/CartRequirement.php @@ -135,6 +135,8 @@ public function quantityIsEligible(CartItem $cartItem): array return $firstRequirementsQuantityError; } + if (empty($this->second_set_of_required_shopify_product_ids)) return [true, null]; + $secondRequirementsQuantityError = $this->catchQuantityMismatch($cartItem, $nonWhiteGloveProductVariantsInCart, $this->second_set_of_required_shopify_product_ids); if (! $secondRequirementsQuantityError[0]) { @@ -146,49 +148,38 @@ public function quantityIsEligible(CartItem $cartItem): array public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProductVariantsInCart, $set_of_required_shopify_product_ids): array { - // Find required products at the top level + // Find required products that appear at the top level $topLevelProductIds = $nonWhiteGloveProductVariantsInCart ->map(fn($item) => $item->getProduct()?->id) ->values() ->intersect($set_of_required_shopify_product_ids); - // Check bundles if no required products are found at the top level - if ($topLevelProductIds->isEmpty()) { - $bundleProductVariantIds = resolve(GoLoadUp::class)->getProductVariantIdsOfBundleLineItemsInCart(); - $bundleRequirementsMet = $bundleProductVariantIds->intersect($set_of_required_shopify_product_ids); - - foreach ($bundleRequirementsMet as $productId) { - $occurrenceCount = $this->getRequiredProductOccurrenceCountInBundles($productId); - - if ($this->isQuantityMismatch($wgCartItem->getQuantity(), $occurrenceCount, $wgCartItem)) { - return $this->generateErrorResponse($wgCartItem); - } - } + $totalRequirementsMetQuantity = 0; - return [true, null]; + // get total quantity of required products that appear at the top level + foreach ($topLevelProductIds as $productId) { + $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn($item) => $item->getProduct()?->id === $productId); + $totalRequirementsMetQuantity += ($cartItem->getQuantity() ?? 0); } + // Find required products that appear in bundles + $bundleProductVariantIds = resolve(GoLoadUp::class)->getProductVariantIdsOfBundleLineItemsInCart(); + $bundleRequirementsMet = $bundleProductVariantIds->intersect($set_of_required_shopify_product_ids); - // Validate quantities for top-level products - foreach ($topLevelProductIds as $productId) { - $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn($item) => $item->getProduct()?->id === $productId); + // get total quantity of required products that appear in bundles + foreach ($bundleRequirementsMet as $productId) { $occurrenceCount = $this->getRequiredProductOccurrenceCountInBundles($productId); - $totalEligibleQuantity = ($cartItem->getQuantity() ?? 0) + $occurrenceCount; + $totalRequirementsMetQuantity += $occurrenceCount; + } - if ($this->isQuantityMismatch($wgCartItem->getQuantity(), $totalEligibleQuantity, $wgCartItem)) { - return $this->generateErrorResponse($wgCartItem); - } + if ($wgCartItem->getQuantity() > $totalRequirementsMetQuantity) { + return $this->generateErrorResponse($wgCartItem); } return [true, null]; } - private function isQuantityMismatch(int $selectedQuantity, int $availableQuantity, CartItem $wgCartItem): bool - { - return $selectedQuantity > $availableQuantity; - } - private function generateErrorResponse(CartItem $wgCartItem): array { $serviceTitle = $wgCartItem?->getVariant()?->title ?? 'service'; From 3347c5ff4c236433f76c77fb2dd6ed9c999abd96 Mon Sep 17 00:00:00 2001 From: OniiCode Date: Tue, 19 Nov 2024 23:34:23 +0000 Subject: [PATCH 4/6] Remove line break --- src/Models/CartRequirement.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Models/CartRequirement.php b/src/Models/CartRequirement.php index d3d1fda..67238e2 100644 --- a/src/Models/CartRequirement.php +++ b/src/Models/CartRequirement.php @@ -169,7 +169,6 @@ public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProduc // get total quantity of required products that appear in bundles foreach ($bundleRequirementsMet as $productId) { $occurrenceCount = $this->getRequiredProductOccurrenceCountInBundles($productId); - $totalRequirementsMetQuantity += $occurrenceCount; } From 4432b30f73da4b0f65859beed71670534930e340 Mon Sep 17 00:00:00 2001 From: OniiCoder Date: Tue, 19 Nov 2024 23:34:43 +0000 Subject: [PATCH 5/6] Fix styling --- src/Models/CartRequirement.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Models/CartRequirement.php b/src/Models/CartRequirement.php index 67238e2..bfae003 100644 --- a/src/Models/CartRequirement.php +++ b/src/Models/CartRequirement.php @@ -135,7 +135,9 @@ public function quantityIsEligible(CartItem $cartItem): array return $firstRequirementsQuantityError; } - if (empty($this->second_set_of_required_shopify_product_ids)) return [true, null]; + if (empty($this->second_set_of_required_shopify_product_ids)) { + return [true, null]; + } $secondRequirementsQuantityError = $this->catchQuantityMismatch($cartItem, $nonWhiteGloveProductVariantsInCart, $this->second_set_of_required_shopify_product_ids); @@ -150,7 +152,7 @@ public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProduc { // Find required products that appear at the top level $topLevelProductIds = $nonWhiteGloveProductVariantsInCart - ->map(fn($item) => $item->getProduct()?->id) + ->map(fn ($item) => $item->getProduct()?->id) ->values() ->intersect($set_of_required_shopify_product_ids); @@ -158,7 +160,7 @@ public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProduc // get total quantity of required products that appear at the top level foreach ($topLevelProductIds as $productId) { - $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn($item) => $item->getProduct()?->id === $productId); + $cartItem = $nonWhiteGloveProductVariantsInCart->first(fn ($item) => $item->getProduct()?->id === $productId); $totalRequirementsMetQuantity += ($cartItem->getQuantity() ?? 0); } From f4a2e2649f8126e405c713b12ed03cb7284bd9b0 Mon Sep 17 00:00:00 2001 From: OniiCode Date: Tue, 3 Dec 2024 19:29:13 +0000 Subject: [PATCH 6/6] Account for the 'split_line_items_to_one_quantity_per_line_item' feature --- src/Models/CartRequirement.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Models/CartRequirement.php b/src/Models/CartRequirement.php index bfae003..419c091 100644 --- a/src/Models/CartRequirement.php +++ b/src/Models/CartRequirement.php @@ -174,13 +174,27 @@ public function catchQuantityMismatch(CartItem $wgCartItem, $nonWhiteGloveProduc $totalRequirementsMetQuantity += $occurrenceCount; } - if ($wgCartItem->getQuantity() > $totalRequirementsMetQuantity) { + if ($this->getTotalQuantityOfSpecifiedProductInCart($wgCartItem) > $totalRequirementsMetQuantity) { return $this->generateErrorResponse($wgCartItem); } return [true, null]; } + public function getTotalQuantityOfSpecifiedProductInCart(CartItem $wgCartItem) + { + // Admin can set line items to split to one quantity per line item in cart. + // This will require we make sure we have the accurate quantity of the go load up service we have in the cart. + if (settings(\Astrogoat\Shopify\Settings\ShopifySettings::class, 'split_line_items_to_one_quantity_per_line_item') === true) { + + $allDuplicates = resolve(GoLoadUp::class)->getWhiteGloveProductVariantsInCart(); // get all duplicates of the service product in the cart + + return $allDuplicates->count(); + } + + return $wgCartItem->getQuantity(); + } + private function generateErrorResponse(CartItem $wgCartItem): array { $serviceTitle = $wgCartItem?->getVariant()?->title ?? 'service';