Skip to content

Commit

Permalink
Merge pull request #49 from heseya/feature/B2B-156
Browse files Browse the repository at this point in the history
B2B-156, B2B-71 and some other fixes
  • Loading branch information
pa-cholek authored Sep 4, 2024
2 parents ad75cbd + bde90dd commit af04dfb
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 31 deletions.
9 changes: 8 additions & 1 deletion app/Http/Controllers/ProductController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
use Domain\Product\Dtos\ProductCreateDto;
use Domain\Product\Dtos\ProductSearchDto;
use Domain\Product\Dtos\ProductUpdateDto;
use Domain\Product\Dtos\ProductVariantPriceDto;
use Heseya\Dto\DtoException;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Response as HttpResponse;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\Response as HttpResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class ProductController extends Controller
Expand Down Expand Up @@ -193,4 +195,9 @@ public function deleteAttachment(Product $product, MediaAttachment $attachment):

return Response::noContent();
}

public function process(Request $request, Product $product, ProductVariantPriceDto $dto): HttpResponse
{
return $this->productService->getPriceForVariant($product, $dto)->toResponse($request);
}
}
2 changes: 1 addition & 1 deletion app/Jobs/CalculateDiscount.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ public function __construct(Discount $discount, bool $updated = false)
*/
public function handle(DiscountService $discountService): void
{
$discountService->calculateDiscount($this->discount, $this->updated);
$discountService->calculateDiscount($this->discount);
}
}
88 changes: 70 additions & 18 deletions app/Services/DiscountService.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ public function calc(Money $value, Discount $discount): Money
}

/**
* @param Collection<int,SalesChannel>|null $salesChannels
*
* @throws ServerException
* @throws MoneyMismatchException
* @throws UnknownCurrencyException
Expand All @@ -283,7 +285,7 @@ public function calc(Money $value, Discount $discount): Money
* @throws NumberFormatException
* @throws StoreException
*/
public function calculateDiscount(Discount $discount, bool $updated): void
public function calculateDiscount(Discount $discount, ?Collection $salesChannels = null): void
{
// If discount has conditions based on time, then must be added or removed from cache
$this->checkIsDiscountActive($discount);
Expand All @@ -294,11 +296,11 @@ public function calculateDiscount(Discount $discount, bool $updated): void

// if job is called after update, then calculate discount for all products,
// because it may change the list of related products or target_is_allow_list value
if (!$updated && $discount->active) {
if ($discount->wasRecentlyCreated && $discount->active) {
$products = $this->allDiscountProductsIds($discount);
}

$this->applyDiscountsOnProductsLazy($products, $salesWithBlockList);
$this->applyDiscountsOnProductsLazy($products, $salesWithBlockList, $salesChannels);
}

/**
Expand Down Expand Up @@ -336,6 +338,8 @@ public function calcOrderShippingDiscounts(Order $order, OrderDto $orderDto): Or
}

/**
* @param Collection<int,Product> $products
*
* @throws MathException
* @throws StoreException
* @throws MoneyMismatchException
Expand Down Expand Up @@ -567,6 +571,9 @@ public function applyDiscountOnProduct(
}

/**
* @param Collection<int,Product> $products
* @param Collection<int,SalesChannel> $salesChannels
*
* @throws MoneyMismatchException
* @throws ServerException
* @throws UnknownCurrencyException
Expand All @@ -577,15 +584,17 @@ public function applyDiscountOnProduct(
* @throws DtoException
* @throws StoreException
*/
public function applyDiscountsOnProducts(Collection $products): void
public function applyDiscountsOnProducts(Collection $products, ?Collection $salesChannels = null): void
{
$salesWithBlockList = $this->getSalesWithBlockList();
foreach ($products as $product) {
$this->applyAllDiscountsOnProduct($product, $salesWithBlockList);
$this->applyAllDiscountsOnProduct($product, $salesWithBlockList, $salesChannels);
}
}

/**
* @param Collection<int,SalesChannel>|null $salesChannels
*
* @throws ServerException
* @throws MoneyMismatchException
* @throws UnknownCurrencyException
Expand All @@ -596,10 +605,10 @@ public function applyDiscountsOnProducts(Collection $products): void
* @throws DtoException
* @throws StoreException
*/
public function applyDiscountsOnProduct(Product $product): void
public function applyDiscountsOnProduct(Product $product, ?Collection $salesChannels = null): void
{
$salesWithBlockList = $this->getSalesWithBlockList();
$this->applyAllDiscountsOnProduct($product, $salesWithBlockList);
$this->applyAllDiscountsOnProduct($product, $salesWithBlockList, $salesChannels);
}

/**
Expand Down Expand Up @@ -678,6 +687,9 @@ public function applyDiscountOnOrder(Discount $discount, Order $order): Order
}

/**
* @param Collection<int,Discount> $salesWithBlockList
* @param Collection<int,SalesChannel>|null $salesChannels
*
* @throws StoreException
* @throws ClientException
* @throws NumberFormatException
Expand All @@ -691,13 +703,18 @@ public function applyDiscountOnOrder(Discount $discount, Order $order): Order
public function applyAllDiscountsOnProduct(
Product $product,
Collection $salesWithBlockList,
?Collection $salesChannels = null,
): void {
if ($salesChannels === null) {
$salesChannels = $this->salesChannelService->getCachedActiveSalesChannels();
}

$allOldPrices = [];
$allNewPrices = [];

$sales = $this->getAllAplicableSalesForProduct($product, $salesWithBlockList);

foreach ($this->salesChannelService->getCachedActiveSalesChannels() as $salesChannel) {
foreach ($salesChannels as $salesChannel) {
try {
$oldPrices = $this->priceService->getCachedProductPrices($product, [
ProductPriceType::PRICE_MIN,
Expand Down Expand Up @@ -731,6 +748,8 @@ public function applyAllDiscountsOnProduct(
}

/**
* @param Collection<int,Discount> $salesWithBlockList
*
* @return Collection<int,Discount>
*/
public function getAllAplicableSalesForProduct(Product $product, Collection $salesWithBlockList, bool $calcForCurrentUser = false): Collection
Expand Down Expand Up @@ -1053,6 +1072,7 @@ private function calcPrice(Money $price, string $productId, Discount $discount):

/**
* @param Collection<int,Discount> $sales
* @param array<string,string> $schemas
*
* @throws MoneyMismatchException
* @throws ServerException
Expand All @@ -1062,10 +1082,11 @@ private function calcPrice(Money $price, string $productId, Discount $discount):
* @throws NumberFormatException
* @throws DtoException
*/
private function calcAllDiscountsOnProduct(
public function calcAllDiscountsOnProductVariant(
Product $product,
Collection $sales,
SalesChannel $salesChannel,
array $schemas = [],
): ProductCachedPriceDto {
$priceMap = $salesChannel->priceMap;
assert($priceMap instanceof PriceMap);
Expand All @@ -1086,6 +1107,18 @@ private function calcAllDiscountsOnProduct(
$gross = $initialPrice->gross;
}

foreach ($schemas as $schemaId => $optionId) {
/** @var Schema $schema */
$schema = $product->schemas()->findOrFail($schemaId);

$schema_price = $schema->getPrice($optionId, $schemas, $priceMap->currency);
if ($priceMap->is_net) {
$schema_price = $this->salesChannelService->addVat($schema_price, $this->salesChannelService->getVatRate($salesChannel));
}

$gross = $gross->plus($schema_price);
}

$minPrice = $gross;
foreach ($sales as $sale) {
if ($minPrice->isGreaterThan($minimalProductPrice)) {
Expand All @@ -1102,7 +1135,26 @@ private function calcAllDiscountsOnProduct(
}

/**
* @param array<DiscountTargetType> $targetTypes
* @param Collection<int,Discount> $sales
*
* @throws MoneyMismatchException
* @throws ServerException
* @throws UnknownCurrencyException
* @throws RoundingNecessaryException
* @throws MathException
* @throws NumberFormatException
* @throws DtoException
*/
private function calcAllDiscountsOnProduct(
Product $product,
Collection $sales,
SalesChannel $salesChannel,
): ProductCachedPriceDto {
return $this->calcAllDiscountsOnProductVariant($product, $sales, $salesChannel);
}

/**
* @param array<int,DiscountTargetType> $targetTypes
*
* @throws ClientException
* @throws MathException
Expand Down Expand Up @@ -1203,9 +1255,9 @@ private function calcOrderProductDiscount(
}

/**
* @param Collection<array-key, Discount> $discounts
* @param Collection<array-key,Discount> $discounts
*
* @return Collection<array-key, Discount>
* @return Collection<array-key,Discount>
*/
private function sortDiscounts(Collection $discounts): Collection
{
Expand All @@ -1218,6 +1270,10 @@ private function sortDiscounts(Collection $discounts): Collection
}

/**
* @param Collection<int,string> $productIds
* @param Collection<int,Discount> $salesWithBlockList
* @param Collection<int,SalesChannel>|null $salesChannels
*
* @throws MoneyMismatchException
* @throws ServerException
* @throws UnknownCurrencyException
Expand All @@ -1228,7 +1284,7 @@ private function sortDiscounts(Collection $discounts): Collection
* @throws NumberFormatException
* @throws StoreException
*/
private function applyDiscountsOnProductsLazy(Collection $productIds, Collection $salesWithBlockList): void
public function applyDiscountsOnProductsLazy(Collection $productIds, Collection $salesWithBlockList, ?Collection $salesChannels = null): void
{
$productQuery = Product::with([
'discounts',
Expand All @@ -1241,11 +1297,7 @@ private function applyDiscountsOnProductsLazy(Collection $productIds, Collection
$productQuery = $productQuery->whereIn('id', $productIds);
}

$productQuery->chunk(100, function ($products) use ($salesWithBlockList): void {
foreach ($products as $product) {
$this->applyAllDiscountsOnProduct($product, $salesWithBlockList);
}
});
$productQuery->chunk(100, fn (Collection $products) => $products->each(fn (Product $product) => $this->applyAllDiscountsOnProduct($product, $salesWithBlockList, $salesChannels)));
}

/**
Expand Down
43 changes: 38 additions & 5 deletions app/Services/ProductService.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
use Domain\Product\Dtos\ProductCreateDto;
use Domain\Product\Dtos\ProductSearchDto;
use Domain\Product\Dtos\ProductUpdateDto;
use Domain\Product\Dtos\ProductVariantPriceDto;
use Domain\Product\Models\ProductBannerMedia;
use Domain\Product\Resources\ProductVariantPriceResource;
use Domain\ProductAttribute\Models\Attribute;
use Domain\ProductAttribute\Models\AttributeOption;
use Domain\ProductAttribute\Services\AttributeService;
Expand All @@ -45,6 +47,7 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Spatie\LaravelData\DataCollection;
use Spatie\LaravelData\Optional;
Expand Down Expand Up @@ -143,18 +146,26 @@ public function delete(Product $product): void
DB::commit();
}

public function updateMinPrices(Product $product): void
/**
* @param Collection<int,SalesChannel>|null $salesChannels
*/
public function updateMinPrices(Product $product, ?Collection $salesChannels = null): void
{
$this->updateInitialPricesForAllActiveSalesChannels($product);
$salesChannels = $salesChannels === null ? $this->salesChannelService->getCachedActiveSalesChannels() : $salesChannels;

$this->discountService->applyDiscountsOnProduct($product);
$this->updateInitialPricesForSalesChannels($product, $salesChannels);

$this->discountService->applyDiscountsOnProduct($product, $salesChannels);
}

public function updateInitialPricesForAllActiveSalesChannels(Product $product): void
/**
* @param Collection<int,SalesChannel> $salesChannels
*/
public function updateInitialPricesForSalesChannels(Product $product, Collection $salesChannels): void
{
$prices = [];

foreach (SalesChannel::active()->hasPriceMap()->with('priceMap')->get() as $salesChannel) {
foreach ($salesChannels as $salesChannel) {
$priceMap = $salesChannel->priceMap;
assert($priceMap instanceof PriceMap);

Expand All @@ -181,7 +192,29 @@ public function updateInitialPricesForAllActiveSalesChannels(Product $product):
$this->priceService->setCachedProductPrices($product->getKey(), [ProductPriceType::PRICE_INITIAL->value => $prices]);
}

public function getPriceForVariant(Product $product, ProductVariantPriceDto $dto, bool $calculateForCurrentUser = false): ProductVariantPriceResource
{
$salesChannel = $this->salesChannelService->getCurrentRequestSalesChannel();
$priceMap = $salesChannel->priceMap;

if ($priceMap === null) {
throw new ClientException(Exceptions::CLIENT_SALES_CHANNEL_PRICE_MAP);
}

$price_initial = $product->mappedPriceForPriceMap($priceMap);

$sales = $this->discountService->getAllAplicableSalesForProduct($product, $this->discountService->getSalesWithBlockList(), $calculateForCurrentUser);
$price = $this->discountService->calcAllDiscountsOnProductVariant($product, $sales, $salesChannel, $dto->schemas);

return ProductVariantPriceResource::from([
'price_initial' => ProductCachedPriceDto::from($price_initial, $salesChannel),
'price' => $price,
]);
}

/**
* Returns initial mix/max prices, without calculating tax.
*
* @return Money[]
*/
public function getMinMaxPrices(Product $product, Currency|PriceMap|SalesChannel $filter = null): array
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

use Domain\PriceMap\PriceMap;
use Domain\PriceMap\PriceMapProductPrice;
use Domain\PriceMap\PriceMapSchemaOptionPrice;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
PriceMapProductPrice::whereNotIn('price_map_id', PriceMap::query()->pluck('id'))->delete();
PriceMapSchemaOptionPrice::whereNotIn('price_map_id', PriceMap::query()->pluck('id'))->delete();

Schema::table('price_map_product_prices', function (Blueprint $table): void {
$table->foreign('price_map_id')->references('id')->on('price_maps')->cascadeOnDelete();
});

Schema::table('price_map_schema_option_prices', function (Blueprint $table): void {
$table->foreign('price_map_id')->references('id')->on('price_maps')->cascadeOnDelete();
});
}

public function down(): void {}
};
3 changes: 3 additions & 0 deletions routes/product.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@
->middleware('can:products.edit');
Route::delete('id:{product:id}/attachments/id:{attachment:id}', [ProductController::class, 'deleteAttachment'])
->middleware('can:products.edit');

Route::post('id:{product:id}/process', [ProductController::class, 'process'])
->middleware('can:products.show', 'can:cart.verify');
});
Loading

0 comments on commit af04dfb

Please sign in to comment.